前言
上一文讲到了图片, 这里咱们就讲一个罕用的图片场景: 瀑布流, 他的实现和优化
什么瀑布流
瀑布流,又称瀑布流式布局。是比拟风行的一种网站页面布局,视觉体现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会一直加载数据块并附加至以后尾部。最早采纳此布局的网站是 Pinterest,逐步在国内风行开来。国内大多数清爽站根本为这类格调。
更直观的展现如下图所示:
优缺点
长处:
- 表面好看,更有艺术性。
- 用户浏览时的参观和思维不容易被打断,留存更容易。
毛病:
- 用户无奈理解内容总长度,对内容没有宏观掌控。
- 用户无奈理解当初所处的具体位置,不晓得离起点还有多远。
- 回溯时不容易定位到之前看到的内容。
- 容易造成页面加载的负荷。
- 容易造成用户浏览的疲劳,没有短暂的休息时间。
实现计划
纯 CSS 实现
这里要介绍 CSS 属性: column
当初罕用的 column
属性有这些
- column-count
将一个元素的内容分成指定数量的列。涉疫设置为具体数字, 或者auto
- column-fill
管制元素的内容在分成多列时如何均衡。有auto
、balance
两个值, 别离示意主动填充和程度调配 - column-gap
两列间的间隔, 可设置: px, 百分比, rem - column-rule
列布局中, 分割线的设置, 能够设置格调、宽度、色彩(和 border 雷同的参数) - column-span
列布局中横向的占用元素, 具体作用可查看此处 - column-width
每一列的最小宽度, 如果内部容易宽度特地小, 则会生效
另一个 CSS 属性 columns
他是用来设置元素的列宽和列数属性, 是由 column-width
和 column-count
两属性合并而来的简写属性
对于兼容性, 目前来看兼容性还能够, 可适应大多数状况
相干 CSS 属性的详情, 可点击此处
这里再提供一个我应用 columns
的 demo, 预览图:
在线查看地址: https://grewer.github.io/JsDe...
(_可通过缩放浏览器大小来查看他的成果_)
js 实现
通过 js 咱们也能够实现瀑布流, 外围思路是:
- 咱们固定每张图片的宽度, 如 200px, 这样咱们就能计算出以后屏幕中一行是 N 张图片
- 通过步骤 1 中计算出来的数量, 咱们创立高度数组, 用来寄存每一列的高度
- 遍历图片, 找到步骤 2 中高度数组的最小值(默认为 0), 这样咱们在对应序号插入图片, 同时高度数组中, 也更新他的高度
具体实现
第 1,2 步中的初始化和高度数组
const width = 200; // 默认设置为 200px 的宽度 const columns = Math.floor(window.innerWidth / 200) // 计算出以后页面的列 const columnsHeightArr = [] // mock 图片地址 const urls = new Array(10).fill(0).map((it, index) => { return `https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_${index}.png` })
在获取图片高度时, 咱们须要留神的是, 图片未加载实现时, 是无奈获取到图片高度的
所以这里咱们筹备先加载图片, 在图片全副加载实现后, 再进行高度的计算
let flag = 0 const getImg = (url) => { let img = new Image(); img.src = url; const imgCallback = () => { flag++; if (flag === urls.length) { handler(); } } // 是否缓存 if (img.complete) { imgCallback() } else { img.onload = imgCallback } }
在图片全副加载结束之后, 进入 handler
函数, 正式操作图片:
// 增加图片到容器中, 通过 position: absolute 的计划来实现 const appendImages = (url, position, top) => { const img = document.createElement('img'); img.src = url; img.style.left = (position * width) + 'px'; img.style.top = top + 'px'; container.appendChild(img) return img } // 获取高度数组中的最小高度 const getMin = (arr) => { let minHeight = arr[0]; let index = 0 for (let i = 1; i < arr.length; i++) { if (arr[i] < minHeight) { minHeight = arr[i] index = i } } return {index, minHeight} } const insertImages = () => { for (let i = 0; i < urls.length; i++) { // 判断是否小于一行, 这样咱们就能间接退出了 if (columnsHeightArr.length < columns) { const img = appendImages(urls[i], i, 0) columnsHeightArr[i] = img.offsetHeight; } else { const {index, minHeight} = getMin(columnsHeightArr) const img = appendImages(urls[i], index, minHeight); columnsHeightArr[index] = columnsHeightArr[index] + img.offsetHeight; } } }
最初的实现成果:
因为图片是一口气加载完, 再 append 到页面中的, 所以页面不会显示有图片的加载流程
在线 demo : 点击查看
优化图片加载
在上一计划中, 咱们是先加载完所有图片, 再将图片放到容器中
这样做的毛病就是: 页面加载迟缓, 并且图片也没有加载流程, 页面呈现的有点突兀
假如:
如果咱们一开始不加载所有的图片,而是加载 N 张, 分批次加载, 这样会不会好很多呢?
尝试解决:
const insertImages = () => { if (!urls || urls.length <= 0) { console.log('done 全副加载结束') return } // 一行有 N 张图片的话, 咱们就以 N 作为一个批次加载的图片数量 const arr = urls.splice(0, columns) let flag = arr.length; arr.forEach(async (item, i) => { if (columnsHeightArr.length < columns) { const img = await loadImage(item); appendImages(img, i, 0) columnsHeightArr[i] = img.offsetHeight; } else { const img = await loadImage(item); const {index, minHeight} = getMin(columnsHeightArr) appendImages(img, index, minHeight); columnsHeightArr[index] = columnsHeightArr[index] + img.offsetHeight; } // 做出查看, 所有图片是否都曾经加载结束 flag--; if (flag <= 0) { // 调用本身, 直到图片全副加载结束 insertImages() } }) }
成果展现:
在线 demo : 点击查看
从目前的成果来看, 尽管加载看上去好一点点, 但还是不尽如人意
从根本原因上看, 是因为咱们要获取图片的长宽来计算布局, 所以要期待加载
那么咱们提出构想, 在获取到图片门路的同时, 能获取尺寸比例, 那么加载速度问题也就解决了
图片源头优化
假如咱们的初始数据是这样:
[ { "url": "https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_0.png", "width": 1776, "height": 1184 }, //...省略]
或者是这样:
[ "https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_0.png?w=1776&h=1184", // ... 省略]
如果是长宽比例也是没问题的:
[ "https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_0.png?scale=1.5", // scale = height/width // ... 省略]
这样的数据, 须要肯定的反对, 比方在上传时, 前端将图片的尺寸一起上传, 或者后端计算尺寸存入数据库等等
咱们加载这样的数据, 就只有关怀高度即可, 不必再放心图片是什么时候加载实现的
const insertImages = () => { data.forEach( (item, i) => { const img = document.createElement('img'); img.src = item.url; const height = ((item.height/item.width) * (200-gap)) + gap; if (columnsHeightArr.length < columns) { appendImages(img, i, 0, height) columnsHeightArr[i] = height; } else { const {index, minHeight} = getMin(columnsHeightArr) appendImages(img, index, minHeight, height); columnsHeightArr[index] = columnsHeightArr[index] + height; } }) }
成果展现:
在线 demo : 点击查看
从这里咱们解脱了图片的加载速度限制, 能够说是优化胜利了(当然这种计划是须要肯定反对)
长列表的优化
瀑布流和失常的列表一样, 也会存在一个列表过长时的渲染问题
之前我在一篇文章里介绍了长列表的优化(点击此处查看), 外面介绍了几种计划
虚构列表是一种很好的优化计划, 然而他和瀑布流布局并不好兼容, 因为虚构列表须要的是每一行的高度(即便高度不一样)
然而瀑布流是依照列来布局的, 没有残缺行的概念, 这就造成了不兼容的抵触
目前来看 CSS 属性 content-visibility
可反对瀑布流的优化, 然而具体成果还须要在业务中实战进去
总结
本文介绍了瀑布流的概念, 以及两种不同的实现计划, 并对 JS 计划作出了肯定的优化解决
CSS 计划比拟不便, 然而兼容性略差
JS 计划是一种普适性较强的计划, 针对图片的加载性能和布局来说, 须要对图片的比例作出提前的计算
这里留下本文所有 demo 的源码地址供大家参考: 点此查看
援用
- https://www.zhihu.com/questio...
- https://developer.mozilla.org...