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

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

[[416106]]

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

瀑布流布局

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

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

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

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

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

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

MasonryLite.vue

  1. <template> 
  2.   <div class="masonry-lite"
  3.     <slot></slot> 
  4.   </div> 
  5. </template> 
  6. <script> 
  7. import { calcPositions } from './index.js'
  8. export default { 
  9.   name'MasonryLite'
  10.   props: { 
  11.     gap: { 
  12.       type: Number, 
  13.       default: 12, 
  14.     }, 
  15.     columns: { 
  16.       type: Number, 
  17.       default: 2, 
  18.     }, 
  19.   }, 
  20.   data() { 
  21.     return {}; 
  22.   }, 
  23.   mounted() { 
  24.     this.doLayout(); 
  25.   }, 
  26.   methods: { 
  27.     doLayout() { 
  28.       const children = [...this.$el.querySelectorAll('.masonry-item')]; 
  29.       if (children.length === 0) { 
  30.         return
  31.       } 
  32.       const { positions, containerHeight } = calcPositions({ 
  33.         elements: children, 
  34.         columns: this.columns, 
  35.         gap: this.gap, 
  36.       }); 
  37.       children.forEach((item, index) => { 
  38.         item.style.cssText = `left:${positions[index].left}px;top:${positions[index].top}px;`; 
  39.       }); 
  40.       this.$el.style.height = `${containerHeight}px`; 
  41.     }, 
  42.   }, 
  43. }; 
  44. </script> 
  45. <style lang="scss" scoped> 
  46. .masonry-lite{ 
  47.   position: relative
  48. .masonry-item { 
  49.   position: absolute
  50. </style> 

使用组件:

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

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

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

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

npm 包:masonry-lite

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

本文转载自微信公众号「1024译站」,可以通过以下二维码关注。转载本文请联系1024译站公众号。

 

责任编辑:武晓燕 来源: 1024译站
相关推荐

2013-02-19 10:24:47

瀑布流布局CSS

2024-08-19 14:01:00

2012-05-02 13:53:00

JavaScript

2020-08-10 06:36:21

强化学习代码深度学习

2021-10-18 09:09:16

数据库

2021-06-22 09:55:05

代码图像技术

2021-04-15 11:10:40

GitHub代码开发者

2020-09-29 10:09:43

Python文字识别编程语言

2024-09-03 17:04:15

前端算法布局

2021-01-29 10:57:57

新基建政策解读智慧物流

2020-07-07 07:55:53

web app数据科学机器学习

2023-11-01 10:36:19

2024-11-12 13:41:49

2020-10-24 20:10:40

Python 开发编程语言

2021-02-23 07:01:24

js小游戏技术

2024-06-04 14:20:47

数字化转型数字化

2021-12-02 09:31:22

Python 代码进度条

2023-10-27 11:38:09

PythonWord

2023-12-24 22:52:26

PythonPPT代码

2024-01-10 14:45:46

Redis数据库存储
点赞
收藏

51CTO技术栈公众号