我的需求:html
// 瀑布流布局 waterFall() { ... const columns = 3 // 列数 const gap = 10; // 间隔 const itemWidth = ~~((this.getClient().width / columns - gap)) ... }, // 获取页面宽度 getClient() { const SCROLL_WIDTH = 20 return { /* ** window.innerWidth - SCROLL_WIDTH: 页面宽度(包括滚动条)- 比滚动条多一点的宽度 ** 不使用document.body.clientWidth 和 document.documentElement.clientWidth缘由: ** 页面首次加载时的宽度没有被滚动条挤压的,这样会致使滚动加载时获取的页面宽度与首次的宽度不一致 ** 通常浏览器滚动条宽度为17px,减去20是保守估计并为右侧留必定空间 ** 移动端滚动条是悬浮在页面在上,不会形成挤压,所以在移动端中能够不减去滚动条宽度 */ width: window.innerWidth - SCROLL_WIDTH // width: window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth } }, // 对box进行布局 reflow(el, itemWidth, columns, gap) { el.style.width = itemWidth + 'px' ... }
// 瀑布流布局 waterFall() { const columns = 3 // 列数 const gap = 10; // 间隔 const itemWidth = ~~((this.getClient().width / columns - gap)) const box = document.getElementById("big-box") const items = box.children for (let i = this.loadCount; i < items.length; i++, this.loadCount++) { // 获取图片元素 const img = items[i].getElementsByTagName('img')[0] // 图片有缓存时直接布局(主要在窗口尺寸变化时调用) if(img.complete) { this.reflow(items[i], itemWidth, columns, gap) } // 图片无缓存时先对加载速度快的图片进行布局 else { img.onload = () => { this.reflow(items[i], itemWidth, columns, gap) } } } }, // 对box进行布局 reflow(el, itemWidth, columns, gap) { el.style.width = itemWidth + 'px' // 第一行 if (this.arr.length < columns) { el.style.top = 0; el.style.left = (itemWidth + gap) * this.arr.length + 'px' this.arr.push(el.offsetHeight) } // 其余行 else { // 最小的列高度 const minHeight = Math.min(...this.arr) // 当前高度最小的列下标 const index = this.arr.indexOf(minHeight) el.style.top = minHeight + gap + 'px' el.style.left = (itemWidth + gap) * index + 'px' this.arr[index] = this.arr[index] + el.offsetHeight + gap } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <title>JS 实现瀑布流(Vue)</title> <style> .container { position: relative; } .box { position: absolute; width: 0; /* 设置0是为了布局时才显示图片,防止看到图片都在第一张的位置上堆叠 */ } .box img { width: 100%; } </style> </head> <body> <div id="app"> <div id="big-box" class="container"> <div class="box" v-for="(item, index) in pic_list" :key="index"> <img :src="item"> </div> </div> </div> <script> new Vue({ el: '#app', name: 'WaterFall', data() { return { isReSize: false, // 窗口尺寸是否发生变化 lock: true, // 锁 pic_list: [], arr: [], // 存放每一列的最小高度 loadCount: 0 // 已经布局好的元素下标 } }, methods: { // 瀑布流布局 waterFall() { const columns = 3 // 列数 const gap = 10; // 间隔 const itemWidth = ~~((this.getClient().width / columns - gap)) // console.log(this.getClient().width, itemWidth) const box = document.getElementById("big-box") const items = box.children // console.log(items) // 窗口尺寸发生变化时,所有box从新布局 if(this.isReSize) { this.loadCount = 0 this.arr = [] this.isReSize = false } for (let i = this.loadCount; i < items.length; i++, this.loadCount++) { // 获取图片元素 const img = items[i].getElementsByTagName('img')[0] // 图片有缓存时直接布局(主要在窗口尺寸变化时调用) if(img.complete) { this.reflow(items[i], itemWidth, columns, gap) } // 图片无缓存时先对加载速度快的图片进行布局 else { img.onload = () => { this.reflow(items[i], itemWidth, columns, gap) } } } console.log('-------------------------------') }, // 对box进行布局 reflow(el, itemWidth, columns, gap) { el.style.width = itemWidth + 'px' // 第一行 if (this.arr.length < columns) { el.style.top = 0; el.style.left = (itemWidth + gap) * this.arr.length + 'px' this.arr.push(el.offsetHeight) } // 其余行 else { // 最小的列高度 const minHeight = Math.min(...this.arr) // 当前高度最小的列下标 const index = this.arr.indexOf(minHeight) // console.log(index, minHeight) el.style.top = minHeight + gap + 'px' el.style.left = (itemWidth + gap) * index + 'px' this.arr[index] = this.arr[index] + el.offsetHeight + gap } // console.log(JSON.parse(JSON.stringify(this.arr))) }, // 获取页面宽度 getClient() { const SCROLL_WIDTH = 20 return { /* ** window.innerWidth - SCROLL_WIDTH: 页面宽度(包括滚动条)- 比滚动条多一点的宽度 ** 不使用document.body.clientWidth 和 document.documentElement.clientWidth缘由: ** 页面首次加载时的宽度没有被滚动条挤压的,这样会致使滚动加载时获取的页面宽度与首次的宽度不一致 ** 通常浏览器滚动条宽度为17px,减去20是保守估计并为右侧留必定空间 ** 移动端滚动条是悬浮在页面在上,不会形成挤压,所以在移动端中能够不减去滚动条宽度 */ width: window.innerWidth - SCROLL_WIDTH // width: window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth } }, // 延迟函数,防止短期内执行屡次 wait(func, time=300) { if(this.lock) { this.lock = false setTimeout(() => { func() this.lock = true }, time) } }, async getPic() { // const {data: res} = await axios.get('http://127.0.0.1:5000/pics') // 经过访问本地资源模拟获取图片路径 res = [ "/static/img/1.jpg", "/static/img/2.jpg", "/static/img/3.jpg", "/static/img/4.jpg", "/static/img/5.jpg", "/static/img/6.jpg", "/static/img/7.png", "/static/img/8.png", "/static/img/9.jpg", "/static/img/10.jpg", "/static/img/11.jpg", "/static/img/12.jpg", "/static/img/13.jpg", "/static/img/14.jpg", "/static/img/15.jpg", "/static/img/16.jpg", "/static/img/17.png", "/static/img/18.jpg", "/static/img/19.jpg", "/static/img/20.png", ] this.pic_list.push(...res) await this.$nextTick() this.waterFall() } }, mounted() { this.getPic() // 窗口尺寸变化事件 window.onresize = () => { this.isReSize = true this.wait(this.waterFall) } // 窗口滚动事件 window.onscroll = () => { this.wait(() => { // 是否滚动到底部 const IS_BOTTOM = document.documentElement.scrollHeight - document.documentElement.scrollTop <= document.documentElement.clientHeight if(IS_BOTTOM) { this.getPic() console.log('到底了') } }) } } }) </script> </body> </html>