本文摘自PHP中文网,作者小云云,侵删。
瀑布流布局中的图片有一个核心特点―等宽不定等高,瀑布流布局在国内网网站都有一定规模的使用,比如pinterest、花瓣网等等。本文主要和大家详细分析了一个原生JS实现瀑布流插件以及代码相关讲解,对此有兴趣的读者们参考学习下吧,希望能帮助到大家。基础功能实现
首先我们定义好一个有 20 张图片的容器,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <body>
<style>
#waterfall {
position: relative;
}
.waterfall-box {
float: left;
width: 200px;
}
</style>
</body>
<p id= "waterfall" >
<img src= "images/1.png" class= "waterfall-box" >
<img src= "images/2.png" class= "waterfall-box" >
<img src= "images/3.png" class= "waterfall-box" >
<img src= "images/4.png" class= "waterfall-box" >
<img src= "images/5.png" class= "waterfall-box" >
<img src= "images/6.png" class= "waterfall-box" >
...
</p>
由于未知的 css 知识点,丝袜最长的妹子把下面的空间都占用掉了。。。
接着正文,假如如上图,每排有 5 列,那第 6 张图片应该出现前 5 张图片哪张的下面呢?当然是绝对定位到前 5 张图片高度最小的图片下方。
那第 7 张图片呢?这时候把第 6 张图片和在它上面的图片当作是一个整体后,思路和上述是一致的。代码实现如下:
Waterfall.prototype.init = function () {
...
const perNum = this .getPerNum()
const perList = []
for (let i = 0; i < perNum; i++) {
perList.push(imgList[i].offsetHeight)
}
let pointer = this .getMinPointer(perList)
for (let i = perNum; i < imgList.length; i++) {
imgList[i].style.position = 'absolute'
imgList[i].style.left = `${imgList[pointer].offsetLeft}px`
imgList[i].style.top = `${perList[pointer]}px`
perList[pointer] = perList[pointer] + imgList[i].offsetHeight
pointer = this .getMinPointer(perList)
}
}
|
细心的朋友也许发现了代码中获取图片的高度用到了 offsetHeight
这个属性,这个属性的高度之和等于图片高度 + 内边距 + 边框
,正因为此,我们用了 padding 而不是 margin 来设置图片与图片之间的距离。此外除了offsetHeight
属性,此外还要理解 offsetHeight
、clientHeight
、offsetTop
、scrollTop
等属性的区别,才能比较好的理解这个项目。css 代码简单如下:
1 2 3 4 5 6 | .waterfall-box {
float : left ;
width : 200px ;
padding-left : 10px ;
padding-bottom : 10px ;
}
|
scroll、resize 事件监听的实现
实现了初始化函数 init 以后,下一步就要实现对 scroll 滚动事件进行监听,从而实现当滚到父节点的底部有源源不断的图片被加载出来的效果。这时候要考虑一个点,是滚动到什么位置时触发加载函数呢?这个因人而异,我的做法是当满足 父容器高度 + 滚动距离 > 最后一张图片的 offsetTop
这个条件,即橙色线条 + 紫色线条 > 蓝色线条时触发加载函数,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | window.onscroll = function () {
if (scrollPX + bsHeight > imgList[imgList.length - 1].offsetTop) {
const fragment = document.createDocumentFragment()
for (let i = 0; i < 20; i++) {
const img = document.createElement( 'img' )
img.setAttribute( 'src' , `images/${i+1}.png`)
img.setAttribute( 'class' , 'waterfall-box' )
fragment.appendChild(img)
}
$waterfall.appendChild(fragment)
}
}
|
因为父节点可能自定义节点,所以提供了对监听 scroll 函数的封装,代码如下:
1 2 3 4 5 6 7 8 9 | proto.bind = function () {
const bindScrollElem = document.getElementById( this .opts.scrollElem)
util.addEventListener(bindScrollElem || window, 'scroll' , scroll.bind( this ))
}
const util = {
addEventListener: function (elem, evName, func) {
elem.addEventListener(evName, func, false )
},
}
|
resize 事件的监听与 scroll 事件监听大同小异,当触发了 resize 函数,调用 init 函数进行重置就行。
使用发布-订阅模式和继承实现监听绑定
既然以开发插件为目标,不能仅仅满足于功能的实现,还要留出相应的操作空间给开发者自行处理。联想到业务场景中瀑布流中下拉加载的图片一般都来自 Ajax 异步获取,那么加载的数据必然不能写死在库里,期望能实现如下调用(此处借鉴了 waterfall 的使用方式),
1 2 3 4 | const waterfall = new Waterfall({options})
waterfall.on( "load" , function () {
})
|
观察调用方式,不难联想到使用发布/订阅模式来实现它,关于发布/订阅模式,之前在 Node.js 异步异闻录 有介绍它。其核心思想即通过订阅函数将函数添加到缓存中,然后通过发布函数实现异步调用,下面给出其代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function eventEmitter() {
this .sub = {}
}
eventEmitter.prototype.on = function (eventName, func) {
if (! this .sub[eventName]) {
this .sub[eventName] = []
}
this .sub[eventName].push(func)
}
eventEmitter.prototype.emit = function (eventName) {
const argsList = Array.prototype.slice.call(arguments, 1)
for (let i = 0, length = this .sub[eventName].length; i < length; i++) {
this .sub[eventName][i].apply( this , argsList)
}
}
|
接着,要让 Waterfall 能使用发布/订阅模式,只需让 Waterfall 继承 eventEmitter 函数,代码实现如下:
1 2 3 4 5 6 | function Waterfall(options = {}) {
eventEmitter.call( this )
this .init(options)
}
Waterfall.prototype = Object.create(eventEmitter.prototype)
Waterfall.prototype.constructor = Waterfall
|
继承方式的写法吸收了基于构造函数继承和基于原型链继承两种写法的优点,以及使用 Object.create
隔离了子类和父类,关于继承更多方面的细节,可以另写一篇文章了,此处点到为止。
小优化
为了防止 scroll 事件触发多次加载图片,可以考虑用函数防抖与节流实现。在基于发布-订阅模式的基础上,定义了个 isLoading 参数表示是否在加载中,并根据其布尔值决定是否加载,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let isLoading = false
const scroll = function () {
if (isLoading) return false
if (scrollPX + bsHeight > imgList[imgList.length - 1].offsetTop) {
isLoading = true
this .emit( 'load' )
}
}
proto.done = function () {
this .on( 'done' , function () {
isLoading = false
...
})
this .emit( 'done' )
}
|
这时候需要在调用的地方加上 waterfall.done
, 从而告知当前图片已经加载完毕,代码如下:
1 2 3 4 5 | const waterfall = new Waterfall({})
waterfall.on( "load" , function () {
waterfall.done()
})
|
相关推荐:
纯原生JS的瀑布流插件Macy.js使用详解
Jquery瀑布流插件使用介绍_jquery
jQuery瀑布流插件Wookmark使用实例_jquery
以上就是JS代码实现瀑布流插件的详细内容,更多文章请关注木庄网络博客!
相关阅读 >>
深入研究node.js中的日志信息
javascript的结束方法有哪些?
javascript如何修改div内容
javascript数据类型的介绍
content-type几种值的区别及用法介绍
css如何实现任意角度的扇形(代码示例)
javascript怎么实现点击按钮跳转页面
javascript中blur是什么
javascript变量的意思
react中如何引入插件
更多相关阅读请进入《javascript》频道 >>
人民邮电出版社
本书对 Vue.js 3 技术细节的分析非常可靠,对于需要深入理解 Vue.js 3 的用户会有很大的帮助。——尤雨溪,Vue.js作者
转载请注明出处:木庄网络博客 » JS代码实现瀑布流插件