实现瀑布流布局,就这几行代码?

2022年06月22日08:31:02 热门 1338

大家好,我是Echa 哥,瀑布流布局我记得前年有跟大家介绍用纯CSS方案实现过,不知道粉丝们还记得吗? 不记得没关系,请见实现瀑布流布局,就这几行代码? - 天天要闻

前言

瀑布流布局是一种比较流行的页面布局方式,表现为参差不齐的多栏卡片。跟网格布局相比,显得更灵动,更具艺术气息。

实现瀑布流布局的方式有多种,比如multi-column布局,grid布局,flex 布局等。但是这些实现方式都有各自的局限性,代码也略复杂。

其实,有个最原始、最简单,也是兼容性最好的实现方式,那就是使用绝对定位。瀑布流布局的元素是一些等宽不等高的卡片,只要根据元素的实际宽高计算出自己的坐标位置就行了。

要计算坐标自然要用到 JavaScript,这就不是纯 CSS 方案,对某些前端来讲显得不那么纯粹。不过只要理清思路了,也用不了几行代码。本文就给出最近实现的一个版本。

// 计算每个卡片的坐标
export function calcPositions({ columns = 2, gap = 7, elements }) {
  if (!elements || !elements.length) {
    return [];
  }
  const y = []; //上一行卡片的底部纵坐标数组,用于找到新卡片填充位置
  const positions = []; // 每个卡片的坐标数组
  elements.forEach((item, index) => {
    if (y.length < columns) { // 还未填满一行
      y.push(item.offsetHeight);
      positions.push({
        left: (index % columns) * (item.offsetWidth + gap),
        top: 0
      });
    } else {
      const min = Math.min(...y); // 最小纵坐标
      const idx = y.indexOf(min); // 纵坐标最小的卡片索引
      y.splice(idx, 1, min + gap + item.offsetHeight); // 替换成新卡片的纵坐标
      positions.push({
        left: idx * (item.offsetWidth + gap),
        top: min + gap
      });
    }
  });
// 由于采用绝对定位,容器是无法自动撑开的。因此需要计算实际高度,即最后一个卡片的top加上自身高度
  return { positions, containerHeight: positions[positions.length - 1].top + elements[elements.length - 1].offsetHeight };
}

上面这段代码的作用就是计算每个卡片的lefttop,以及容器的总高度。关键位置都有注释,应该不难理解。

有了这几行核心代码,要想封装成瀑布流组件就很容易了。以 Vue 为例,可以这样封装:
MasonryLite.vue

<template>
  <div class="masonry-lite">
    <slot></slot>
  </div>
</template>
<script>
import { calcPositions } from './index.js';
export default {
  name: 'MasonryLite',
  props: {
    gap: {
      type: Number,
      default: 12,
    },
    columns: {
      type: Number,
      default: 2,
    },
  },
  data() {
    return {};
  },
  mounted() {
    this.doLayout();
  },
  methods: {
    doLayout() {
      const children = [...this.$el.querySelectorAll('.masonry-item')];
      if (children.length === 0) {
        return;
      }
      const { positions, containerHeight } = calcPositions({
        elements: children,
        columns: this.columns,
        gap: this.gap,
      });
      children.forEach((item, index) => {
        item.style.cssText = `left:${positions[index].left}px;top:${positions[index].top}px;`;
      });
      this.$el.style.height = `${containerHeight}px`;
    },
  },
};
</script>
<style lang="scss" scoped>
.masonry-lite{
  position: relative;
}
.masonry-item {
  position: absolute;
}
</style>

使用组件:

<MasonryLite>
  <div class="product-card masonry-item" v-v-for="(item, index) in items" :key="index">
    <img :src="item.imageUrl" />
    <header>{{ item.title }}</header>
  </div>
</MasonryLite>

不过这样其实还会有点问题,就是doLayout的执行时机。因为该方案基于绝对定位,需要元素在渲染完成后才能获取到实际宽高。如果卡片内有延迟加载的图片或者其他动态内容,高度会发生变化。这种情况下就需要在DOM更新后主动调用一次doLayout重新计算布局。

如果大家有更好的实现方案,欢迎交流!

代码仓库:https://github.com/kaysonli/masonry-lite

npm 包:masonry-lite

如果觉得对你有帮助,帮忙点个不要钱的star。

热门分类资讯推荐

曾小贤的上司Lisa榕,现实中不仅才貌双全,还嫁给了CEO - 天天要闻

曾小贤的上司Lisa榕,现实中不仅才貌双全,还嫁给了CEO

曾小贤的上司Lisa榕,现实中不仅才貌双全,还嫁给了CEO虽然说《爱情公寓》这部剧在剧情上充满了争议,但是一定程度上,这部剧也是很多人的回忆,是伴随了一代人的青春回忆,而且剧中的很多角色都成为了经典,他们的口头禅也一直被拿来玩儿梗。
Lisa榕做主持多年没红,被陈赫拉进爱情公寓爆红,如今怎样了 - 天天要闻

Lisa榕做主持多年没红,被陈赫拉进爱情公寓爆红,如今怎样了

谈到《爱情公寓》这部火爆一时的欢乐喜剧,大家肯定都不陌生。不知道大家是否还记得《爱情公寓》中那个把曾小贤治得服服帖帖的女上司Lisa榕,现实中的她名叫榕榕,和剧中的形象也判若两人。1981年出生在辽宁沈阳的榕榕,毕业于上海戏剧学院,后来成为了上海东方传媒集团有限公司的一名主持人。