前言:
本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小生感激不尽。
本篇文章为您分析一下原生JS实现图片瀑布流效果
页面需求
1. 图片之前拥有最小间隙
2. 图片可以根据浏览器窗口的改变而改变
3. 需要用到函数节流与函数防抖的知识
需要前置知识:
函数节流与防抖地址: https://www.cnblogs.com/tewutuyo/p/javascript_debounce_throttle.html
HTML结构
<div class="container"></div>
<script src="../../plugin/helpers.js"></script> <!--函数节流与防抖要引用,此插件请查看我的第一篇博客JS函数的节流与防抖 -->
<script src="../../plugin/waterfall.js"></script> <!--瀑布流代码-->
CSS 样式
无
JS 行为
JS大致思路
1. 根据用户传入的配置信息设置图片信息
2. 配置默认值
3. 设置页面的图片
4. 设置父级元素的定位信息
5. 设置每张图片的left、top值
<1>.计算一行有几张图片
定义一个数组,这个数组用来记录每一列下一张的top值
初始值为[0,0,0,n...]; 数组的每一项为0
<2>. 设置图片位置时
(1).得到数组中最小的值,设置top
(2).更新数组中该项的top值
(3).得到该项是数组中的第几项,用于计算该项的left值
5. 获得水平方向上的距离信息(一行排几张图、最小间隙)
6. 设置图片的left、top值
<script>
// 因为我本地有40张以img开头的所以我直接循环来获得。
var srcs = [];
for (var i =0;i <=40; i++ ){
srcs.push(`img/${i}.jpg`);
}
// 用户可以自行配置参数
myPlugin.createWaterFall({
minGap: 15, // 垂直方向的最小间隙
imgSrcs: srcs, // 图片的路径数组
imgWidth: 220, // 单张图片的宽度
container: document.querySelector(".container") // 需要渲染的容器
});
</script>
/**
* 第一步: 创建一个图片瀑布流
* @param {*} option 参数配置
*/
window.myPlugin.createWaterFall = function (option) {
// option默认值
var defaultOption = {
minGap: 10, // 图片最小间隙
imgSrcs: [], // 图片的路径数组
imgWidth: 220, // 单张图片的宽度
container: document.body // 需要渲染的容器,如果用户没有传,默认为body
}
// 第二步: 对象混合
var option = Object.assign({}, defaultOption, option);
// console.log(option);
var imgs = []; // 存放所有的图片dom对象。
}
createImg();
// setFatherPosition();
/**
* 第三步: 创建图片
*/
function createImg() {
for (var i = 0; i < option.imgSrc.length; i++) {
var img = document.createElement("img"); // 创建图片
img.src = option.imgSrc[i]; // 设置图片路径
img.style.width = option.imgWidth + "px";// 设置每张图片宽度
img.style.position = "absolute"; // 设置图片为绝对定位
imgs.push(img); // 添加到图片数组中
option.container.appendChild(img); // 添加图片到container中
}
}
如果我们把他设置为绝对定位他是这样的
接下来我们要设置父级的定位
/**
* 第四步: 处理父元素,因为图片都是绝对定位的,父元素必须是一个定位元素。
*/
function handleParent() {
var style = getComputedStyle(option.container); // 获取到父级的最终定位
if (style.position === "static") { // 如果父级没有定位 (为何要如此定位,因为万一父级有定位的情况下,我们要保证他不被影响)
option.container.style.position = "relative";// 设置父级为相对定位
}
}
接下来我们要设置每张图片的定位
那么,我们要知道一行内有多少张图?
还有他们之间的最小间隙
那么,我们要知道一行内有多少张图?
还有他们之间的最小间隙
/**
* 第五步: 得到图片水平方向上的信息。
*/
function getHorizontalInfo() {
// 5.1 定义一个对象用来存储数据
var obj = {};
// 5.2 容器的宽度
obj.containerWidth = option.container.clientWidth;
// 5.3 计算一行有多少个图片
obj.number = (obj.containerWidth + option.minGap) / (option.imgWidth + option.minGap);
// 5.4 向下取整, 每行的图片只能少不能多。多了他会放不下
obj.number = Math.floor(obj.number);
// 5.5 重新计算每一个水平空隙
// 总宽度-图片的数量*图片的宽度=剩余空间
obj.gap = (obj.containerWidth - obj.number * option.imgWidth) / (obj.number - 1);
console.log(obj.gap);
return obj; // 返回这个对象
}
得到了图片水平方向上的距离之后
我们就来设置他们的top和left值
写一个setImgPosition函数
我们就来设置他们的top和left值
写一个setImgPosition函数
/**
* 第六步: 设置每一张图片的坐标
*/
function setImgPosition() {
// 6.1 获取水平方向信息保存到info变量中
var info = getHorizontalInfo();
// console.log(info);
// 第七步: 存放每一列下一张图片的top值
// 7.1 创建数组
var arr = new Array(info.number);
// 7.2 填充数组的每一项为0
arr.fill(0);
// 7.3 获取所有的图片
imgs.forEach(function (img) {
// 设置图片的坐标
// 7.4 找到数组里面的最小值
var minTop = Math.min.apply(null, arr);
// 7.5 图片的纵坐标
img.style.top = minTop + "px";
// 7.6 更新数组的top值(最小值拿的是数组的哪一项,找到对应的列编号。再更新)
var index = arr.indexOf(minTop);
// 设置数组当前这一项
// 图片当前的高度 + 垂直方向间隙
arr[index] += img.clientHeight + info.gap;
// 横坐标
img.style.left = index * (option.imgWidth + info.gap) + "px";
});
}
运行函数setImgPosition
得到效果如下
得到效果如下
为什么会这样呢?因为图片是异步加载的,加载完才有图片的高度
所以应该是每一张图片加载完就给他重新设置高度
因此setImgPosition函数不能在外面调用,他应该在图片的事件中调用
所以应该是每一张图片加载完就给他重新设置高度
因此setImgPosition函数不能在外面调用,他应该在图片的事件中调用
img.onload = function () {
setImgPosition();
};
为了防止有时候有好几张图片几乎是同一时间加载完成的
调用setImgPosition函数调用的就太频繁了
因此我们要使用函数防抖来限制他的频繁调用
在createImg函数中引用下面代码:
调用setImgPosition函数调用的就太频繁了
因此我们要使用函数防抖来限制他的频繁调用
在createImg函数中引用下面代码:
最后一步就是浏览器窗口改变页面要发生重排
因此给container添加一个监听事件
因此给container添加一个监听事件
// 窗口尺寸变化事件
var debounce = myPlugin.debounce(setImgPosition, 300, false);
window.onresize = function () {
debounce();
}
下面附上完整代码
JS 行为
if (!window.myPlugin) {
window.myPlugin = {};
}
/**
* 第一步: 创建一个图片瀑布流
* @param {*} option 参数配置
*/
window.myPlugin.createWaterFall = function (option) {
// option默认值
var defaultOption = {
minGap: 10, // 图片最小间隙
imgSrcs: [], // 图片的路径数组
imgWidth: 220, // 单张图片的宽度
container: document.body // 需要渲染的容器
}
// 第二步: 对象混合
var option = Object.assign({}, defaultOption, option);
// console.log(option);
var imgs = []; // 存放所有的图片dom对象
// 处理父元素
handleParent();
// 3.1 创建图片
createImgs();
// 窗口尺寸变化事件
var debounce = myPlugin.debounce(setImgPosition, 300, false);
window.onresize = function () {
debounce();
}
/**
* 第五步: 设置每一张图片的坐标
*/
function setImgPosition() {
// 第七步: 获取水平方向信息
var info = getHorizontalInfo();
// console.log(info);
// 7.1 存放每一列下一张图片的top值
var arr = new Array(info.number);
// 7.2
arr.fill(0);
// console.log(arr);
// 7.3 获取所有的图片
imgs.forEach(function (img) {
// 设置图片的坐标
// 7.4 找到数组里面的最小值
var minTop = Math.min.apply(null, arr);
// 7.5 图片的坐标
img.style.top = minTop + "px";
// 7.6 更新数组的top值(最小值拿的是数组的哪一项,找到对应的列编号。再更新)
var index = arr.indexOf(minTop);
// 设置数组当前这一项
// 图片当前的高度 + 垂直方向间隙
arr[index] += img.clientHeight + info.gap;
// 横坐标
img.style.left = index * (option.imgWidth + info.gap) + "px";
});
// 设置容器的高度
var maxTop = Math.max.apply(null, arr);
option.container.style.height = maxTop - info.gap + "px";
}
/**
* 第六步: 得到图片水平方向上的信息。
*/
function getHorizontalInfo() {
// 6.1
var obj = {};
// 6.2 容器的宽度
obj.containerWidth = option.container.clientWidth;
// 6.3 计算一行有多少个图片
obj.number = (obj.containerWidth + option.minGap) / (option.imgWidth + option.minGap);
// 6.4 向下取整, 每行的图片只能少不能多。
obj.number = Math.floor(obj.number);
// 6.5 重新计算每一个水平空隙
// 总宽度-图片的数量*图片的宽度=剩余空间
obj.gap = (obj.containerWidth - obj.number * option.imgWidth) / (obj.number - 1);
console.log(obj.gap);
return obj;
}
/**
* 第三步: 创建图片
*/
function createImgs() {
// 函数节流
var debounce = myPlugin.debounce(setImgPosition, 50, false);
// 循环图片路径数组
for (var i = 0; i < option.imgSrcs.length; i++) {
var img = document.createElement("img");
img.src = option.imgSrcs[i];
img.style.width = option.imgWidth + "px";
img.style.position = "absolute";
img.style.transition = ".5s";
imgs.push(img);
img.onload = function () {
// 设置图片元素的坐标
debounce();
}
option.container.appendChild(img);
}
}
/**
* 第四步: 处理父元素,因为图片都是绝对定位的,父元素必须是一个定位元素。
*/
function handleParent() {
// 如果父元素不是定位元素,则将其变为相对定位元素
var style = getComputedStyle(option.container);
// console.log(style.position);
if (style.position === "static") {
option.container.style.position = "relative";
}
}
}
结语
整完!!!