瀑布流的实现方案及优缺点
A. column方案
column可以指定容器下元素列的宽度和数量
1 | columns: column-width column-count; |
但我们瀑布流用到的css属性是:
column-count:指定列数
column-gap:列之间的差距
实现
1 2 3 4 5 6 7 8 9 | <ul class= "img-wrapper" > <li><img src= "./image/1.jpg" alt= "" ></li> <!-- ... ... ... --> <li><img src= "./image/20.jpg" alt= "" ></li> </ul> |
我们在html中先放入二十张图片作为本次演示。
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | img-wrapper{ column-count: 4 ; column-gap: 10px ; counter-reset : count; width : 960px ; margin : 0 auto ; } .img-wrapper>li{ position : relative ; margin-bottom : 10px ; } |
这里我们用column-count设定为了4列,column-gap间距为10像素。就这么简单的两句我们就实现了一个瀑布流。
对了,为了我们更直观的观察排列规律,我们用伪类再做个计数器(后面的方案都会出现该伪类和html结构)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .img-wrapper>li>img{ width : 100% ; height : auto ; vertical-align : middle ; } .img-wrapper>li::after{ counter-increment : count; content : counter (count); width : 2em ; height : 2em ; background-color : rgba( 0 , 0 , 0 , 0.9 ); color : #ffffff ; line-height : 2em ; text-align : center ; position : absolute ; font-size : 1em ; z-index : 2 ; left : 0 ; top : 0 ; } |
效果
我们可以观察到计数下标就会知道column的排布规律是先从上往下排,然后从左到右排。
优点
- 实现简单,仅需两行核心代码。
- 图片自动填充不用考虑图片加载状态。
缺点
-
兼容性仍有些不尽人意。
-
排列规律永远都是先上下再左右,无法控制,动态加载会出现严重问题。
B. flex方案
用到的html结构与伪类计数与A方案相同,这里不再过多占用篇幅。
这个方案用到了弹性盒子,你没听错,用弹性布局也可以实现一个瀑布流,虽然很多局限性,但是也可以,在一定需求场景内使用。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 | .img-wrapper{ display : flex; flex-wrap: wrap; flex- direction : column; height : 1300px ; } .img-wrapper>li{ position : relative ; width : calc( 100% / 4 ); padding : 5px ; box-sizing: border-box; } |
我们对父容器设置弹性盒后,因为瀑布流是多行的所以还要flex-wrap设置wrap,并且flex-direction还要设置为column。最关键的是一定要设置一个高度。当然在其子元素用百分比设定要显示几列。
就这样,flex也可以实现一个瀑布流了。
当然,我们如果想改变一定程度的序列优先级,可以改变css的order属性。
1 2 3 4 5 6 7 8 9 10 11 12 | .img-wrapper>li:nth-child( 4 n+ 1 ){ order: 1 ; } .img-wrapper>li:nth-child( 4 n+ 2 ){ order: 2 ; } .img-wrapper>li:nth-child( 4 n+ 3 ){ order: 3 ; } .img-wrapper>li:nth-child( 4 n){ order: 4 ; } |
效果
可以看到flex瀑布流也实现了,而order可以一定程度上改变其顺序。不是像column必须按照先上下再左右去排列了。
优点
- 实现相对简单。
- 图片自动填充不用考虑图片加载状态。
- 顺序在一定程度上可以改变。
缺点
- 高度是固定的,很难做活。
- 顺序虽然可以改变,但是仍然不灵活,不尽人意。
C. js+absolute方案
js实现瀑布流的话,我们可以考虑把子元素全部设置成绝对定位。然后监听图片加载,如果加载完就把子元素设置其对应的位置,逐个塞到父容器中。
实现
1 2 3 4 5 6 | import Waterfall from "./js/Waterfall" window.onload = new Waterfall({ $el: document.querySelector( ".img-wrapper" ), count: 4 , gap: 10 }) |
我们先在配置阶段把父容器和列数,间距设置好。
然后再去写Waterfall类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | export default class Waterfall { constructor(options) { this .$el = null ; // 父容器 this .count = 4; // 列数 this .gap = 10; // 间距 Object.assign( this , options); this .width = 0; // 列的宽度 this .items = []; // 子元素集合 this .H = []; // 存储每列的高度方便计算 this .flag = null ; // 虚拟节点集合 this .init(); } init() { this .items = Array.from( this .$el.children); this .reset(); this .render(); } reset() { this .flag = document.createDocumentFragment(); this .width = this .$el.clientWidth / this .count; this .H = new Array( this .count).fill(0); this .$el.innerHTML = "" ; } render() { const { width, items,flag,H,gap } = this ; items.forEach(item => { item.style.width = width + "px" ; item.style.position = "absolute" ; let img = item.querySelector( "img" ); if (img.complete){ let tag = H.indexOf(Math.min(...H)); item.style.left = tag * (width + gap) + "px" ; item.style.top = H[tag] + "px" ; H[tag] += img.height*width/ img.width + gap; flag.appendChild(item); } else { img.addEventListener( "load" , () => { let tag = H.indexOf(Math.min(...H)); item.style.left = tag * (width + gap) + "px" ; item.style.top = H[tag] + "px" ; H[tag] += img.height*width/ img.width + gap; flag.appendChild(item); this .$el.append(flag); }) } }) this .$el.append(flag); } } |
我们这里就简单实现了一下:
- 初始化,计算出列宽来,将H作为列高存储器,4列那么就是[0,0,0,0]。然后收集子元素后,清除父容器内容。
- 遍历其子元素,设置其都为绝对定位,设置其列宽。后监听其下的图片加载是否完毕。
- 如果加载成功,那么计算应该在的位置,瀑布流的常规原则是哪一列数值最小就在那一列上设置新图片。当然他的相对高度和间距也要计算出来,同时在H当前列上要把高度存起来。
- 每次图片加载完就更新虚拟节点到父容器中。
效果
我们这里可以看到这个瀑布流就比较符合我们的期望了,可以看到他初始的顺序变化是从左到右的。
优点
- 控制灵活,随意扩展。
- 也可以无限加载,不用过多考虑兼容问题。
- 同时可以添加诸多动画来增强用户体验。
缺点
- 实现相对复杂。
- 图片填充需要考虑图片加载状态。
- 性能逊色于纯css实现。
思考
我们已经说完了这三种方案,其实绝大部分的商业级都是js实现的瀑布流,比如isotope等插件。本质实现起来并不麻烦,我们有时间可以自己封装一套,在vue上怎么用,在react上怎么用,怎么过滤,怎么通过用关键词去排序改变顺序等等,都可以考虑进去。算是比较常见功能所以自己试试看也没坏处,自己动手丰衣足食么~
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签