分享一個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
  • public static void GetRegistData() { string name = "huishuangzhu"; //搜索到註冊表根目錄 RegistryKey hkml = Registry.ClassesRoot; //搜索到註冊表根目錄下的XXX文件夾。 RegistryK ...
  • 用acme.sh自動部署功能變數名稱證書 安裝ACME 目前使用量最大的免費SSL證書就是Let’s Encrypt,自2018-03開始,Let’s Encrypt官方發佈上線了免費的SSL泛功能變數名稱證書,目前通過DNS方式獲取比較快,國內可以通過鵝雲的DNSPod功能變數名稱API或者貓雲功能變數名稱API自動簽發Let’ ...
  • 經常看到有群友調侃“為什麼搞Java的總在學習JVM調優?那是因為Java爛!我們.NET就不需要搞這些!”真的是這樣嗎?今天我就用一個案例來分析一下。 昨天,一位學生問了我一個問題:他建了一個預設的ASP.NET Core Web API的項目,也就是那個WeatherForecast的預設項目模 ...
  • 1、環境搭建 1.1 依賴 <!-- nacos註冊中心 註解 @EnableDiscoveryClient --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba- ...
  • ULID:Universally Unique Lexicographically Sortable Identifier(通用唯一詞典分類標識符) UUID:Universally Unique Identifier(通用唯一標識符) 為什麼不選擇UUID UUID 目前有 5 個版本: 版本1: ...
  • 虛基類/抽象類 抽象類:有純虛函數的類 虛繼承 通過修飾繼承方式, 如代碼2是虛繼承,被虛繼承的類稱為虛基類 虛繼承派生類的記憶體佈局方式 先是vbptr => 派生類的數據 =>基類的數據 , 對比代碼1和代碼2,發現原本基類數據在前面,派生類數據在後面,但是在虛繼承的時候 基類數據方式放到了後面, ...
  • 下麵給出 Kafka 一些重要概念,讓大家對 Kafka 有個整體的認識和感知,後面還會詳細的解析每一個概念的作用以及更深入的原理 • Producer:消息生產者,向 Kafka Broker 發消息的客戶端。 • Consumer:消息消費者,從 Kafka Broker 取消息的客戶端。 • ...
  • 前面介紹了對稱加密演算法,本文將介紹密碼學中另一類重要應用:消息摘要(Digest),什麼是消息摘要?簡單的定義是:對一份數據,進行一個單向的Hash函數,生成一個固定長度的Hash值,這個值就是這份數據的摘要,也稱為指紋。 ...
  • 弟弟最近要考試,臨時抱佛腳在網上找了一堆學習資料複習,這不剛就來找我了,說PDF上有水印,影響閱讀效果,到時候考不好就怪資料不行,氣的我差點當場想把他揍一頓! 算了,弟弟長大了,看在打不過他的份上,就不打他了~ 稍加思索,我想起了Python不是可以去水印?說搞就搞! 去除水印原理 去除方法: 用 ...
  • 作者:陳昌浩 1 導讀 if…else…在代碼中經常使用,聽說可以通過Java 8的Function介面來消滅if…else…!Function介面是什麼?如果通過Function介面介面消滅if…else…呢?讓我們一起來探索一下吧。 2 Function介面 Function介面就是一個有且僅有 ...