我为什么要手写瀑布流布局,是因为行将要作念一个h5商城,首页商品是瀑布流布局的,还从来没写过呢,毕竟用的简直是太少了。固然有许多现成的,但行为一个有追求的风景员91porn,com,如故要在时代限度内尽量搞懂其收场旨趣。
什么是瀑布流布局❝瀑布流,又称瀑布流式布局。是相比流行的一种网站页面布局,视觉进展为散乱不都的多栏布局,跟着页面升沉条向下升沉,这种布局还会不绝加载数据块并附加至现时尾部。
❞我作念了个动画来确认这少量:
图片
这里为了看的明晰,咱们假定每个元素是按次渲染的,不错看到后续的每个元素都在找高度最小的那一列去摆设。这种布局十分适用于各个子元素高度不均匀的情况。
整期望路参考了国内两大着名网站抖音pc版和小红书pc版,发现一个共同点,他们都是无论在多大的屏幕尺寸下,「扫数元素的宽度是一样的」,即使拖动更动了屏幕大小,元素宽度动态调治了,每个元素跟其他兄弟元素的宽度亦然一致的,「只消高度不一致」,而这等于程序的瀑布流布局。
图片
抖音图片
小红书那么,我就不错暂时先把每个元素的宽度定死,先不有计划更动屏幕宽度的情况。
<!DOCTYPE html><html lang='en'><head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>瀑布流布局demo</title> <link rel='stylesheet' href='styles.css'></head><body> <div id='container'> <!-- 履行块 --> <div class='item'>1</div> <div class='item'>2</div> <div class='item'>3</div> <div class='item'>4</div> <div class='item'>5</div> <div class='item'>6</div> <div class='item'>7</div> <div class='item'>8</div> <div class='item'>9</div> <div class='item'>10</div> <div class='item'>11</div> <div class='item'>12</div> <div class='item'>13</div> <div class='item'>14</div> <div class='item'>15</div> <div class='item'>16</div> <div class='item'>17</div> <div class='item'>18</div> <div class='item'>19</div> <div class='item'>20</div> </div></body></html>系数20个div,目下还莫得履行,无法撑开高度,咱们就假定扫数奇数的高100px,偶数的高150px,每一个的宽度都是200px,然后先用flex布局(粗略无谓flex布局,把item设为inline-block),望望会是什么后果。
丝袜电影/* styles.css */#container { display: flex; flex-wrap: wrap;}.item { flex-shrink: 0; width: 200px; margin-bottom: 10px; background: #ccc; border: 1px solid #999; box-sizing: border-box; text-align: center;}/* 立时高度,用于演示 */.item:nth-child(odd) { height: 100px;}.item:nth-child(even) { height: 150px;}
图片
flex和inline-block后果差未几,这可不是咱们想要的。那何如才能让每个元素去找到它该去的场地呢?这时就要用到「css定位」了。
光有定位还不够,还得用「js计较」现存元素的高度,才知说念每个元素到底该去哪。
要领:91porn,com
父元素相对定位,每个子元素十足定位。js凭证屏幕宽度和每个元素的宽度,计较最多清楚若干列。拿到扫数dom元素,遍历并及时计较每一列中现存元素的高度,找到高度最小的那一列,把现时遍历到的这个元素十足定位到那一列,因为每个元素宽度是一样的,十足定位的left值等于列数*元素宽度,top值等于这个最小高度。到这里想路就理清了。
代码收场先改一下css,把flex去掉,改用定位,其他代码不变。
/* styles.css */#container { /* display: flex; flex-wrap: wrap; */ position: relative;}.item { /* flex-shrink: 0; */ position: absolute;}目下是这么的了,毕竟扫数元素都十足定位了,没建树left和top值,就会都在页面左上角。
图片
别急,底下来用js建树每个元素的left和top。咱们不雅察抖音和小红书的布局,就不错看出,它们是一列列的摆设的,垂直方进取是凹凸对都的(毕竟每个子元素宽度是一样的),水普通向是散乱不都的(因为不同子元素高度不一样),是以咱们不错把这些子元素漫衍成几列。
Q: 那到底是几列呢?
A: 得到父容器宽度,除以元素宽度和罅隙的和,向下取整,等于最多不错清楚的列数。
const container = document.getElementById('container')const containerWidth = container.offsetWidth// 列数const columnCount = Math.floor(containerWidth / (200 + 10));
有列数还不够,瀑布流的中枢是「散乱不都纵横交错,每个元素找最矮的那一列」去清楚。是以还要知说念每一列的总高度。开动化每列高度的数组,用来保存每列的总高度,开动值为0。
let columnHeights = Array(columnCount).fill(0);然后遍历子元素,每次找到高度最矮的那一列,这里就要用到高度数组了,现时元素十足定位的left值等于下标乘以宽度加隔断的和,top值等于最矮的那一列的总高度。然后更新那一列的高度,等于加上元素自己高度和隔断的高度。
荟萃上头的代码,整理完善一下:
// script.jswindow.onload = function () { // 得到父元素dom const container = document.getElementById('container'); // 列宽,也等于每个子元素 const columnWidth = 200; // 隔断宽度 const columnGap = 10; // 父元素自己宽度 const containerWidth = container.offsetWidth; // 计较最多清楚若干列 const columnCount = Math.floor(containerWidth / (columnWidth + columnGap)); // 开动化每列高度的数组 let columnHeights = Array(columnCount).fill(0); const items = Array.from(container.getElementsByClassName('item')) items.forEach(item => { // 找到高度最矮的那一列的下标 const minColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); // 现时元素的top值等于最矮的那一列的高度 const top = columnHeights[minColumnIndex]; // 现时元素的left值等于列宽乘以最矮的那一列的下标 const left = minColumnIndex * (columnWidth + columnGap); item.style.top = `${top}px`; item.style.left = `${left}px`; // 及时更新高度(自己高度+隔断) columnHeights[minColumnIndex] += item.offsetHeight + columnGap; });};
此时后果如下:
图片
基本上如故收场了。加载更多一般这种网站数据都许多,都是分页加载的,咱们不错监听页面升沉事件,当页面触底时,动态加载更多量据。
window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { // 模拟加载更多量据,动态创建出10个item元素 const newItems = []; for (let i = 0; i < 10; i++) { const newItem = document.createElement('div'); newItem.className = 'item'; newItem.style.height = `${Math.floor(Math.random() * 100) + 100}px`; container.appendChild(newItem); newItems.push(newItem); } // 依然跟上头一样遍历 newItems.forEach(item => { // 找到高度最矮的那一列的下标 const minColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); // 现时元素的top值等于最矮的那一列的高度 const top = columnHeights[minColumnIndex]; // 现时元素的left值等于列宽乘以最矮的那一列的下标 const left = minColumnIndex * (columnWidth + columnGap); item.style.top = `${top}px`; item.style.left = `${left}px`; // 及时更新高度(自己高度+隔断) columnHeights[minColumnIndex] += item.offsetHeight + columnGap; }); }});运行了一遍才发现,只消一动弹,还没触底呢就抑止的加载更多,尝试加防抖也不成,那一定是判断触底的代码有问题,调试了一下才发现,原本是document.body.offsetHeight一直是0,难怪呢会一直触发加载更多呢,导致这里又徜徉了时辰,不知说念是什么原因。
图片
其后检查元素才想起来,原本是咱们的子元素一都都是十足定位的,导致父元素高度塌陷了,是以body的高度也一直是0。我还一直在看js的问题,哎!
知说念了原因,惩处风景就肤浅了,在每次遍历完成item数组,更新高度数组的值之后,给父元素container建树一个高度,这个高度等于最高的那一列的高度:
container.style.height = `${Math.max(...columnHeights)}px`;
很彰着目下这个代码不够优雅,相易代码过多。
咱们把中枢的那段共同代码封装成一个函数,接纳要遍历的那些dom元素的数组行为参数。
// 布局function layoutItem(items) { items.forEach(item => { // 找到高度最矮的那一列的下标 const minColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); // 现时元素的top值等于最矮的那一列的高度 const top = columnHeights[minColumnIndex]; // 现时元素的left值等于列宽乘以最矮的那一列的下标 const left = minColumnIndex * (columnWidth + columnGap); item.style.top = `${top}px`; item.style.left = `${left}px`; // 及时更新高度(自己高度+隔断) columnHeights[minColumnIndex] += item.offsetHeight + columnGap; });}然后在最脱手的时候,和页面触底的时候诀别调用这个layoutItems函数。
const initItems = Array.from(container.getElementsByClassName('item'));layoutItems(initItems);window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { const newItems = []; for (let i = 0; i < 10; i++) { const newItem = document.createElement('div'); newItem.className = 'item'; newItem.textContent = `New ${Math.floor(Math.random() * 100)}`; newItem.style.height = `${Math.floor(Math.random() * 100) + 100}px`; container.appendChild(newItem); newItems.push(newItem); } layoutItems(newItems); } container.style.height = `${Math.max(...columnHeights)}px`;});
其实建树container高度的这句代码应该放到layoutItems函数内部去。
望望后果
图片
转头好了,原生js手写瀑布流布局到这里就完成了。咱们这里加载更多用的是监听scroll事件,其实还不错荟萃之前的一篇著述,用IntersectionObserver这个api,在页面履行底部放一个loading元素,不雅察这个loading元素与页面的交叉情况。
若是用vue来收场,亦然相通的想路,亦然凭证容器宽度和子元素宽度计较列数,不外不错无谓操作dom了,只用处理数据,把所少见据分红一个二维数组,有几列就有几项,然后处理每一条新数据往哪个子数组里塞。
终末贴一下整理之后的一都js代码就截止了:91porn,com
// script.jswindow.onload = function () { // 得到父元素dom const container = document.getElementById('container'); // 列宽 const columnWidth = 200; // 隔断宽度 const columnGap = 10; // 父元素自己宽度 const containerWidth = container.offsetWidth; // 计较最多清楚若干列 const columnCount = Math.floor(containerWidth / (columnWidth + columnGap)); // 开动化每列高度的数组 let columnHeights = Array(columnCount).fill(0); function layoutItems(items) { items.forEach(item => { // 找到高度最矮的那一列的下标 const minColumnIndex = columnHeights.indexOf(Math.min(...columnHeights)); // 现时元素的top值等于最矮的那一列的高度 const top = columnHeights[minColumnIndex]; // 现时元素的left值等于列宽乘以最矮的那一列的下标 const left = minColumnIndex * (columnWidth + columnGap); item.style.top = `${top}px`; item.style.left = `${left}px`; // 及时更新高度(自己高度+隔断) columnHeights[minColumnIndex] += item.offsetHeight + columnGap; }); // 这里不给高度的话,会一直触发loadMore container.style.height = `${Math.max(...columnHeights)}px`; } function loadMoreItems() { const newItems = []; for (let i = 0; i < 10; i++) { const newItem = document.createElement('div'); newItem.className = 'item'; newItem.style.height = `${Math.floor(Math.random() * 100) + 100}px`; container.appendChild(newItem); newItems.push(newItem); } layoutItems(newItems); } function handleScroll() { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { loadMoreItems(); } } window.addEventListener('scroll', handleScroll); // 开动布局 const initItems = Array.from(container.getElementsByClassName('item')); layoutItems(initItems);}; 本站仅提供存储管事,扫数履行均由用户发布,如发现存害或侵权履行,请点击举报。