手把手带你使用Vue实现一个图片水平瀑布流插件

如何使用Vue实现一个图片水平瀑布流插件?这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助。

一.需求来源

今天碰到了一个需求,需要在页面里,用水平瀑布流的方式,将一些图片进行加载,这让我突然想起我很久以前写的一篇文章《JS两种方式实现水平瀑布流布局

但是有个问题,这个需求是Vue项目的,那没办法,这里给大家分享下我的开发过程,项目主体用的是之前在学习的CRMEB的后端框架来开发,UI使用iView-UI,其余的场景与其他的vue项目一致。【相关推荐:vuejs视频教程

二.逻辑设想

如果不是vue环境,我们的逻辑为

1.获取所有的p元素
2.获取盒子的宽度,宽度都是相同,高度不同
3.在浮动布局中每一行的盒子个数不固定,是根据屏幕宽度和盒子宽度决定
4.获取屏幕宽度
5.求出列数,屏幕宽度 / 盒子宽度 取整
6.瀑布流最关键的是第二行的盒子的排布方式,通过获取第一行盒子中最矮的一个的下标,绝对定位,top是最矮盒子的高度,left是最矮盒子的下标 * 盒子的宽度
7.循环遍历所有的盒子,通过列数找到第一行所有的盒子,将第一行盒子的高度放入数组,再取出数组中最小的一个的下标,就是第6步思路的第一行盒子中最矮盒子的下标。
8.循环继续,第二行第一个盒子,通过绝对定位,放进页面。
9.关键,需要将数组中最小的值加上放进的盒子的高度
10.继续循环,遍历所有
11.如果想要加载更多,需要判断最后一个盒子的高度和页面滚动的距离,再将数据通过创建元素,追加进页面,再通过瀑布流布局展示

但如果是Vue项目,我们可以把逻辑归结为以下几步

1.获取屏幕宽度
2..获取盒子的宽度,宽度都是相同,高度不同
3.在浮动布局中每一行的盒子个数不固定,是根据屏幕宽度和盒子宽度决定
4.求出列数,屏幕宽度 / 盒子宽度 取整
5.瀑布流最关键的是第二行的盒子的排布方式,通过获取第一行盒子中最矮的一个的下标,绝对定位,top是最矮盒子的高度,left是最矮盒子的下标 * 盒子的宽度
6.继续循环,遍历所有
7.如果想要加载更多,需要判断最后一个盒子的高度和页面滚动的距离,再将数据通过创建元素,追加进页面,再通过瀑布流布局展示

三.最终效果图片

四.代码分析

先看下我的html部分

<template>
  <div class="tab-container" id="tabContainer">
    <div class="tab-item" v-for="(item, index) in pbList" :key="index">
      <img :src="item.url" />
    </div>
  </div>
</template>
 
<style scoped>
* {
  margin: 0;
  padding: 0;
}
/* 最外层大盒子 */
.tab-container {
  padding-top: 20px;
  position: relative;
}
/* 每个小盒子 */
.tab-container .tab-item {
  position: absolute;
  height: auto;
  border: 1px solid #ccc;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  background: white;
  /* 元素不能中断显示 */
  break-inside: avoid;
  text-align: center;
}
.tab-container .tab-item img {
  width: 100%;
  height: auto;
  display: block;
}
</style>

核心js部分

