這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一.需求來源 今天碰到了一個需求,需要在頁面里,用水平瀑布流的方式,將一些圖片進行載入,這讓我突然想起我很久以前寫的一篇文章《JS兩種方式實現水平瀑布流佈局》 但是有個問題,這個需求是Vue項目的,那沒辦法,這裡給大家分享下我的開發過程, ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
一.需求來源
今天碰到了一個需求,需要在頁面里,用水平瀑布流的方式,將一些圖片進行載入,這讓我突然想起我很久以前寫的一篇文章《JS兩種方式實現水平瀑布流佈局》
但是有個問題,這個需求是Vue項目的,那沒辦法,這裡給大家分享下我的開發過程,項目主體用的是之前在學習的CRMEB的後端框架來開發,UI使用iView-UI,其餘的場景與其他的vue項目一致
二.邏輯設想
如果不是vue環境,我們的邏輯為
1.獲取所有的div元素 2.獲取盒子的寬度,寬度都是相同,高度不同 3.在浮動佈局中每一行的盒子個數不固定,是根據屏幕寬度和盒子寬度決定 4.獲取屏幕寬度 5.求出列數,屏幕寬度 / 盒子寬度 取整 6.瀑布流最關鍵的是第二行的盒子的排布方式,通過獲取第一行盒子中最矮的一個的下標,絕對定位,top是最矮盒子的高度,left是最矮盒子的下標 * 盒子的寬度 7.迴圈遍歷所有的盒子,通過列數找到第一行所有的盒子,將第一行盒子的高度放入數組,再取出數組中最小的一個的下標,就是第6步思路的第一行盒子中最矮盒子的下標。 8.迴圈繼續,第二行第一個盒子,通過絕對定位,放進頁面。 9.關鍵,需要將數組中最小的值加上放進的盒子的高度 10.繼續迴圈,遍歷所有 11.如果想要載入更多,需要判斷最後一個盒子的高度和頁面滾動的距離,再將數據通過創建元素,追加進頁面,再通過瀑布流佈局展示
但如果是Vue項目,我們可以把邏輯歸結為以下幾步
1.獲取屏幕寬度 2..獲取盒子的寬度,寬度都是相同,高度不同 3.在浮動佈局中每一行的盒子個數不固定,是根據屏幕寬度和盒子寬度決定 4.求出列數,屏幕寬度 / 盒子寬度 取整 5.瀑布流最關鍵的是第二行的盒子的排布方式,通過獲取第一行盒子中最矮的一個的下標,絕對定位,top是最矮盒子的高度,left是最矮盒子的下標 * 盒子的寬度 6.繼續迴圈,遍歷所有 7.如果想要載入更多,需要判斷最後一個盒子的高度和頁面滾動的距離,再將數據通過創建元素,追加進頁面,再通過瀑布流佈局展示
三.最終效果圖片
四.代碼分析
先看下我的html部分
<template> <div class="tab-container" id="tabContainer"> <div class="tab-item" v-for="(item, index) in pbList" :key="index"> <img :src="item.url" /> </div> </div> </template> <style scoped> * { margin: 0; padding: 0; } /* 最外層大盒子 */ .tab-container { padding-top: 20px; position: relative; } /* 每個小盒子 */ .tab-container .tab-item { position: absolute; height: auto; border: 1px solid #ccc; box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); background: white; /* 元素不能中斷顯示 */ break-inside: avoid; text-align: center; } .tab-container .tab-item img { width: 100%; height: auto; display: block; } </style>
核心js部分
<script> export default { name:'compList', props:{ pbList:{ type:Array, default:()=>{return []} } }, data() { return { }; }, mounted() { this.$nextTick(()=>{ this.waterFall("#tabContainer", ".tab-item"); //實現瀑布流 }) }, methods: { waterFall( wrapIdName, contentIdName, columns = 5, columnGap = 20, rowGap = 20 ) { // 獲得內容可用寬度(去除滾動條寬度) const wrapContentWidth = document.querySelector(wrapIdName).offsetWidth; // 間隔空白區域 const whiteArea = (columns - 1) * columnGap; // 得到每列寬度(也即每項內容寬度) const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns); // 得到內容項集合 const contentList = document.querySelectorAll(contentIdName); // 成行內容項高度集合 const lineConentHeightList = []; for (let i = 0; i < contentList.length; i++) { // 動態設置內容項寬度 contentList[i].style.width = contentWidth + "px"; // 獲取內容項高度 const height = contentList[i].clientHeight; if (i < columns) { // 第一行按序佈局 contentList[i].style.top = "0px"; contentList[i].style.left = contentWidth * i + columnGap * i + "px"; // 將行高push到數組 lineConentHeightList.push(height); } else { // 其他行 // 獲取數組最小的高度 和 對應索引 let minHeight = Math.min(...lineConentHeightList); let index = lineConentHeightList.findIndex( (listH) => listH === minHeight ); contentList[i].style.top = minHeight + rowGap +"px"; contentList[i].style.left = (contentWidth + columnGap) * index + "px"; // 修改最小列的高度 最小列的高度 = 當前自己的高度 + 拼接過來的高度 + 行間距 lineConentHeightList[index] += height + rowGap; } } }, }, }; </script>
這裡要給大家提個醒,在當插件使用的時候,我們需要用this.$nextTick()來進行頁面初始化,因為方法成功的前提是要等頁面初始化載入完畢後才能進行獲取和計算
總體插件代碼為:
<template> <div class="tab-container" id="tabContainer"> <div class="tab-item" v-for="(item, index) in pbList" :key="index"> <img :src="item.url" /> </div> </div> </template> <script> export default { name:'compList', props:{ pbList:{ type:Array, default:()=>{return []} } }, data() { return { }; }, mounted() { this.$nextTick(()=>{ this.waterFall("#tabContainer", ".tab-item"); //實現瀑布流 }) }, methods: { waterFall( wrapIdName, contentIdName, columns = 5, columnGap = 20, rowGap = 20 ) { // 獲得內容可用寬度(去除滾動條寬度) const wrapContentWidth = document.querySelector(wrapIdName).offsetWidth; // 間隔空白區域 const whiteArea = (columns - 1) * columnGap; // 得到每列寬度(也即每項內容寬度) const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns); // 得到內容項集合 const contentList = document.querySelectorAll(contentIdName); // 成行內容項高度集合 const lineConentHeightList = []; for (let i = 0; i < contentList.length; i++) { // 動態設置內容項寬度 contentList[i].style.width = contentWidth + "px"; // 獲取內容項高度 const height = contentList[i].clientHeight; if (i < columns) { // 第一行按序佈局 contentList[i].style.top = "0px"; contentList[i].style.left = contentWidth * i + columnGap * i + "px"; // 將行高push到數組 lineConentHeightList.push(height); } else { // 其他行 // 獲取數組最小的高度 和 對應索引 let minHeight = Math.min(...lineConentHeightList); let index = lineConentHeightList.findIndex( (listH) => listH === minHeight ); contentList[i].style.top = minHeight + rowGap +"px"; contentList[i].style.left = (contentWidth + columnGap) * index + "px"; // 修改最小列的高度 最小列的高度 = 當前自己的高度 + 拼接過來的高度 + 行間距 lineConentHeightList[index] += height + rowGap; } } }, }, }; </script> <style scoped> * { margin: 0; padding: 0; } /* 最外層大盒子 */ .tab-container { padding-top: 20px; position: relative; } /* 每個小盒子 */ .tab-container .tab-item { position: absolute; height: auto; border: 1px solid #ccc; box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04); background: white; /* 元素不能中斷顯示 */ break-inside: avoid; text-align: center; } .tab-container .tab-item img { width: 100%; height: auto; display: block; } </style>
五.外層使用和懶載入
在使用這個插件的時候,有兩個問題,就是因為內層是position: absolute;定位,不會撐開外部的div,會導致外層盒模型不好佈局,還有就是頁面下拉懶載入,那要怎麼辦呢?
這裡我給出我的處理方法
整體代碼如下:
<template> <div> <div class="list-box" @scroll="scrollFun"> <compList :pbList="pbList" ref="compList"></compList> </div> </div> </template> <script> import compList from "@/pages/test/components/compList"; export default { name:'testList', components:{ compList }, data() { return { //瀑布流數據 pbList: [ { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", } ], addList:[ { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", }, { url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641", } ], bottomMain:true }; }, methods:{ scrollFun(e) { const offsetHeight= e.target.offsetHeight const scrollHeight= e.target.scrollHeight const scrollTop= e.target.scrollTop if((scrollHeight - (offsetHeight+scrollTop)) < 10){ if(this.bottomMain){ this.bottomMain = false this.addListDataFun() } } }, addListDataFun(){ this.$Spin.show({ render: (h) => { return h('div', [ h('Icon', { 'class': 'demo-spin-icon-load', props: { type: 'ios-loading', size: 18 } }), h('div', '數據更新中...') ]) } }); setTimeout(() => { this.pbList = this.pbList.concat(this.addList) this.bottomMain = true this.$nextTick(()=>{ this.$refs.compList.waterFall("#tabContainer", ".tab-item") this.$Spin.hide() }) },1000) } } }; </script> <style scoped> .list-box{ position: relative; width: 100%; height: calc(100vh - 240px); background: white; padding: 20px 30px 20px 20px; margin-top: 20px; box-sizing: border-box; overflow: auto; } </style>
下拉的核心代碼為:
scrollFun(e) { const offsetHeight= e.target.offsetHeight const scrollHeight= e.target.scrollHeight const scrollTop= e.target.scrollTop if((scrollHeight - (offsetHeight+scrollTop)) < 10){ if(this.bottomMain){ this.bottomMain = false this.addListDataFun() } } }, addListDataFun(){ this.$Spin.show({ render: (h) => { return h('div', [ h('Icon', { 'class': 'demo-spin-icon-load', props: { type: 'ios-loading', size: 18 } }), h('div', '數據更新中...') ]) } }); setTimeout(() => { this.pbList = this.pbList.concat(this.addList) this.bottomMain = true this.$nextTick(()=>{ this.$refs.compList.waterFall("#tabContainer", ".tab-item") this.$Spin.hide() }) },1000) }