vue 实现简易瀑布流+上拉加载更多

145 阅读1分钟
    <template>
      <div class="waterfall" @scroll="loadMore">
          <div class="masonry" ref="masonry">
            <div
              class="item"
              v-for="(item, index) in showPicList"
              :key="index"
            >
              <img class="picture" v-real-img="item.src" :src="imgDefault"  @load="imageOnLoad" />
            </div>
          </div>
          <div v-if="!addPicList.length" class="placeholder">数据已加载完毕</div>
        </div>
    </template>

<script>
export default {
  name: 'Waterfall',
  data() {
    return {
      addPicList: [],
      showPicList: [],
      pageNumber: 1,
      imgDefault: require('../../assets/imgs/picError.png'),
    }
  },
  mounted() {
    this.getData()
    const debounceWaterfall = this.debounce(this.waterfall, 50)
    window.addEventListener('resize', () => debounceWaterfall())
    this.$eventBus.$on('imageOnLoad', () => debounceWaterfall())
  },
  methods: {
    getData() {
      // 发送接口
      this.$store.dispatch('api' + this.pageNumber).then((res) => {
        this.addPicList = []
        if (res.data.pageRecords.length) {
          this.pageNumber += 1 // 下一页
          this.addPicList = res.data.pageRecords
          this.showPicList = [...this.showPicList, ...this.addPicList]
        }
      })
    },
    debounce(func, delay) {
      let timer = null
      return function (...args) {
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
          func.apply(this, args)
        }, delay)
      }
    },
    imageOnLoad() {
      this.$eventBus.$emit('imageOnLoad')
    },
    waterfall() {
      var items = this.$refs.masonry.children
      var columns = 5 // 每行展示几列
      var arr = []
      // 距离左右边缘的距离固定,item的缝隙固定
      var gapToTop = 10 // 与顶部之间的距离,避免盒子重合
      var gap = 10 // 上下间距
      var gapBetween = 10 // 左右item之间的距离
      var gapToSide = 10 // 到masonry左、右边缘的距离
      var calcItemWidth = (this.$refs.masonry.clientWidth - 2 * gapToSide - (columns - 1) * gapBetween) / columns
      for (var i = 0; i < items.length; i++) {
        this.$refs.masonry.children[i].style.width = calcItemWidth + 'px'
        if (i < columns) {
          items[i].style.top = gapToTop + 'px'
          // 调节第一行偏移量,让整个瀑布流区域居中
          items[i].style.left = (calcItemWidth + gapBetween) * i + gapToSide + 'px'
          arr.push(items[i].offsetHeight + gap)
        } else {
          var minHeight = arr[0]
          var index = 0
          for (var j = 0; j < arr.length; j++) {
            if (minHeight > arr[j]) {
              minHeight = arr[j]
              index = j
            }
          }
          items[i].style.top = arr[index] + gapToTop + 'px'
          items[i].style.left = items[index].offsetLeft + 'px'
          arr[index] = arr[index] + items[i].offsetHeight + gap
        }
      }
      this.$refs.masonry.style.height = Math.max(...arr) + gap + 'px'
    },
    loadMore(event) {
      const el = event.target
      if (Math.ceil(el.scrollTop + el.clientHeight) >= el.scrollHeight) {
        this.getData()
      }
    },
  }
}
</script>

<style lang="less">
    .waterfall {
        overflow-y: scroll;
        max-height: 1000px;
        .masonry {
          position: relative;
          background-color: rgb(233, 233, 233);
          .item {
            position: absolute;
            overflow: hidden;
            border-radius: 10px;
            background-color: #ffffff;
            img {
              width: 100%;
              cursor: pointer;
            }
          }
        }
  }
</style>