100行js代碼搞定瀑布流插件 zx-waterfall

效果圖

100行js代碼搞定瀑布流插件

GitHub源碼&Demo: https://github.com/capricorncd/zx-waterfall

waterfall.js

/**
 * Created by Capricorncd.
 * Date: 2018/12/14 16:00
 * https://github.com/capricorncd
 */
import App from './app.js'
// 默認配置文件
const DEFAULT_OPTS = {
  // 外容器
  wrapper: null,
  // 子元素選擇器
  itemSelector: '',
  // 元素間距
  gutter: 20,
  itemWidth: 300
}

class ZxWaterfall {
  constructor (opts) {
    this.opts = Object.assign({}, DEFAULT_OPTS, opts)
    // 數據列表
    this.list = []
    this.init()
    // 監聽window.onresize
    App.addEvent(window, 'resize', this.resetPosition.bind(this))
  }

  init () {
    let opts = this.opts
    if (!App.isElement(opts.wrapper)) {
      throw new Error(`瀑布流外容器非DOM元素`)
    }
    // 獲取容器Rect信息
    let wrapperBox = opts.wrapper.getBoundingClientRect()
    // console.log(wrapperBox.width)
    // 可列數
    let columnNum = Math.floor(wrapperBox.width / (opts.itemWidth + opts.gutter))
    // 元素實際寬度
    this.itemWidth = (wrapperBox.width - (columnNum + 1) * opts.gutter) / columnNum
    // 列數組
    this.columns = Array(columnNum)
    this.columns.fill(0, 0)
    // console.log(this.columns, this.itemWidth)
  }

  // 設置數據
  setData (arr) {
    // 元素添加完成後,重新計算位置
    if (Array.isArray(arr) && arr.length) {
      let oldLen = this.list.length
      this.list = this.list.concat(arr)
      this._setPosition(oldLen)
    }
  }

  resetPosition () {
    this.init()
    this._setPosition(0)
  }

  _setPosition (start) {
    let opts = this.opts
    // 間距
    let gutter = opts.gutter
    // 獲取當前容器內子元素
    let childs = opts.wrapper.querySelectorAll(opts.itemSelector)
    for (let i = start; i < this.list.length; i++) {
      let $item = childs[i]
      if (!$item) continue
      $item.style.width = this.itemWidth + 'px'
      $item.style.display = 'inline-block'
      let itemHeight = $item.offsetHeight
      // 獲取列高度的最小值
      let min = Math.min.apply(null, this.columns)
      let index = this.columns.findIndex(i => i === min)
      // 設置$item位置
      $item.style.top = `${min + gutter}px`
      $item.style.left = `${(this.itemWidth + gutter) * index + gutter}px`
      // 重置瀑布流當前列高度值
      this.columns[index] = min + itemHeight + gutter
      // 設置/更新容器最新高度
      opts.wrapper.style.minHeight = Math.max.apply(null, this.columns)
    }
  }

  // 清空列表
  clearList () {
    this.list = []
    this.init()
  }

  /**
   * 預加載媒體元素
   * @param arr
   */
  loadMedia (arr) {
    return new Promise(resolve => {
      if (Array.isArray(arr) && arr.length) {
        let len = arr.length
        let count = 0
        /* eslint-disable */
        function _count () {
          count++
          if (len === count) resolve()
        }

        function _loadImage (url) {
          let $el = App.createElm('img', {src: url})
          $el.onload = _count
          $el.onerror = _count
        }
        
        arr.forEach(url => {
          _loadImage(url)
        })
      } else {
        resolve()
      }
    })
  }
}

export default ZxWaterfall

app.js

export default {
  addEvent (el, type, func) {
    el.addEventListener(type, func, false)
  },
  isElement (obj ) {
    return !!(obj && obj.nodeType === 1)
  },
  isObject (obj) {
    let type = typeof obj
    return type === 'function' || type === 'object' && !!obj
  },
  createElm (tag, attrs) {
    if (this.isObject(tag) && attrs === void 0) {
      attrs = tag
      tag = void 0
    }
    const $el = document.createElement(tag || 'div')
    for (let key in attrs) {
      $el.setAttribute(key, attrs[key])
    }
    return $el
  }
}

使用

demo.vue

<template>
  <ul ref="listWrapper" class="list-wrapper">
    <li class="item" v-for="(item, index) in list" :key="index" style="display: none">
      // item 內容(略)
      <img :src="item.imgUrl">
    </li>
  </ul>
</template>
<script>
import ZxWaterfall from '../waterfall.js'
export default {
  data () {
    return {
      waterfall: null,
      list: []
    }
  },
  methods: {
    async getList () {
      // 從服務器獲取列表數據
      let res = await App.post({page: 1})
      this.list = this.list.concat(res.list)
      // 獲取需要顯示的媒體元素
      let medias = res.list.map(item => item.imgUrl)
      // 預加載媒體元素
      await this.waterfall.loadMedia(medias)
      this.waterfall.setData(res.list)
    }
  },
  mounted () {
    const $wrapper = this.$refs.listWrapper
    // 實例化瀑布流
    this.waterfall = new ZxWaterfall({
      wrapper: $wrapper,
      itemSelector: '.item',
      // 元素寬度,大概值
      itemWidth: 300,
      // 元素間間隔
      gutter: 20
    })
  }
}
</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章