<script>
export default {
  name:'compList',
  props:{
    pbList:{
      type:Array,
      default:()=>{return []}
    }
  },
  data() {
    return {
    };
  },
  mounted() {
    this.$nextTick(()=>{
      this.waterFall("#tabContainer", ".tab-item"); //实现瀑布流
    })
  },
  methods: {
    waterFall(
        wrapIdName,
        contentIdName,
        columns = 5,
        columnGap = 20,
        rowGap = 20
    ) {
      // 获得内容可用宽度(去除滚动条宽度)
      const wrapContentWidth =
          document.querySelector(wrapIdName).offsetWidth;
 
      // 间隔空白区域
      const whiteArea = (columns - 1) * columnGap;
 
      // 得到每列宽度(也即每项内容宽度)
      const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns);
 
      // 得到内容项集合
      const contentList = document.querySelectorAll(contentIdName);
 
      // 成行内容项高度集合
      const lineConentHeightList = [];
 
      for (let i = 0; i < contentList.length; i++) {
        // 动态设置内容项宽度
        contentList[i].style.width = contentWidth + "px";
 
        // 获取内容项高度
        const height = contentList[i].clientHeight;
 
        if (i < columns) {
          // 第一行按序布局
          contentList[i].style.top = "0px";
          contentList[i].style.left = contentWidth * i + columnGap * i + "px";
 
          // 将行高push到数组
          lineConentHeightList.push(height);
        } else {
          // 其他行
          // 获取数组最小的高度 和 对应索引
          let minHeight = Math.min(...lineConentHeightList);
          let index = lineConentHeightList.findIndex(
              (listH) => listH === minHeight
          );
 
          contentList[i].style.top = minHeight + rowGap +"px";
          contentList[i].style.left = (contentWidth + columnGap) * index + "px";
 
          // 修改最小列的高度 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 行间距
          lineConentHeightList[index] += height + rowGap;
        }
      }
    },
  },
};
</script>

这里要给大家提个醒,在当插件使用的时候,我们需要用this.$nextTick()来进行页面初始化,因为方法成功的前提是要等页面初始化加载完毕后才能进行获取和计算

总体插件代码为:

<template>
  <div class="tab-container" id="tabContainer">
    <div class="tab-item" v-for="(item, index) in pbList" :key="index">
      <img :src="item.url" />
    </div>
  </div>
</template>
 
<script>
export default {
  name:'compList',
  props:{
    pbList:{
      type:Array,
      default:()=>{return []}
    }
  },
  data() {
    return {
    };
  },
  mounted() {
    this.$nextTick(()=>{
      this.waterFall("#tabContainer", ".tab-item"); //实现瀑布流
    })
  },
  methods: {
    waterFall(
        wrapIdName,
        contentIdName,
        columns = 5,
        columnGap = 20,
        rowGap = 20
    ) {
      // 获得内容可用宽度(去除滚动条宽度)
      const wrapContentWidth =
          document.querySelector(wrapIdName).offsetWidth;
 
      // 间隔空白区域
      const whiteArea = (columns - 1) * columnGap;
 
      // 得到每列宽度(也即每项内容宽度)
      const contentWidth = parseInt((wrapContentWidth - whiteArea) / columns);
 
      // 得到内容项集合
      const contentList = document.querySelectorAll(contentIdName);
 
      // 成行内容项高度集合
      const lineConentHeightList = [];
 
      for (let i = 0; i < contentList.length; i++) {
        // 动态设置内容项宽度
        contentList[i].style.width = contentWidth + "px";
 
        // 获取内容项高度
        const height = contentList[i].clientHeight;
 
        if (i < columns) {
          // 第一行按序布局
          contentList[i].style.top = "0px";
          contentList[i].style.left = contentWidth * i + columnGap * i + "px";
 
          // 将行高push到数组
          lineConentHeightList.push(height);
        } else {
          // 其他行
          // 获取数组最小的高度 和 对应索引
          let minHeight = Math.min(...lineConentHeightList);
          let index = lineConentHeightList.findIndex(
              (listH) => listH === minHeight
          );
 
          contentList[i].style.top = minHeight + rowGap +"px";
          contentList[i].style.left = (contentWidth + columnGap) * index + "px";
 
          // 修改最小列的高度 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 行间距
          lineConentHeightList[index] += height + rowGap;
        }
      }
    },
  },
};
</script>
 
<style scoped>
* {
  margin: 0;
  padding: 0;
}
/* 最外层大盒子 */
.tab-container {
  padding-top: 20px;
  position: relative;
}
/* 每个小盒子 */
.tab-container .tab-item {
  position: absolute;
  height: auto;
  border: 1px solid #ccc;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  background: white;
  /* 元素不能中断显示 */
  break-inside: avoid;
  text-align: center;
}
.tab-container .tab-item img {
  width: 100%;
  height: auto;
  display: block;
}
</style>

五.外层使用和懒加载

