CSS3: 像小红书按行加载数据的瀑布流终于有方案了

2,112 阅读4分钟

什么是瀑布流

瀑布流布局就像在游乐园里排队玩水滑梯一样有趣!想象一下,你站在一个高高的平台上,眼前是一排排的水滑道,每个滑道的终点都是个小水池。你站在起点,一溜烟地往下滑,速度飞快!你的朋友们也在旁边的滑道上滑来滑去,好不热闹。

瀑布流布局如此有趣,因为它不像传统的布局一样一行行地摆放元素,而是像水滑梯一样,元素从上到下、从左到右地排列。每个元素的大小不一,就像人们身材各异,有的高,有的矮,有的胖,有的瘦。它们像水滑道上的人一样紧紧挨着,在空间里形成了一个有趣的错落有致的景象。

想象一下,你站在最高的滑道上,你是那个最炫酷的元素,所有的目光都聚集在你身上。你自豪地往下滑,经过每个滑道上的人们,他们惊叹你的高度,赞叹你的美丽。你顺利地滑到终点,掀起一片水花,大家都为你鼓掌欢呼!

瀑布流布局就是这样有趣又引人注目,每个元素都有机会在舞台上展示自己的风采。它让我们的网页看起来活力十足,像一场欢乐的游乐园冒险!

小红书上的瀑布流布局

就是这样,每个商品就像超市里的货架,展示着各种各样的内容。你可以往下滑动页面,看到一个美食的推荐,继续滑动又遇到了一篇旅行攻略,再往下滑就是一篇关于时尚的文章。这些内容就像超市里的商品,你可以随心所欲地浏览,发现自己感兴趣的东西,积极互动,获取灵感

你以为的这样

image.png

哈哈,虽然不是这样,但却有共同的特性

image.png

从图中可以看出它的特性,

从布局角度来看,瀑布流布局更类似网格布局中自动放置网格项目的布局,但又没有严格遵循该布局模式。

最著名的瀑布流布当属 Pinterest,比如他的搜索结果页面的布局效果:

img

我们来看个例子

<div class="masonry--container">
    <div class="item">
        <img src="https://picsum.photos/1024/860?random=1" alt="">
        <h3>Blog Post</h3>
        <p>Lorem ipsum dolor si...</p>
    </div>
    <!-- 省略其他 item -->
</div>
@layer layout {
    .masonry--container {
        column-count: 4;
        column-gap: 1rem;
    }
}

在上面的例子中,你会看到整个布局看起来像瀑布流布局。然而,项目(卡片)的顺序沿着列运行。也就是说,使用多列布局来实现瀑布流布局和真正的瀑布流布局之间有着关键性的区别:“**在多列布局中,项目(卡片)是按列显示的,通常在瀑布流布局中,希望项目(卡片)是按行显示**”。

借网上的一个图:

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1edd5a26f57f4f7babe14fd1729cbfb6~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=637&h=372&s=81684&e=png&b=fcfcfc)

因此,多列布局实现的“瀑布流布局”有可能无法满足我们实际的业务需求。例如,Pinterest 网站的搜索结果页,期望搜索出来的结果总是排列在前面,比如说页面最顶部就能看到搜索的结果,而不是像多列布局实现的效果那样,排在前面的都在第一列。

而且加载更多的时候,我们是希望按行加载的,而不是重新按多列排。


# 调研

目前单用css还是无法实现真正的瀑布流布局, 即便使用CSS 网格布局中自动放置网格项目的特性(即 `grid-auto-flow: dense`),也会有诸多问题。

只能通过js来弥补这个上面提到的缺陷
当使用JavaScript来实现按行加载的瀑布流时,可以结合使用DOM操作和CSS样式来达到效果。以下是一个简单的JavaScript方案:

首先,创建一个包含瀑布流元素的容器,并为每个元素添加一个自定义的类名,例如".item"。

```html
<div class="container">
  <div class="item">瀑布流元素1</div>
  <div class="item">瀑布流元素2</div>
  ...
</div>

接下来,使用JavaScript来控制瀑布流的加载和排序。

window.addEventListener("load", function() {
  // 获取容器和瀑布流元素
  const container = document.querySelector(".container");
  const items = document.querySelectorAll(".item");

  // 记录每一行的高度
  let rowHeights = [];

  // 遍历瀑布流元素,计算每一行的高度并进行排序
  items.forEach(function(item) {
    // 将元素添加到容器中
    container.appendChild(item);

    // 获取元素的高度
    const itemHeight = item.offsetHeight;

    // 检查当前行是否有足够的空间容纳元素
    const rowIndex = rowHeights.findIndex(function(rowHeight) {
      return rowHeight + itemHeight <= container.offsetHeight;
    });

    if (rowIndex !== -1) {
      // 如果有足够的空间,将元素添加到对应行中
      item.style.order = rowIndex;
      rowHeights[rowIndex] += itemHeight;
    } else {
      // 如果没有足够的空间,创建新行并添加元素
      const newRow = document.createElement("div");
      newRow.classList.add("row");
      newRow.appendChild(item);
      container.appendChild(newRow);
      rowHeights.push(itemHeight);
    }
  });
});

在这个JavaScript方案中,我们使用offsetHeight属性获取到每个瀑布流元素的高度,并根据容器的高度动态计算每一行的高度。然后,通过设置元素的order属性来实现按行排序的效果。

记得在CSS中设置.row类的样式,以便美化新创建的行。

.row {
  display: flex;
}

以上的JavaScript方案会在页面加载完成后自动按行加载和排序瀑布流元素

当然,后续还需进一步优化,欢迎大家指教