Vue原生图片瀑布流

图片瀑布流可以节省图片的排版空间,美观图片的排列,避免图片排列的参差不齐。
实现图片瀑布流可以固定宽(花瓣),也可以固定高(百度图片),看个人需求,我的需求是宽固定。
如果图片的排列不适用瀑布流的话,排版如下:
在这里插入图片描述
是不是特别的难以接受,的确,自己都无法接受,更别说展示给他人观看了。
如果使用瀑布流,排版如下:
在这里插入图片描述
那可真是天差地别啊,如此美观的排列,简直让人心情愉悦。
图片瀑布流代码:
HTML

<div class="waterFall-box" ref="box">
    <div class="img-box" v-for="(item, index) in images" :key="index" ref="img">
      <img :src="item.img" alt="">
    </div>
    <footer v-if="isLoad == false"
            :style="{position: 'absolute', top: Math.max(...heightArray)+'px', color: 'red', left: '50%', transform: 'translateX(-50%)'}">
      没有图片加载了...
    </footer>
  </div>



  HTML代码可以自己定,看你的需求了,我这里遍历一个<div>,这个<div>装着<img>
<div><img /></div>的格式

每个图片的格子套在一个大的容器里面,这样后面对每个格子进行absolute定位的时候,不至于看不见图片的现象。

VUE.JS
1.首先如果不了解vue的this.$nextTick()API的话,先去官网看一下这个api,nextTick
这个api让你避免dom找不到的问题。
2.首先请求json数据(方便后台),可以在create或者mounted里面请求json,如果是本地json,需要放在public文件夹下,不然请求不到

created() {
      Axios({
        url: '/waterFall.json',
        method: 'get'
      }).then(res => {
        if (res.data.code == 200) {
          this.images = res.data.data ? res.data.data : []
          this.loadImgHeight()
        }
      })
    }

3.加载完毕json,使用JS的Image()对象预加载图片,避免找不到dom,提前渲染图片:

/**
       * 预加载图片资源
       * */
      loadImgHeight() {
        let count = 0 //计数变量 判断是否预加载图片是否完成
        this.images.forEach((item) => {
          //使用image类预加载图片
          let image = new Image()
          image.src = item.img
          image.onload = image.onerror = event => {
            count++
            if(count == this.images.length) {
              this.$nextTick(() => {
                this.init()
                this.positionImg(0)
              })
            }
          }
        })
      }

4.预加载完毕图片,可以先初始化数据,判断当前页面容器可以排列多少固定宽度的图片格子,这都是需要先计算出来的,ClientWidth就很不错了,首先得到当前容器的可视宽度,然后模板除固定宽度,四舍五入得到可排列的列格子,

/**
       * @remarks 初始化
       * 初始化容器的宽度,计算出容器可容纳多少固定宽度图片的列,
       * 如果可排列固定宽度的图片宽度无法沾满容器的宽度,需要计算出空余的宽度,固定首图片的left
       * */
      init() {
        //得到页面的宽度
        const pageWidth_padding = this.$refs.box.clientWidth
        //页面的padding像素
        this.offsetP = this.$refs.box.style.paddingLeft.replace(/[^0-9]/ig, "")
        //获得页面的真实宽度(除去padding、margin、border...)
        const pageWidth = pageWidth_padding - (this.offsetP * 2)
        //计算出当前页面可展示多少列图片
        const column = Math.floor(pageWidth / this.imgWidth)
        //偏移像素值
        this.surplusW = pageWidth - (column * this.imgWidth)
        //初始化存储高度数组
        for (let i = 0; i < column; i++) {
          this.heightArray.push(0)
        }
      }

我这里多计算了排列图片格子后,剩下多余的宽度,好是图片居中排布(如果图片刚刚布满容器,则可以省略)。

5.此时,就可以定位我们的图片了,宽度固定瀑布流的逻辑:
核心思路就这两个老哥了····
5.1.每次循环,找到最低高度(计算top偏移)
5.2.找到最低高度的索引(计算left偏移)

每次都图片的定位,只需要找到上一列最低高度的图片位置,把当前图片定位到上一列最低高度的下面
top:为上一列最低高度图片的高度,left:为上一列最低高度的存储在数组的索引
图片的固定宽度*

/**
       * @remark 定位图片
       * @param:
       *  start: 循环开始位置,开始为0,如果滚动条滑到底部,则start为容器存在图片资源的数量即this.images.length
       *  ----------宽高都计算img的父容器的宽高
       * */
      positionImg(start) {
        //获得img标签的父容器的DOM
        let parentDom = this.$refs.img
        for (let i = start; i < this.images.length; i++) {
          //获得最小高度
          const minHeight = Math.min(...this.heightArray)
          //获得最小高度索引
          const index = this.heightArray.indexOf(minHeight)
          //获得当前图片的高度
          const currHeight = parentDom[i].clientHeight
          //定位
          parentDom[i].style.transform = '50px'
          parentDom[i].style.position = 'absolute'
          parentDom[i].style.top = minHeight + 'px'
          parentDom[i].style.left = this.imgWidth * index + + ((Math.floor((this.surplusW / 2)) + 30)) +  'px'
          this.heightArray[index] += currHeight
        }
        //对父容器赋值当前heightArray数组的最大高度
        this.$refs.box.style.height = Math.max(...this.heightArray) + 50 + 'px'
      }

注意一点就是,因为position:absolute定位后,脱离了文档流,致使,容器没了高度(就不能自适应撑开高度,导致,定位排列后的图片无法查看,所以,定位完成,数组里面会存储一个最大高度,这个高度就是当前排列的图片的最大可是高度)