在使用这个插件的时候,有两个问题,就是因为内层是position: absolute;定位,不会撑开外部的p,会导致外层盒模型不好布局,还有就是页面下拉懒加载,那要怎么办呢?

这里我给出我的处理方法

 整体代码如下:

<template>
  <div>
    <div class="list-box" @scroll="scrollFun">
      <compList :pbList="pbList" ref="compList"></compList>
    </div>
  </div>
</template>
 
<script>
import compList from "@/pages/test/components/compList";
export default {
  name:'testList',
  components:{
    compList
  },
  data() {
    return {
      //瀑布流数据
      pbList: [
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        }
      ],
      addList:[
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fm%2Fpublic%2Fp2650049201.jpg&refer=http%3A%2F%2Fimg3.doubanio.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1664935370&t=d4bf3e4d352c277a1bdebfcc8fda959f",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        },
        {
          url: "https://img1.baidu.com/it/u=2911909188,130959360&fm=253&fmt=auto&app=138&f=JPEG?w=440&h=641",
        }
      ],
      bottomMain:true
    };
  },
  methods:{
    scrollFun(e) {
      const  offsetHeight= e.target.offsetHeight
      const  scrollHeight= e.target.scrollHeight
      const  scrollTop= e.target.scrollTop
      if((scrollHeight - (offsetHeight+scrollTop)) < 10){
        if(this.bottomMain){
          this.bottomMain = false
          this.addListDataFun()
        }
      }
    },
    addListDataFun(){
      this.$Spin.show({
        render: (h) => {
          return h('div', [
            h('Icon', {
              'class': 'demo-spin-icon-load',
              props: {
                type: 'ios-loading',
                size: 18
              }
            }),
            h('div', '数据更新中...')
          ])
        }
      });
      setTimeout(() => {
        this.pbList = this.pbList.concat(this.addList)
        this.bottomMain = true
        this.$nextTick(()=>{
          this.$refs.compList.waterFall("#tabContainer", ".tab-item")
          this.$Spin.hide()
        })
      },1000)
    }
  }
};
</script>
 
<style scoped>
.list-box{
  position: relative;
  width: 100%;
  height: calc(100vh - 240px);
  background: white;
  padding: 20px 30px 20px 20px;
  margin-top: 20px;
  box-sizing: border-box;
  overflow: auto;
}
</style>

下拉的核心代码为:

scrollFun(e) {
  const  offsetHeight= e.target.offsetHeight
  const  scrollHeight= e.target.scrollHeight
  const  scrollTop= e.target.scrollTop
  if((scrollHeight - (offsetHeight+scrollTop)) < 10){
    if(this.bottomMain){
      this.bottomMain = false
      this.addListDataFun()
    }
  }
},
addListDataFun(){
  this.$Spin.show({
    render: (h) => {
      return h('div', [
        h('Icon', {
          'class': 'demo-spin-icon-load',
          props: {
            type: 'ios-loading',
            size: 18
          }
        }),
        h('div', '数据更新中...')
      ])
    }
  });
  setTimeout(() => {
    this.pbList = this.pbList.concat(this.addList)
    this.bottomMain = true
    this.$nextTick(()=>{
      this.$refs.compList.waterFall("#tabContainer", ".tab-item")
      this.$Spin.hide()
    })
  },1000)
}

这里使用的是iView-UI的全局加载事件,如果你要用其他的UI框架,也可以自行修改

到这里,所有的思路就结束了

