图片瀑布流可以节省图片的排版空间,美观图片的排列,避免图片排列的参差不齐。
实现图片瀑布流可以固定宽(花瓣),也可以固定高(百度图片),看个人需求,我的需求是宽固定。
如果图片的排列不适用瀑布流的话,排版如下:
是不是特别的难以接受,的确,自己都无法接受,更别说展示给他人观看了。
如果使用瀑布流,排版如下:
那可真是天差地别啊,如此美观的排列,简直让人心情愉悦。
图片瀑布流代码:
HTML
<div class="waterFall-box" ref="box">
<div class="img-box" v-for="(item, index) in images" :key="index" ref="img">
<img :src="item.img" alt="">
</div>
<footer v-if="isLoad == false"
:style="{position: 'absolute', top: Math.max(...heightArray)+'px', color: 'red', left: '50%', transform: 'translateX(-50%)'}">
没有图片加载了...
</footer>
</div>
HTML代码可以自己定,看你的需求了,我这里遍历一个<div>,这个<div>装着<img>:
<div><img /></div>的格式
每个图片的格子套在一个大的容器里面,这样后面对每个格子进行absolute定位的时候,不至于看不见图片的现象。
VUE.JS
1.首先如果不了解vue的this.$nextTick()API的话,先去官网看一下这个api,nextTick
这个api让你避免dom找不到的问题。
2.首先请求json数据(方便后台),可以在create或者mounted里面请求json,如果是本地json,需要放在public文件夹下,不然请求不到:
created() {
Axios({
url: '/waterFall.json',
method: 'get'
}).then(res => {
if (res.data.code == 200) {
this.images = res.data.data ? res.data.data : []
this.loadImgHeight()
}
})
}
3.加载完毕json,使用JS的Image()对象预加载图片,避免找不到dom,提前渲染图片:
/**
* 预加载图片资源
* */
loadImgHeight() {
let count = 0 //计数变量 判断是否预加载图片是否完成
this.images.forEach((item) => {
//使用image类预加载图片
let image = new Image()
image.src = item.img
image.onload = image.onerror = event => {
count++
if(count == this.images.length) {
this.$nextTick(() => {
this.init()
this.positionImg(0)
})
}
}
})
}
4.预加载完毕图片,可以先初始化数据,判断当前页面容器可以排列多少固定宽度的图片格子,这都是需要先计算出来的,ClientWidth就很不错了,首先得到当前容器的可视宽度,然后模板除固定宽度,四舍五入得到可排列的列格子,
/**
* @remarks 初始化
* 初始化容器的宽度,计算出容器可容纳多少固定宽度图片的列,
* 如果可排列固定宽度的图片宽度无法沾满容器的宽度,需要计算出空余的宽度,固定首图片的left
* */
init() {
//得到页面的宽度
const pageWidth_padding = this.$refs.box.clientWidth
//页面的padding像素
this.offsetP = this.$refs.box.style.paddingLeft.replace(/[^0-9]/ig, "")
//获得页面的真实宽度(除去padding、margin、border...)
const pageWidth = pageWidth_padding - (this.offsetP * 2)
//计算出当前页面可展示多少列图片
const column = Math.floor(pageWidth / this.imgWidth)
//偏移像素值
this.surplusW = pageWidth - (column * this.imgWidth)
//初始化存储高度数组
for (let i = 0; i < column; i++) {
this.heightArray.push(0)
}
}
我这里多计算了排列图片格子后,剩下多余的宽度,好是图片居中排布(如果图片刚刚布满容器,则可以省略)。
5.此时,就可以定位我们的图片了,宽度固定瀑布流的逻辑:
核心思路就这两个老哥了····
5.1.每次循环,找到最低高度(计算top偏移)
5.2.找到最低高度的索引(计算left偏移)
每次都图片的定位,只需要找到上一列最低高度的图片位置,把当前图片定位到上一列最低高度的下面
top:为上一列最低高度图片的高度,left:为上一列最低高度的存储在数组的索引图片的固定宽度*
/**
* @remark 定位图片
* @param:
* start: 循环开始位置,开始为0,如果滚动条滑到底部,则start为容器存在图片资源的数量即this.images.length
* ----------宽高都计算img的父容器的宽高
* */
positionImg(start) {
//获得img标签的父容器的DOM
let parentDom = this.$refs.img
for (let i = start; i < this.images.length; i++) {
//获得最小高度
const minHeight = Math.min(...this.heightArray)
//获得最小高度索引
const index = this.heightArray.indexOf(minHeight)
//获得当前图片的高度
const currHeight = parentDom[i].clientHeight
//定位
parentDom[i].style.transform = '50px'
parentDom[i].style.position = 'absolute'
parentDom[i].style.top = minHeight + 'px'
parentDom[i].style.left = this.imgWidth * index + + ((Math.floor((this.surplusW / 2)) + 30)) + 'px'
this.heightArray[index] += currHeight
}
//对父容器赋值当前heightArray数组的最大高度
this.$refs.box.style.height = Math.max(...this.heightArray) + 50 + 'px'
}
注意一点就是,因为position:absolute定位后,脱离了文档流,致使,容器没了高度(就不能自适应撑开高度,导致,定位排列后的图片无法查看,所以,定位完成,数组里面会存储一个最大高度,这个高度就是当前排列的图片的最大可是高度)
6.如果有需求的话,可以在mounted监听滚动条的滚动行为(window.addEventListener就非常好用了),如果滚动到底部,则继续加载图片。
完整代码:
<template>
<div class="waterFall-box" ref="box">
<div class="img-box" v-for="(item, index) in images" :key="index" ref="img">
<img :src="item.img" alt="">
</div>
<footer v-if="isLoad == false"
:style="{position: 'absolute', top: Math.max(...heightArray)+'px', color: 'red', left: '50%', transform: 'translateX(-50%)'}">
没有图片加载了...
</footer>
</div>
</template>
<script>
import Axios from 'axios'
export default {
name: "WaterFall",
data() {
return {
images: [], //存储图片资源
imgWidth: 220, //图片的宽度
heightArray: [], //存储高度数组,用于判断最小高度的图片位置
isLoad: true, //是否继续加载图片
surplusW: 0, //是否存在剩余宽度
offsetP: 0,
count: 0
}
},
methods: {
/**
* 预加载图片资源
* */
loadImgHeight() {
let count = 0 //计数变量 判断是否预加载图片是否完成
this.images.forEach((item) => {
//使用image类预加载图片
let image = new Image()
image.src = item.img
image.onload = image.onerror = event => {
count++
if(count == this.images.length) {
this.$nextTick(() => {
this.init()
this.positionImg(0)
})
}
}
})
},
/**
* @remarks 初始化
* 初始化容器的宽度,计算出容器可容纳多少固定宽度图片的列,
* 如果可排列固定宽度的图片宽度无法沾满容器的宽度,需要计算出空余的宽度,固定首图片的left
* */
init() {
//得到页面的宽度
const pageWidth_padding = this.$refs.box.clientWidth
//页面的padding像素
this.offsetP = this.$refs.box.style.paddingLeft.replace(/[^0-9]/ig, "")
//获得页面的真实宽度(除去padding、margin、border...)
const pageWidth = pageWidth_padding - (this.offsetP * 2)
//计算出当前页面可展示多少列图片
const column = Math.floor(pageWidth / this.imgWidth)
//偏移像素值
this.surplusW = pageWidth - (column * this.imgWidth)
//初始化存储高度数组
for (let i = 0; i < column; i++) {
this.heightArray.push(0)
}
},
/**
* @remark 定位图片
* @param:
* start: 循环开始位置,开始为0,如果滚动条滑到底部,则start为容器存在图片资源的数量即this.images.length
* ----------宽高都计算img的父容器的宽高
* */
positionImg(start) {
//获得img标签的父容器的DOM
let parentDom = this.$refs.img
for (let i = start; i < this.images.length; i++) {
//获得最小高度
const minHeight = Math.min(...this.heightArray)
//获得最小高度索引
const index = this.heightArray.indexOf(minHeight)
//获得当前图片的高度
const currHeight = parentDom[i].clientHeight
//定位
parentDom[i].style.transform = '50px'
parentDom[i].style.position = 'absolute'
parentDom[i].style.top = minHeight + 'px'
parentDom[i].style.left = this.imgWidth * index + + ((Math.floor((this.surplusW / 2)) + 30)) + 'px'
this.heightArray[index] += currHeight
}
//对父容器赋值当前heightArray数组的最大高度
this.$refs.box.style.height = Math.max(...this.heightArray) + 50 + 'px'
}
},
mounted() {
const _this = this
//监听滚动条滚动,实现懒加载图片
window.addEventListener('scroll', function () {
//得到可滚动距离
const scrollDistance = document.documentElement.scrollHeight - document.documentElement.clientHeight
//滚动到顶部的距离
const scroll = document.documentElement.scrollTop
if (scrollDistance == scroll) {
Axios({
url: '/waterFall2.json',
method: 'get'
}).then(res => {
if (res.data.code == 200) {
_this.count += 1
if(_this.count == 4) {
_this.isLoad = false
}
if(_this.isLoad) {
const start = _this.images.length
for (let item of res.data.data) {
_this.images.push(item)
}
//滑到底部继续加载图片,this.$nextTick()异步加载,待资源虚拟DOM加载完毕
_this.$nextTick(() => {
_this.positionImg(start)
})
}
}
})
}
})
},
created() {
Axios({
url: '/waterFall.json',
method: 'get'
}).then(res => {
if (res.data.code == 200) {
this.images = res.data.data ? res.data.data : []
this.loadImgHeight()
}
})
}
}
</script>
<style scoped>
.waterFall-box {
position: relative;
text-align: center;
overflow-y: hidden;
}
.waterFall-box .img-box {
width: 210px;
vertical-align: top;
display: block;
float: left;
}
.waterFall-box .img-box img {
width: 100%;
animation: imgBox .5s ease-in-out;
}
.waterFall-box .img-box img:hover {
transform: translateY(-3px);
transition: transform .5s ease-in-out;
box-shadow: 0 20px 20px 2px #737373;
}
@keyframes imgBox {
0%{
opacity: 0;
transform: translateY(-100px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
</style>