JS achieve a native plug-flow waterfall

Waterfall layout images have a core feature - variable-width high waterfalls flow layout has a certain scale in the domestic network sites such as Pinterest , petal net and so on. Then the rest is based on the characteristics of the start waterfall adventure.

Basic functions to achieve

First, we define a good picture of the container 20,

<body>
  <style>
    #waterfall {
      position: relative;
    }
    .waterfall-box {
      float: left;
      width: 200px;
    }
  </style>
</body>
<div 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">
    ...
  </div>

Due to the unknown css knowledge, stockings longest sister occupies the space below out. . .

Then the body, if the above chart, each row has five, it should first six pictures before five sheets of pictures which appear below it? Of course, absolute positioning is to minimize the height of the first five pictures in the picture below.

That first seven pictures? This is the first time the six pictures and the picture on it as a whole after the ideas and the above is the same. Code is implemented as follows:

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)
  }
}

Careful friends may have found a high degree of code uses images acquired offsetHeightthis property, which is equal to the sum of the height 图片高度 + 内边距 + 边框, because of this, we used padding instead of margin to set the distance between the picture and the picture. Also in addition to offsetHeightproperty, in addition to understanding offsetHeight, clientHeight, offsetTop, scrollTopand other attributes of the difference, in order to better understand the project. Simple css code as follows:

.waterfall-box {
  float: left;
  width: 200px;
  padding-left: 10px;
  padding-bottom: 10px;
}

This completes the basic layout of the cascade, the effect is as follows:

scroll, resize event monitor implementation

After initialization function init achieved, the next step is to realize the scroll scroll event listener to achieve when there is a steady stream of images is loaded roll out the effect of the parent node at the bottom. This time to consider a point, loading function is triggered when scrolling to what position do? This varies, my approach is as satisfying 父容器高度 + 滚动距离 > 最后一张图片的 offsetTopthe condition that triggered the orange line + load function when the purple line> blue lines, as follows:

window.onscroll = function() {
  // ...
  if (scrollPX + bsHeight > imgList[imgList.length - 1].offsetTop) {// 浏览器高度 + 滚动距离 > 最后一张图片的 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)
  }
}

Because the parent node may customize, thus providing a package of the listener scroll function, as follows:

  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 的使用方式),

const waterfall = new Waterfall({options})

waterfall.on("load", function () {
  // 此处进行 ajax 同步/异步添加图片
})

观察调用方式,不难联想到使用发布/订阅模式来实现它,关于发布/订阅模式,之前在 Node.js 异步异闻录 有介绍它。其核心思想即通过订阅函数将函数添加到缓存中,然后通过发布函数实现异步调用,下面给出其代码实现:

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 函数,代码实现如下:

function Waterfall(options = {}) {
  eventEmitter.call(this)
  this.init(options) // 这个 this 是 new 的时候,绑上去的
}

Waterfall.prototype = Object.create(eventEmitter.prototype)
Waterfall.prototype.constructor = Waterfall

继承方式的写法吸收了基于构造函数继承和基于原型链继承两种写法的优点,以及使用 Object.create 隔离了子类和父类,关于继承更多方面的细节,可以另写一篇文章了,此处点到为止。

小优化

为了防止 scroll 事件触发多次加载图片,可以考虑用函数防抖与节流实现。在基于发布-订阅模式的基础上,定义了个 isLoading 参数表示是否在加载中,并根据其布尔值决定是否加载,代码如下:

let isLoading = false
const scroll = function () {
  if (isLoading) return false // 避免一次触发事件多次
  if (scrollPX + bsHeight > imgList[imgList.length - 1].offsetTop) { // 浏览器高度 + 滚动距离 > 最后一张图片的 offsetTop
    isLoading = true
    this.emit('load')
  }
}

proto.done = function () {
  this.on('done', function () {
    isLoading = false
    ...
  })
  this.emit('done')
}

这时候需要在调用的地方加上 waterfall.done, 从而告知当前图片已经加载完毕,代码如下:

const waterfall = new Waterfall({})
waterfall.on("load", function () {
  // 异步/同步加载图片
  waterfall.done()
})

项目地址

项目地址

此插件在 React 项目中的运用

项目简陋,不足之处在所难免,欢迎留下你们宝贵的意见。

Guess you like

Origin www.cnblogs.com/homehtml/p/12039165.html