(学习视频分享:web前端开发编程基础视频

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要实现一个 Vue 3 瀑布流组件,你可以这么做: 1. 在你的 Vue 项目中安装并引入 masonry 布局插件,比如: ``` npm install masonry-layout ``` 2. 在你的 Vue 组件中引入 masonry 布局插件,并在 mounted 钩子函数中初始化 masonry 布局: ``` import Masonry from 'masonry-layout'; export default { mounted() { this.msnry = new Masonry(this.$el, { // 配置 masonry 布局的选项 }); }, }; ``` 3. 在组件的 template 中使用 masonry 布局。你可以在外层包裹一个容器元素,然后给容器元素的子元素设置类似 masonry-brick 的类名,这些子元素就会按照 masonry 布局的方式排列: ``` <template> <div class="masonry-container"> <div class="masonry-brick" v-for="item in items" :key="item.id"> <!-- 这里是单个瀑布流元素的内容 --> </div> </div> </template> ``` 4. 在组件的 data 中定义 items 数据,表示瀑布流中的所有元素,然后在 template 中使用 v-for 指令渲染这些元素。 5. 如果你想在组件中动态添加、删除瀑布流元素,你可以在对应的方法中调用 masonry 布局插件的 addItems、remove、reloadItems 等方法来更新布局。 例如,在组件的 addItem 方法中,你可以这么写: ``` addItem() { // ### 回答2: 要实现一个基于Vue3的瀑布流组件,我们可以按照以下步骤进行: 1. 创建一个Vue组件,并在组件中定义我们需要的数据和方法。 2. 在数据中定义一个数组,用于存储需要展示的图片或其他内容的信息,例如图片的URL地址。 3. 在组件的生命周期钩子函数中,可以使用一些异步请求或者模拟数据的方法来获取图片或其他内容的信息,并将获取到的数据存储到数组中。 4. 在组件的模板中,使用`v-for`指令来循环遍历数组中的数据,并在每个循环项中展示相应的内容。 5. 使用CSS样式来实现瀑布流布局,可以使用`grid`布局或者`flex`布局来实现。设置每个项的宽度和高度,并使用`column-count`或者`column-width`等属性来控制列数和列宽。 6. 当内容很多时,需要实现视图滚动时的无限加载功能,可以通过监听页面的滚动事件,在滚动到底部时加载更多的内容。 7. 可以添加一些交互的功能,例如点击某个内容项时,可以弹出大图展示或者跳转到详情页。 总结起来,实现一个Vue3的瀑布流组件主要需要定义数据、实现数据的获取和展示,以及使用CSS样式来布局和滚动加载等功能。通过以上步骤的操作,我们就可以完成一个基于Vue3的瀑布流组件。 ### 回答3: 实现一个Vue3瀑布流组件可以通过以下步骤进行: 1. 安装Vue3和相关依赖:首先,我们需要在项目中安装Vue3和其他可能用到的相关依赖,可以使用npm或yarn命令进行安装。 2. 创建瀑布流组件:在Vue3中,创建一个组件需要使用`createApp`方法。我们可以在需要显示瀑布流的页面上使用`createApp`方法创建一个Vue实例,并在`setup`函数中定义瀑布流组件。 3. 定义瀑布流布局:在瀑布流组件中,我们需要定义如何进行布局。一种常见的瀑布流布局是使用CSS的`column-count`属性实现。我们可以在瀑布流组件的模板中使用`v-for`指令遍历需要展示的数据,并使用`column-count`属性将它们分成多列。 4. 动态计算布局:由于瀑布流中的每列高度不一致,我们需要动态计算每个元素的位置。可以在瀑布流组件的`setup`函数中使用Vue3的响应式数据来保存每列的高度,并在模板中根据每列的高度动态设置每个元素的位置。 5. 处理滚动加载:瀑布流一般需要滚动加载更多数据,我们可以通过监听滚动事件来判断是否需要加载更多数据。可以使用Vue3的`onMounted`钩子函数来在组件加载完毕后绑定滚动事件,并使用`onUnmounted`钩子函数在组件卸载时解绑滚动事件。 6. 实现数据加载:在滚动加载时,可以通过请求接口或其他方式加载更多数据,并将新加载的数据添加到原有的数据列表中。可以在瀑布流组件的`setup`函数中使用Vue3的响应式数据来保存数据列表,并在模板中使用`v-for`指令遍历展示所有的数据。 7. 响应式优化:为了提高性能和用户体验,可以对瀑布流组件进行响应式优化。可以使用Vue3的`watchEffect`函数来监听数据的变化,并在数据发生变化时重新计算布局和渲染页面。 总结:通过以上步骤,我们可以实现一个基本的Vue3瀑布流组件。当然,实际项目中可能还需要考虑更多的功能和细节,如图片加载优化、动画效果等。希望以上的回答能给您提供一些参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值