分享一個Vue實現圖片水平瀑布流的插件

来源:https://www.cnblogs.com/smileZAZ/archive/2022/09/30/16744941.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一.需求來源 今天碰到了一個需求,需要在頁面里,用水平瀑布流的方式,將一些圖片進行載入,這讓我突然想起我很久以前寫的一篇文章《JS兩種方式實現水平瀑布流佈局》 但是有個問題,這個需求是Vue項目的,那沒辦法,這裡給大家分享下我的開發過程, ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

一.需求來源

今天碰到了一個需求,需要在頁面里,用水平瀑布流的方式,將一些圖片進行載入,這讓我突然想起我很久以前寫的一篇文章《JS兩種方式實現水平瀑布流佈局》

但是有個問題,這個需求是Vue項目的,那沒辦法,這裡給大家分享下我的開發過程,項目主體用的是之前在學習的CRMEB的後端框架來開發,UI使用iView-UI,其餘的場景與其他的vue項目一致

二.邏輯設想

如果不是vue環境,我們的邏輯為

1.獲取所有的div元素
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;定位,不會撐開外部的div,會導致外層盒模型不好佈局,還有就是頁面下拉懶載入,那要怎麼辦呢?

這裡我給出我的處理方法

 整體代碼如下:

<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框架,也可以自行修改

到這裡,所有的思路就結束了

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • LIKE操作符:通配符搜索只能用於文本欄位(字元串) 1、%通配符 1 select 2 col_name 3 from 4 table_name 5 where 6 col_name 7 like "%str" 2、_通配符 1 select 2 col_name 3 from 4 table_n ...
  • 一直使用Postgresql資料庫,有一張表是這樣的: DROP TABLE IF EXISTS "public"."devicedata"; CREATE TABLE "public"."devicedata" ( "Id" varchar(200) COLLATE "pg_catalog"."d ...
  • 據中國信通院發佈,2012年到2021年10年間,我國數字經濟規模由12萬億元增長到45.5萬億元,在整個GDP中的比重由21.6%提升至39.8%。順應時代發展新趨勢,“數據”成為新的生產要素已是毋庸置疑的共識。 如果說數據中台的崛起代表著企業數字化轉型從流程驅動走向數據驅動,從數字化走向智能化。 ...
  • 首發微信公眾號:SQL資料庫運維 原文鏈接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1&sn=450e9e94fa709b5eeff0de371c62072b&chksm=ea37536cdd40da7 ...
  • 現如今,手機錄屏是必不可少的能力之一。對於游戲領域作者來說,在平時直播玩游戲、製作攻略、操作集錦時,不方便切屏,這時在游戲內如果有一個錄製按鈕就可以隨時開啟,記錄下每個精彩瞬間,減少後期剪輯工作量;在直播App中開啟一鍵錄屏,不光方便主播後續的賬號運營與復盤,用戶也能隨時截取有意思的片段傳播在社交媒 ...
  • #背景 webpack構建過程中的hooks都有什麼呢?除了在網上看一些文章,還可以通過更直接的辦法,結合官方文檔快速讓你進入webpack的hook世界 寫一個入口文件 //index.js const webpack = require("webpack"); const path = requ ...
  • 語言基礎-變數 前言 從本篇博客開始 博主個人認為重要的知識點都會在在行前添加 ⭐ 來進行標識 變數 ECMASCRIPT變數是鬆散類型,意思是變數可以用於保存任何類型的數據。ECMASCRIPT中有三個關鍵字可以來聲明變數:var、let和const。 值得註意的是let和const只能在ES6以 ...
  • React Or Taro 項目配置Eslint校驗 一、下載Eslint相關deps依賴項; npm install --save-dev eslint-plugin-prettier eslint-plugin-jsx-a11y eslint-config-airbnb 註意:由於eslint- ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...