瀑布流js实现
瀑布流布局是一种比较流行的网站页面布局,视觉表现为参差不齐的多栏布局,最早采用此布局的网站是Pinterest,逐渐在国内流行开来。
特点
-
瀑布流对用户来说有着很强的吸引力,瀑布流会在它的页面底部给用户不断地加载新的暗示信息,通过给出不完整的视觉图片去吸引你的好奇心,让你停不下来地想要向下不断地探索。
-
瀑布流的信息较为集中,可以在最小的操作成本下能够获得最多的内容体验,因此瀑布流能更好的适应移动端,由于移动设备屏幕比电脑小,一个屏幕显示的内容不会非常多,因此可能要经常翻页。而在建网站时使用瀑布流布局,用户则只需要进行滚动就能够不断浏览内容。
-
另外瀑布流的主要特质就是:定宽而不定高,这样的页面设计区别于传统的矩阵式图片布局模式,巧妙的利用视觉层级,视线的任意流动来缓解视觉的疲劳。
实现
html与css
首先在html文件中准备一个盒子,并添加id,以便后面通过id来操作dom
<!DOCTYPE html>
<html lang="zh">
<head>
<title>手写瀑布流</title>
<meta charset="UTF-8">
</head>
<body>
<div id="mainBorder"></div>
</body>
</html>
添加css样式,设置容器宽度,定位
*{
margin: 0;
padding: 0;
}
#mainBorder{
position: relative;
width: 100vw;
}
然后开始编写js代码
设置图片宽度,获取容器
const imgWidth = 200 //设置图片宽度
const mainDom = document.getElementById("mainBorder")
提前准备好图片,建议以数字从大到小排列,便于使用for循环插入图片
向容器中插入图片
// 添加图片
for (let i = 0; i < 20; i++) {
const imgUrl = `./images/${i}.jpg` //拼接图片路径
const img = document.createElement('img') // 创建节点
img.src = imgUrl
mainDom.appendChild(img) // 向容器中添加节点
}
在这里,通过for循环产生的数字i刚好就是我们准备好的图片名称,只需要使用模板字符串拼接成完整的图片路径,然后赋值给创建的img标签的src属性即可
在瀑布流布局中,我们需要针对每张图片进行绝对定位,可以提前把公共样式提取出来
.waterfallsFlow{
width: 200px;
position: absolute;
}
任意一个类名,设置绝对定位和宽度,并把这个类名添加给我们插入的图片
修改插入图片的代码
for (let i = 0; i < 20; i++) {
const imgUrl = `./images/${i}.jpg`
const img = document.createElement('img')
img.src = imgUrl
img.className = "waterfallsFlow" // 添加类名
mainDom.appendChild(img)
}
为创建的img元素添加类名,以绑定样式
接下来就是比较核心的两个方法
计算瀑布流相关数值
首先编写第一个函数,根据我们设置的图片宽度与屏幕当前尺寸,计算瀑布流的列数以及每一列的间距
// 计算瀑布流列数与外边距
function calc() {
const windowWidth = window.innerWidth // 获取屏幕宽度
const imgRow = parseInt(windowWidth / imgWidth)
const imgMargin = (windowWidth - imgRow * imgWidth) / (imgRow + 1)
return [imgRow, imgMargin]
}
在获取了屏幕宽度后,用屏幕宽度除以图片宽度并取整,即可知道在当前屏幕我们每行可以放多少列图片,此时添加的图片都是紧密相连,然后再将屏幕添加图片后剩余的宽度均匀分配,也就是每张图片的左右间距
为图片设置定位
function setPosition() {
// es6结构语法
const [imgRow/*瀑布流列数*/, imgMargin/*每张图片的外边距*/] = calc()
const imgs = mainDom.children
// let [rowNum/*当前所在列数*/, imgHeight/*当前图片高度*/] = [1, 0]
let rowHeightList = new Array(imgRow) // 创建一个用来存储每一列高度的数组
// 数组初始化
for (let i = 0; i < rowHeightList.length; i++) {
rowHeightList[i] = 0
}
for (let i = 0; i < imgs.length; i++) {
const minHeight = Math.min(...rowHeightList)
const rowNum = rowHeightList.indexOf(minHeight)
const imgHeight = imgs[i].offsetHeight
// 由于子元素设置了absolute,故父元素不会被撑开,需要给父元素设置高度
mainDom.offsetHeight = Math.max(...rowHeightList)
imgs[i].style.cssText = `
width:${imgWidth}px;
top:${minHeight + imgHeightMargin}px;
left:${imgWidth * rowNum + imgMargin * (rowNum + 1)}px`
rowHeightList[rowNum] += imgHeight + imgHeightMargin
}
}
在为每张图片设置定位时,顺序排列可能导致列于列之间高度差距过大,所以需要使用一个数组来保存每一列的高度,每次插入图片时,都往高度最小的那一列进行插入
由于每张图片都是使用的绝对定位,所以无法撑开容器,如果页面中有其他盒子,便会导致布局混乱,所以要将最高列的高度设置为容器的高度
遍历每一个img节点,使用cssText
+模板字符串,设置定位
然后只需要在窗口加载和尺寸变化时调用此方法,即可实现瀑布流布局
window.onload = setPosition
window.onresize = setPosition
优化
为了解决改变屏幕尺寸时图片闪烁的问题,可以为图片添加一个过渡
修改css代码
.waterfallsFlow{
width: 200px;
position: absolute;
transition: all .5s;
}
由于window.onresize触发过于频繁,可以使用防抖函数
// 使用闭包完成一个简单的防抖函数
function debounce(func,deplay){
let timer = null
return function (){
if (timer){clearTimeout(timer)}
timer = setTimeout(()=>{
func()
},deplay)
}
}
然后修改代码
window.onload = setPosition
window.onresize = debounce(setPosition,100)
大功告成!!!
完整代码
<!DOCTYPE html>
<html lang="zh">
<head>
<title>手写瀑布流</title>
<meta charset="UTF-8">
<style>
*{
margin: 0;
padding: 0;
}
#mainBorder{
position: relative;
width: 100vw;
}
.waterfallsFlow{
width: 200px;
position: absolute;
transition: all .5s;
}
</style>
</head>
<body>
<div id="mainBorder"></div>
<script type="text/javascript">
const imgWidth = 200 //设置图片宽度
const mainDom = document.getElementById("mainBorder")
const imgHeightMargin = 5 // 图片纵向间距
// 添加图片
for (let i = 0; i < 20; i++) {
const imgUrl = `./images/${i}.jpg`
const img = document.createElement('img')
img.src = imgUrl
img.className = "waterfallsFlow"
mainDom.appendChild(img)
}
// 计算瀑布流列数与外边距
function calc() {
const windowWidth = window.innerWidth
const imgRow = parseInt(windowWidth / imgWidth)
const imgMargin = (windowWidth - imgRow * imgWidth) / (imgRow + 1)
return [imgRow, imgMargin]
}
function setPosition() {
const [imgRow/*瀑布流列数*/, imgMargin/*每张图片的外边距*/] = calc()
const imgs = mainDom.children
// let [rowNum/*当前所在列数*/, imgHeight/*当前图片高度*/] = [1, 0]
let rowHeightList = new Array(imgRow) // 创建一个用来存储每一列高度的数组
// 数组初始化
for (let i = 0; i < rowHeightList.length; i++) {
rowHeightList[i] = 0
}
for (let i = 0; i < imgs.length; i++) {
const minHeight = Math.min(...rowHeightList)
const rowNum = rowHeightList.indexOf(minHeight)
const imgHeight = imgs[i].offsetHeight
// 由于子元素设置了absolute,故父元素不会被撑开,需要给父元素设置高度
mainDom.offsetHeight = Math.max(...rowHeightList)
imgs[i].style.cssText = `
width:${imgWidth}px;
top:${minHeight + imgHeightMargin}px;
left:${imgWidth * rowNum + imgMargin * (rowNum + 1)}px`
rowHeightList[rowNum] += imgHeight + imgHeightMargin
}
}
// 使用闭包完成防抖函数
function debounce(func,deplay){
let timer = null
return function (){
if (timer){clearTimeout(timer)}
timer = setTimeout(()=>{
func()
},deplay)
}
}
window.onload = setPosition
window.onresize = debounce(setPosition,100)
</script>
</body>
</html>