H5W3
当前位置:H5W3 > JavaScript > 正文

【JS】在Vue.js中使用JS 实现瀑布流

前言

  • 最近在学习瀑布流的实现方法,网上找了许多实现的方法,但发现与自己的需求不太符合,于是对代码进行了一些改动。
  • 个人需求:

    1. 横向加载
    2. 服务器返回的信息中没有图片尺寸
    3. 每加载完一张图片就设置它的位置,而不是等所有图片都显示完再布局
  • 性能肯定会比较慢,最好的方法还是在服务器返回图片尺寸大小或比例。
  • 为了方便操作,采用了在html中引用Vue.js的方法实现。

效果预览

  • 为了更直观的展示,这里设置了3G网络并禁用了缓存

rcHzlj.gif

  • 正常网速

r6suRg.gif

主要改动地方

  • 根据盒子宽度设置列数 => 根据列数设置box的宽度
    改动原因是根据列数设置宽度我觉得会更直观方便,而且右侧不会留下空白
// 瀑布流布局
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'
...
}
  • HTML文档加载完毕后再统一布局 => 加载完一张图片就布局一次
    (window.onload => image.onload)
// 瀑布流布局
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></script>
<script></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="https://segmentfault.com/a/1190000038626021/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>

存在的问题

  • 目前图片的尺寸获取和位置设置都是在image的onload方法中执行的,效率会比较慢
  • 窗口尺寸改变时会对所有的图片进行重新布局(可以考虑加个数组按图片加载顺序记录下标)
  • 图片未全部加载完就滚动到底部触发可能会导致数据缺失?(未验证)
    可以自行加个全部加载完才执行的判断。

参考文章

  • 原生js实现瀑布流效果
  • img的complete和onload

本文地址:H5W3 » 【JS】在Vue.js中使用JS 实现瀑布流

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址