6.如果有需求的话,可以在mounted监听滚动条的滚动行为(window.addEventListener就非常好用了),如果滚动到底部,则继续加载图片。
完整代码:

<template>
  <div class="waterFall-box" ref="box">
    <div class="img-box" v-for="(item, index) in images" :key="index" ref="img">
      <img :src="item.img" alt="">
    </div>
    <footer v-if="isLoad == false"
            :style="{position: 'absolute', top: Math.max(...heightArray)+'px', color: 'red', left: '50%', transform: 'translateX(-50%)'}">
      没有图片加载了...
    </footer>
  </div>
</template>

<script>
  import Axios from 'axios'

  export default {
    name: "WaterFall",
    data() {
      return {
        images: [], //存储图片资源
        imgWidth: 220, //图片的宽度
        heightArray: [],  //存储高度数组,用于判断最小高度的图片位置
        isLoad: true,  //是否继续加载图片
        surplusW: 0, //是否存在剩余宽度
        offsetP: 0,
        count: 0
      }
    },
    methods: {
      /**
       * 预加载图片资源
       * */
      loadImgHeight() {
        let count = 0 //计数变量 判断是否预加载图片是否完成
        this.images.forEach((item) => {
          //使用image类预加载图片
          let image = new Image()
          image.src = item.img
          image.onload = image.onerror = event => {
            count++
            if(count == this.images.length) {
              this.$nextTick(() => {
                this.init()
                this.positionImg(0)
              })
            }
          }
        })
      },
      /**
       * @remarks 初始化
       * 初始化容器的宽度,计算出容器可容纳多少固定宽度图片的列,
       * 如果可排列固定宽度的图片宽度无法沾满容器的宽度,需要计算出空余的宽度,固定首图片的left
       * */
      init() {
        //得到页面的宽度
        const pageWidth_padding = this.$refs.box.clientWidth
        //页面的padding像素
        this.offsetP = this.$refs.box.style.paddingLeft.replace(/[^0-9]/ig, "")
        //获得页面的真实宽度(除去padding、margin、border...)
        const pageWidth = pageWidth_padding - (this.offsetP * 2)
        //计算出当前页面可展示多少列图片
        const column = Math.floor(pageWidth / this.imgWidth)
        //偏移像素值
        this.surplusW = pageWidth - (column * this.imgWidth)
        //初始化存储高度数组
        for (let i = 0; i < column; i++) {
          this.heightArray.push(0)
        }
      },
      /**
       * @remark 定位图片
       * @param:
       *  start: 循环开始位置,开始为0,如果滚动条滑到底部,则start为容器存在图片资源的数量即this.images.length
       *  ----------宽高都计算img的父容器的宽高
       * */
      positionImg(start) {
        //获得img标签的父容器的DOM
        let parentDom = this.$refs.img
        for (let i = start; i < this.images.length; i++) {
          //获得最小高度
          const minHeight = Math.min(...this.heightArray)
          //获得最小高度索引
          const index = this.heightArray.indexOf(minHeight)
          //获得当前图片的高度
          const currHeight = parentDom[i].clientHeight
          //定位
          parentDom[i].style.transform = '50px'
          parentDom[i].style.position = 'absolute'
          parentDom[i].style.top = minHeight + 'px'
          parentDom[i].style.left = this.imgWidth * index + + ((Math.floor((this.surplusW / 2)) + 30)) +  'px'
          this.heightArray[index] += currHeight
        }
        //对父容器赋值当前heightArray数组的最大高度
        this.$refs.box.style.height = Math.max(...this.heightArray) + 50 + 'px'
      }
    },
    mounted() {
      const _this = this

      //监听滚动条滚动,实现懒加载图片
      window.addEventListener('scroll', function () {
        //得到可滚动距离
        const scrollDistance = document.documentElement.scrollHeight - document.documentElement.clientHeight
        //滚动到顶部的距离
        const scroll = document.documentElement.scrollTop
        if (scrollDistance == scroll) {
          Axios({
            url: '/waterFall2.json',
            method: 'get'
          }).then(res => {
            if (res.data.code == 200) {
              _this.count += 1
              if(_this.count == 4) {
                _this.isLoad = false
              }
              if(_this.isLoad) {
                const start = _this.images.length
                for (let item of res.data.data) {
                  _this.images.push(item)
                }
                //滑到底部继续加载图片,this.$nextTick()异步加载,待资源虚拟DOM加载完毕
                _this.$nextTick(() => {
                  _this.positionImg(start)
                })
              }
            }
          })
        }
      })
    },
    created() {
      Axios({
        url: '/waterFall.json',
        method: 'get'
      }).then(res => {
        if (res.data.code == 200) {
          this.images = res.data.data ? res.data.data : []
          this.loadImgHeight()
        }
      })
    }
  }
</script>

<style scoped>

  .waterFall-box {
    position: relative;
    text-align: center;
    overflow-y: hidden;
  }

  .waterFall-box .img-box {
    width: 210px;
    vertical-align: top;
    display: block;
    float: left;
  }

  .waterFall-box .img-box img {
    width: 100%;
    animation: imgBox .5s ease-in-out;
  }

  .waterFall-box .img-box img:hover {
    transform: translateY(-3px);
    transition: transform .5s ease-in-out;
    box-shadow: 0 20px 20px 2px #737373;
  }

  @keyframes imgBox {
    0%{
      opacity: 0;
      transform: translateY(-100px);
    }
    100% {
      opacity: 1;
      transform: translateX(0);
    }
  }

</style>
  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值