JS+CSS实现瀑布流布局:多列属性与动态定位详解

作者:竹影

时间:2026-10-31

浏览:656

本文详细解析两种实现瀑布流布局的方法。首先介绍利用CSS columns属性实现静态多列布局,适用于固定数据且对顺序无严格要求的场景。其次深入讲解基于JavaScript绝对定位的动态布局方案,涵盖列数计算、图片加载监听、Promise.all处理异步数据及滚动加载逻辑,解决图片未加载导致高度获取错误的问题,实现类似Pinterest的错落有致效果。

文章目录

前言

瀑布流布局是一种视觉呈现为多栏且高度不一的网页排版方式。它广泛应用于以图片展示为主的页面,特点是图片等宽但高度各异。当一行从左至右排满后,后续元素会自动填充到当前高度最短的列下方。随着用户向下滚动页面,新数据会持续加载并追加至底部,形成错落有致、不拘一格的视觉效果。本文将分别通过CSS和JavaScript两种技术路径来实现这一布局效果。

CSS实现瀑布流布局

这种基于CSS实现的瀑布流布局,其内容数据通常是固定的。核心实现依赖于CSS提供的多列布局属性 columns。

页面结构方面,需要创建一个主容器container,用于包裹所有展示项item(包括图片和标题文字)。

  <div class="container">
    <div class="item">
      <img src="..."/>
      <h2>...</h2>
    </div>
    ...
</div>

在样式设置上,需给容器container指定宽度并使其居中显示。同时,通过CSS控制内容分为3列显示 columns: 3;,并设置列间距为20px column-gap: 20px;。

对于每个item元素,需设置特定属性 break-inside: avoid;,以防止在多列布局中元素内容被意外分割。这能避免图片显示在一列末尾而标题文字出现在下一列开头的情况。

.container {
  width: 1200px;
  margin: 20px auto;
  columns: 3;
  column-gap: 20px;
}

.container .item {
  width: 100%;
  break-inside: avoid;
  margin-bottom: 20px;
}
.container .item img{
  width: 100%;
}

前言

小结:从上述结构可以看出,利用CSS多列布局实现的瀑布流,其元素排列顺序是从上到下,再从左到右。这种方式适用于对元素显示顺序没有严格要求,且页面数据相对固定的场景。

JS实现瀑布流布局

思路:

核心逻辑是利用绝对定位动态计算每个元素的top和left值。首先计算一行能容纳的列数,第一行元素直接设置top为0,left值为索引乘以(元素宽度+间隙)。后续元素的排列,需找出当前所有列中高度最小的一列,将新元素添加至该列下方,直至所有元素排列完毕。

创建一个容器container用于存放展示元素,CSS设置为相对定位;内部展示的元素则设置为绝对定位。

<div id="container"></div>

关键步骤之一:计算页面一行能容纳几列元素。

列数计算公式为:列数 = 页面的宽度 / (元素的宽度+元素之间的间隙 ) column = pageWidth / (itemWidth + gap);。

注意:这里的页面宽度通常指body的宽度,且已通过CSS设置了最大宽度和最小宽度限制。

创建createElement函数,用于动态生成组成瀑布流的元素块(包含图片和标题)。在数据请求成功后调用此函数,将元素添加到页面并执行瀑布流布局逻辑。

function createElement(itemData) {
  return new Promise((resolve, reject) => {
    let div = document.createElement('div')
    div.innerHTML = `...`
    div.className = 'item'
    // 监听图片的load事件
    div.querySelector('img').addEventListener('load', () => {
      resolve(div)
    })
  })
}

// 获取喵咪的数据
let page = 1  //请求数据的页码
let isReq = false  //设置节流阀,处于请求过程中,则不再请求数据
function loadNewData() {
  if (isReq) return
  isReq = true
  getData()
    .then(data => {
      // 处理新的数据
      let items = data
      let promises = items.map(item => createElement(item))
      Promise.all(promises).then(nodes => {
        let frag = document.createDocumentFragment()
        isReq = false
        page++
        nodes.forEach(node => {
          frag.appendChild(node)
          Node.push(node)
        })
        document.getElementById('container').appendChild(frag)
        waterfall(Node)
      })
    })
    
}

注意:此处需解决获取元素块高度不正确的问题。

由于图片可能尚未完全加载,此时获取的元素块高度仅包含h4标签的高度,导致布局错误。因此,必须在图片加载完成后才能准确获取元素高度。

a. 通过监听图片的 load 事件,当事件触发时,表示图片已加载完成,此时返回创建的div元素。

b. 在获取数据loadNewData()函数中,对返回的数据执行创建元素方法,并使用Promise.all()对由返回的div元素(Promise 实例)组成的Promise 数组进行处理。当每个Promise实例的状态变为 fulfilled(所有图片都加载完成),即执行瀑布流布局。

创建waterfall函数,实现具体的瀑布流定位逻辑。

该函数接收元素数组,并对每个元素进行布局计算。若元素位于第一行,其top值为0,left值为索引乘以(元素宽度+间隙)。若元素不在第一行,则先找出高度最小的列索引,将元素放置于该列下方,并更新该列的高度记录。

function waterfall(items) {
  for (var i = loaded; i < items.length; i++) {
    let item = items[i]
    let height = item.offsetHeight
    if (i < columns) {
      //满足这个条件则说明在第一行
      item.style.top = 0
      item.style.left = (itemWidth + gap) * i + 'px'
      heightArr.push(height)
    } else {
      //其他行,先找出最小高度列的索引
      const minIndex = getMinIndex(heightArr)
      //top值就是最小列的高度+gap
      const top = heightArr[minIndex] + gap
      item.style.top = top + 'px'
      item.style.left = minIndex * (itemWidth + gap) + 'px'

      //修改原本最小列的高度 
      heightArr[minIndex] = top + height
    }
    loaded = items.length
  }
}

//用于获取heightArr数组中值最小的列的索引。
const getMinIndex = (array) => {
  const min = Math.min.apply(null, array)
  return array.indexOf(min)
}

监听页面滚动事件,当未显示的元素内容高度小于屏幕高度的一定比例(此处设置为0.7,因数据加载较慢,一般设为0.5即可)时,触发新数据的加载与展示。

window.addEventListener('scroll', lazy)
function lazy() {
 const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
 const documentHeight = document.documentElement.scrollHeight
  // 未显示的内容 = 文档(内容)高度-滚动高度-屏幕高度
  if (documentHeight - scrollTop - clientHeight < 0.7 * clientHeight) {
    loadNewData()
  }
}

前言

小结:

a. 文中未展示简单封装的请求数据方法getData(),所用接口来自网络(地址已省略),每次仅加载10张图片且加载速度较慢,读者可自行寻找更优接口。

b. 需要定义的变量包括页面宽度、元素宽度、间隙、列数组高度记录等,具体见代码实现。

前言

基于上述逻辑,即可实现基本的瀑布流布局,其排列顺序和滚动加载数据的体验也符合一般用户的使用习惯。

以上就是js+CSS简单实现瀑布流布局的详细内容,更多关于js瀑布流布局的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章