使用说明
Install zx-waterfall using npm
npm i --save zx-waterfall
ES6+
import ZxWaterfall from 'zx-waterfall'
const waterfall = new ZxWaterfall({
// HTMLElement, waterfall items's outer container
container: document.getElementById('zxWaterfall'),
// children item selector, eg. '.item-container'
itemSelector: '.item-wrapper',
// item's spacing, unit px
gutter: 20,
// item's width
itemWidth: 300
})
// reset
waterfall.reset()
// loaMedias
waterfall.loadMedia(['http://xx.com/aaa.jpg']).then(_ => {
// change
waterfall.change()
})
browser
demo
注意
container 's style must be style.position=relative|absolute|fixed
参数options
container: HTMLElement
瀑布流外容器.
containerWidth: Number
瀑布流宽度,如果初始化时外容器未隐藏状态则需使用该参数.
默认获取外容器宽度.
itemSelector: String
子元素选择器,比如 样式名'.item-container'.
gutter: Number
元素间的间隔.
verticalGutter: Number
元素间垂直方向间隔,默认使用gutter值.
itemWidth: Number
元素宽度, 默认 300, 会根据容器宽度自动调整.
forceItemWidth: Boolean
强制元素宽度,即使用itemWidth作为宽度值,默认 false.
align: String, Optional value left|center|right
强制宽度时,元素显示靠边位置,默认 center.
方法
reset()
重置瀑布流初始值
loadMeida(array)
预加载列表数据中的image媒体元素.
array: ['http://a.com/1.jpg', 'http://a.com/2.jpg']
@return promise
change()
列表数据改变后后,通知瀑布流更新元素位置.
源码
/**
* preload image
* @param url
* @param handler
*/
function loadImage (url, handler) {
let $el = document.createElement('img')
$el.src = url
$el.onload = handler
$el.onerror = handler
$el = null
}
/**
* to int
* @param m
* @returns {number}
*/
function int (m) {
let n = parseInt(m)
return isNaN(n) ? 0 : n
}
/**
* convert pseudoArray to array
* @param pseudoArray
* @param index
* @returns {T[]}
*/
function slice (pseudoArray, index) {
return Array.prototype.slice.call(pseudoArray, int(index))
}
// default options
const DEF_OPTIONS = {
// HTMLElement, waterfall items's outer container
container: null,
// container's width, container are hidden when initialized
// default get container offsetWidth when it's visible
containerWidth: 0,
// children item selector, eg. '.item-container'
itemSelector: '',
// item's spacing, unit px
gutter: 20,
// item's vertical spacing, default use gutter's value
verticalGutter: 0,
// item's width
itemWidth: 300,
// force item width
forceItemWidth: false,
// Horizontal align when forceItemWidth is true
align: 'center'
}
/**
* ZxWaterfall
*/
class ZxWaterfall {
/**
* constructor
* @param opts
*/
constructor (opts) {
opts = Object.assign({}, DEF_OPTIONS, opts)
// check container
if (!opts.container || opts.container.nodeType !== 1) {
throw new TypeError(`Instancing parameter 'container' is not HTMLElement.`)
}
// check itemSelector
if (!opts.itemSelector || typeof opts.itemSelector !== 'string') {
throw new TypeError(`Instancing parameter 'itemSelector' is null or is't a string.`)
}
// check verticalGutter
if (!opts.verticalGutter) {
opts.verticalGutter = opts.gutter
}
// item number
this.count = 0
this.opts = opts
this._init()
// clone this.reset
this._resetClone = this.reset.bind(this)
window.addEventListener('resize', this._resetClone)
}
/**
* initialization
* @private
*/
_init () {
let opts = this.opts
// container width
let containerWidth = int(opts.containerWidth) || opts.container.offsetWidth
// column number
let columnNum = Math.floor(containerWidth / (opts.itemWidth + opts.gutter))
// opts.itemWidth when opts.forceItemWidth = true
// else use compute new width
this.itemWidth = opts.forceItemWidth
? opts.itemWidth
: (containerWidth - (columnNum + 1) * opts.gutter) / columnNum
// column current height array
this.columns = Array(columnNum)
this.columns.fill(0, 0)
// offset left when forceItemWidth=true
this.offsetLeft = 0
if (opts.forceItemWidth) {
let residualSpaceWidth = containerWidth - (this.itemWidth + opts.gutter) * columnNum - opts.gutter
switch (opts.align) {
case 'center':
this.offsetLeft = residualSpaceWidth / 2
break
case 'right':
this.offsetLeft = residualSpaceWidth
break
}
}
}
/**
* set items position
* @private
*/
_setPosition () {
let opts = this.opts
// get new item elements
let $childs = slice(opts.container.querySelectorAll(opts.itemSelector), this.count)
// console.log(this.count, $childs)
let len = $childs.length
// reset this.count value
this.count += len
// handle new $item
let i, $item
for (i = 0; i < len; i++) {
$item = $childs[i]
if (!$item) continue
$item.style.position = 'absolute'
$item.style.width = this.itemWidth + 'px'
$item.style.display = 'inline-block'
// get columns min value
let min = Math.min.apply(null, this.columns)
let index = this.columns.findIndex(val => val === min)
// set $item position
$item.style.top = `${min + opts.verticalGutter}px`
$item.style.left = `${this.offsetLeft + (this.itemWidth + opts.gutter) * index + opts.gutter}px`
// reset waterfall current column height value
let itemHeight = $item.offsetHeight
this.columns[index] = min + itemHeight + opts.verticalGutter
// update container new min height style
// opts.container.style.minHeight = Math.max.apply(null, this.columns) + opts.verticalGutter + 'px'
}
}
/**
* container's items number change
*/
change () {
// reset postion, when new item element append to container, or remove
this._setPosition()
}
/**
* reset
*/
reset () {
this.count = 0
this._init()
this._setPosition()
}
/**
* preload media items
* @param arr media source urls array
* @returns {Promise}
*/
loadMedia (arr) {
return new Promise(resolve => {
if (Array.isArray(arr) && arr.length) {
let len = arr.length
let count = 0
/* eslint-disable */
arr.forEach(url => {
loadImage(url, () => {
count++
if (len === count) resolve()
})
})
} else {
resolve()
}
})
}
/**
* destroy
* removeEventListener window resize
*/
destroy () {
window.removeEventListener('resize', this._resetClone)
}
}
export default ZxWaterfall