JS + 浮动 + 递归实现图片瀑布流懒加载

思路

  • 页面 pege 分成左浮动的数列 lineBox,每列中图片 sinImg 依次向下摆放
  • 每加载一张图片时,判断页面中哪列的高度最小,将当前图片放到最小的那列尾部
  • 监听当前图片 onload 事件,当前图片加载完成后,再加载下一张图片
  • 等待上一张图片加载完的事件,可以用递归完成

实现

初步搭建瀑布流

  • DOM 结构
<div class="page">
  <div class="floatBox clearfix" ref="floatBox">
    <div
      class="lineBox"
      v-for="(item, index) in imgList"
      :key="index"
      ref="lineBox"
    >
      <div
        class="sinImg"
        v-for="(itemImg, indexImg) in item"
        :key="indexImg"
        ref="sinImg"
      >
        <img :src="itemImg.url" alt="" />
        <div class="desc">{{ itemImg.desc }}</div>
      </div>
    </div>
  </div>
</div>
<style lang="scss" scoped>
.page {
  height: 100%;
  .floatBox {
    background: #f5f5f5;
    padding: 20px;
    height: 100%;
    box-sizing: border-box;
    overflow-y: auto;
    .lineBox {
      float: left;
      margin-right: 10px;
      &:last-child {
        margin-right: 0;
      }
      .sinImg {
        width: 200px;
        margin-bottom: 10px;
        background: #fff;
        border-radius: 8px;
        overflow: hidden;
        img {
          width: 100%;
          height: auto;
        }
        .desc {
          width: 100%;
          line-height: 30px;
          padding: 10px;
          box-sizing: border-box;
        }
      }
    }
  }
}
.clearfix:after {
  content: "";
  display: block;
  clear: both;
  visibility: hidden;
}
</style>
  • 先加载每列的第一张图
data() {
  return {
    randomImg: [
      require("@I/float1.png"),
      require("@I/float2.png"),
      require("@I/float3.png"),
      require("@I/float4.png"),
      require("@I/float5.png"),
      require("@I/float6.png"),
    ],
    randomDesc: [
      "描述信息,描述信息描述信息,描述信息。",
      "描述信息描述信息,描述信息,描述信息描述信息描述信息描述信息描述信息。",
      "描述信息描述信息描述信息。",
      "描述信息,描述信息描述信息,描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息描述信息。",
      "描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
      "描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
    ],
    imgList: [], // 图片数组
    split: 3, // 自定义页面分成几列
  };
},
mounted() {
  this.initImg();
},
methods: {
  // 初次进入页面,初始化瀑布流
  initImg() {
    this.getImgList(this.split).then((res) => {
      for (var i = 0; i < this.split; i++) {
        this.imgList[i] = [res[i]];
      }
      this.$forceUpdate();
    });
  },
  // 模拟接口请求数据
  getImgList(pageSize) {
    return new Promise((resolve, reject) => {
      let imgList = [];
      for (var i = 0; i < pageSize; i++) {
        let random1 = Math.floor(Math.random() * (0 - 6) + 6);
        let random2 = Math.floor(Math.random() * (0 - 6) + 6);
        imgList.push({
          url: this.randomImg[random1],
          desc: this.randomDesc[random2],
        });
      }
      setTimeout(() => {
        resolve(imgList);
      }, 500);
    });
  },
},

在这里插入图片描述

判断列高添加图片

  • 遍历数据返回结果数组
  • 判断当前页面中,所有列盒子 lineBox 的高度,选取最低的那一列,将当前遍历的数据结果添加到此列
  • 遍历时,使用定时器 setTimeout,相当于将执行函数按顺序放到队列中,后续会按照此顺序依次执行
// 初次进入页面,初始化瀑布流
initImg() {
  this.getImgList(this.split).then((res) => {
    for (var i = 0; i < this.split; i++) {
      this.imgList[i] = [res[i]];
    }
    this.$forceUpdate();
    this.loadMoreImg();
  });
},
// 加载更多图片
loadMoreImg() {
  this.getImgList(20).then((res) => {
    // 第一种方法,简单轮询,放到队列中依次执行
    res.forEach((item, index) => {
      setTimeout(() => {
        this.pushImg(item);
      }, 0);
    });
  });
},
// 检索高度最小的列,将新数据添加到最小列中
pushImg(item) {
  let lineBoxArr = this.$refs.lineBox; // 列盒子
  let minBoxIndex = 0; // 最小高度盒子的索引
  let minBox = 0; // 最小高度盒子的高度
  lineBoxArr.forEach((item, index) => {
    if (!minBox) {
      // 之前未暂存过最小高度
      minBox = item.offsetHeight;
      minBoxIndex = index;
    } else if (item.offsetHeight < minBox) {
      // 当前元素的高度比之前暂存的还小
      minBox = item.offsetHeight;
      minBoxIndex = index;
    }
  });
  this.imgList[minBoxIndex].push(item); // 将当前元素塞到最小高度盒子中
  this.$forceUpdate();
},

在这里插入图片描述

等待每张图片的 onload 事件

  • 上述方法,队列在依次执行的时候,如果图片较大或者网络环境较差,可能会发生错列的情况。即在上一张图片未完全加载完成时,下一张图片加载前判断列高度最小值不准确的情况。

在这里插入图片描述

  • 加入递归思路,请求到结果后,将结果数组赋值给一个递归数组 circleImgList
  • 递归方法中,每次将 circleImgList 的第一项加入到最小列中,并且等待当前 item 的图片加载完后,移除递归数组 circleImgList 的第一项,再调用下次方法,直到递归数组被移除成空数组
// 递归操作-判断最低的一列,将当前图片塞到最低列中
circleFunc() {
  if (this.circleImgList && this.circleImgList.length > 0) {
    // 待摆放的图片数组
    // 检索高度最小的列,将新数据添加到最小列中
    let lineBoxArr = this.$refs.lineBox; // 获取瀑布流盒子容器-多列
    let minBoxIndex = 0; // 用来记录要插入图片的盒子索引
    let minBox = 0; // 用来记录盒子高度最小值
    lineBoxArr.forEach((item, index) => {
      if (!minBox) {
        // 之前未暂存过最小高度
        minBox = item.offsetHeight;
        minBoxIndex = index;
      } else if (item.offsetHeight < minBox) {
        // 当前元素的高度比之前暂存的还小
        minBox = item.offsetHeight;
        minBoxIndex = index;
      }
    });
    this.imgList[minBoxIndex].push(this.circleImgList[0]);
    this.$forceUpdate();
    this.$nextTick(() => {
      let imgNode = lineBoxArr[minBoxIndex].lastChild.children[0];
      imgNode.onload = () => {
        this.consoleTimeStr("当前图片加载完了,进行下一次", "blue");
        this.circleImgList.shift();
        this.circleFunc();
      };
    });
  }
},
// 打印方法
consoleTimeStr(flag, color) {
  let time = new Date();
  let minute = time.getMinutes();
  let second = time.getSeconds();
  let millSecond = time.getMilliseconds();
  if (minute < 10) {
    minute = "0" + minute;
  }
  if (second < 10) {
    second = "0" + second;
  }
  let str = minute + ":" + second + ":" + millSecond;
  console.log("%c" + flag + ",当前时间:" + str, "color: " + color + ";");
},

在这里插入图片描述

完善

  • 监听瀑布流盒子 floatBox 的滚动事件,当滚动到底部时,加载下一次数据
  • 添加一个 loading 开关,等待当前数据加载完毕,再执行滚动加载事件

在这里插入图片描述

  • 完整代码
<template>
  <div class="page">
    <div class="floatBox clearfix" ref="floatBox">
      <div
        class="lineBox"
        v-for="(item, index) in imgList"
        :key="index"
        ref="lineBox"
      >
        <div
          class="sinImg"
          v-for="(itemImg, indexImg) in item"
          :key="indexImg"
          ref="sinImg"
        >
          <img :src="itemImg.url" alt="" />
          <div class="desc">{{ itemImg.desc }}</div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      randomImg: [
        require("@I/float1.png"),
        require("@I/float2.png"),
        require("@I/float3.png"),
        require("@I/float4.png"),
        require("@I/float5.png"),
        require("@I/float6.png"),
      ],
      randomDesc: [
        "描述信息,描述信息描述信息,描述信息。",
        "描述信息描述信息,描述信息,描述信息描述信息描述信息描述信息描述信息。",
        "描述信息描述信息描述信息。",
        "描述信息,描述信息描述信息,描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息描述信息。",
        "描述信息描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
        "描述信息描述信息,描述信息描述信息描述信息描述信息描述信息。",
      ],
      imgList: [], // 图片数组
      split: 3, // 自定义页面分成几列
      circleImgList: [], // 递归图片数组
      timmer: null,
      loading: false,
    };
  },
  mounted() {
    this.initImg();
  },
  methods: {
    // 初次进入页面,初始化瀑布流
    initImg() {
      this.getImgList(this.split).then((res) => {
        for (var i = 0; i < this.split; i++) {
          this.imgList[i] = [res[i]];
        }
        this.$forceUpdate();
        let floatBox = this.$refs.floatBox;
        floatBox.addEventListener("scroll", (e) => {
          this.scrollFunc();
        });
        this.loadMoreImg();
      });
    },
    // 加载更多图片
    loadMoreImg() {
      this.loading = true;
      this.getImgList(20).then((res) => {
        // 第一种方法,简单轮询,放到队列中依次执行
        // res.forEach((item, index) => {
        //   setTimeout(() => {
        //     this.pushImg(item);
        //   }, 0);
        // });
        // 第二种方法,递归
        this.circleImgList = JSON.parse(JSON.stringify(res));
        this.circleFunc();
      });
    },
    // 模拟接口请求数据
    getImgList(pageSize) {
      return new Promise((resolve, reject) => {
        let imgList = [];
        for (var i = 0; i < pageSize; i++) {
          let random1 = Math.floor(Math.random() * (0 - 6) + 6);
          let random2 = Math.floor(Math.random() * (0 - 6) + 6);
          imgList.push({
            url: this.randomImg[random1],
            desc: this.randomDesc[random2],
          });
        }
        setTimeout(() => {
          resolve(imgList);
        }, 500);
      });
    },
    // 监听滚动事件
    scrollFunc() {
      this.throttle(() => {
        let floatBox = this.$refs.floatBox;
        let scrollTop = floatBox.scrollTop; // 被监听元素卷曲出去的距离
        let clientHeight = floatBox.clientHeight; // 可视区域的高度
        let scrollHeight = floatBox.scrollHeight; // 被监听元素的高度
        if (scrollTop * 1 + clientHeight * 1 >= scrollHeight) {
          this.consoleTimeStr("滚动到底部了", "red");
          if (!this.loading) {
            this.loadMoreImg();
          }
        }
        this.timmer = null;
      }, 200);
    },
    // 节流函数
    throttle(func, delay) {
      return (() => {
        if (this.timmer) {
          return;
        } else {
          this.timmer = setTimeout(func, delay);
        }
      })();
    },
    // 检索高度最小的列,将新数据添加到最小列中
    pushImg(item) {
      let lineBoxArr = this.$refs.lineBox;
      let minBoxIndex = 0;
      let minBox = 0;
      lineBoxArr.forEach((item, index) => {
        if (!minBox) {
          // 之前未暂存过最小高度
          minBox = item.offsetHeight;
          minBoxIndex = index;
        } else if (item.offsetHeight < minBox) {
          // 当前元素的高度比之前暂存的还小
          minBox = item.offsetHeight;
          minBoxIndex = index;
        }
      });
      this.imgList[minBoxIndex].push(item);
      this.$forceUpdate();
    },
    // 递归操作-判断最低的一列,将当前图片塞到最低列中
    circleFunc() {
      if (this.circleImgList && this.circleImgList.length > 0) {
        // 待摆放的图片数组
        // 检索高度最小的列,将新数据添加到最小列中
        let lineBoxArr = this.$refs.lineBox; // 获取瀑布流盒子容器-多列
        let minBoxIndex = 0; // 用来记录要插入图片的盒子索引
        let minBox = 0; // 用来记录盒子高度最小值
        lineBoxArr.forEach((item, index) => {
          if (!minBox) {
            // 之前未暂存过最小高度
            minBox = item.offsetHeight;
            minBoxIndex = index;
          } else if (item.offsetHeight < minBox) {
            // 当前元素的高度比之前暂存的还小
            minBox = item.offsetHeight;
            minBoxIndex = index;
          }
        });
        this.imgList[minBoxIndex].push(this.circleImgList[0]);
        this.$forceUpdate();
        this.$nextTick(() => {
          let imgNode = lineBoxArr[minBoxIndex].lastChild.children[0];
          imgNode.onload = () => {
            this.consoleTimeStr("当前图片加载完了,进行下一次", "blue");
            this.circleImgList.shift();
            this.circleFunc();
          };
        });
      } else {
        this.loading = false;
      }
    },
    // 打印方法
    consoleTimeStr(flag, color) {
      let time = new Date();
      let minute = time.getMinutes();
      let second = time.getSeconds();
      let millSecond = time.getMilliseconds();
      if (minute < 10) {
        minute = "0" + minute;
      }
      if (second < 10) {
        second = "0" + second;
      }
      let str = minute + ":" + second + ":" + millSecond;
      console.log("%c" + flag + ",当前时间:" + str, "color: " + color + ";");
    },
  },
};
</script>
<style lang="scss" scoped>
.page {
  height: 100%;
  .floatBox {
    background: #f5f5f5;
    padding: 20px;
    height: 100%;
    box-sizing: border-box;
    overflow-y: auto;
    .lineBox {
      float: left;
      margin-right: 10px;
      &:last-child {
        margin-right: 0;
      }
      .sinImg {
        width: 200px;
        margin-bottom: 10px;
        background: #fff;
        border-radius: 8px;
        overflow: hidden;
        img {
          width: 100%;
          height: auto;
        }
        .desc {
          width: 100%;
          line-height: 30px;
          padding: 10px;
          box-sizing: border-box;
        }
      }
    }
  }
}
.clearfix:after {
  content: "";
  display: block;
  clear: both;
  visibility: hidden;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/28690.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣题库刷题笔记5--最长回文子串

1、题目如下&#xff1a; 2、个人Python代码实现&#xff1a; 首先想到的是通过类似冒泡排序的方式进行切片&#xff0c;然后判断切片的子字符串是否为回文字符串&#xff0c;然后记录出最长的回文字符串&#xff0c;代码如下&#xff1a; 可以看到&#xff0c;通过切片的方式&…

剑指offer35 复杂链表的复制

复杂链表的复制 文章目录 复杂链表的复制方法一 回溯哈希表第二种解释 方法二&#xff1a;拼接拆分算法流程 参考文献 本题要求我们对一个复杂链表进行复制。在复杂链表中&#xff0c;每个节点除了有一个next指针指向下一个节点&#xff0c;还有一个random指针指向链表中的任意…

嵌入式容器源码解析

问题分析 不同于使用springmvc,在我们使用springboot时无需配置tomcat就可以直接使用&#xff0c;这就说明springboot已经在我们启动项目时将tomcat配置好了&#xff0c;接下来我们就来看看springboot底层是怎么实现的。 源码解析 ServletWebServerFactoryAutoConfiguratio…

Python 标准库 - 并发执行

Python 标准库 - 并发执行 1. 简单介绍2. 程序示例2.1 threading 编程示例2.2 multiprocessing 编程示例2.3 concurrent.futures 编程示例 1. 简单介绍 Python 标准库 非常庞大&#xff0c;所提供的组件涉及范围十分广泛&#xff0c;官方参考链接https://docs.python.org/zh-cn…

unity 3d实现下雨、雾气、萤火虫和火花四溅的粒子效果

文章目录 先看最终效果1. 下雨效果2. 雾气效果3. 萤火虫和火花四溅的效果 3d下雨粒子效果涟漪效果雨滴和涟漪效果结合水花效果雨滴涟漪水花结合问题雾气效果萤火虫火花效果萤火虫和火花效果结合完毕 先看最终效果 1. 下雨效果 2. 雾气效果 3. 萤火虫和火花四溅的效果 3d下雨粒…

函数栈帧的创建与销毁

函数栈帧的创建与销毁 前言认识相关寄存器认识相关汇编命令详解思路图 前言 函数栈帧的创建与销毁在不同编译器下&#xff0c;函数调用过程中栈帧的创建略有差异&#xff0c;具体细节取决于编译器的实现&#xff0c;但大体逻辑是一致的。&#xff08;在使用编译器时&#xff0…

某游戏登录密码加密,webpack

注意&#xff1a;文章内容仅用于学习和技术交流&#xff0c;切勿做出违法的事情&#xff0c;如有侵权请联系我删除。 网址&#xff08;今天的大冤种&#xff09;&#xff1a;aHR0cHM6Ly93d3cuZ205OS5jb20v 一&#xff0c;分析 从上面图片可以看到&#xff0c;他的密码是加密了…

桥接模式(十)

不管怎么样&#xff0c;都要继续充满着希望 上一章简单介绍了适配器模式(九), 如果没有看过, 请观看上一章 一. 桥接模式 引用 菜鸟教程里面的 桥接模式介绍: https://www.runoob.com/design-pattern/bridge-pattern.html 桥接&#xff08;Bridge&#xff09;是用于把抽象化…

谷粒商城p46-配置网关路由与路径重写

软件 &#xff1a; vscode idea 服务&#xff1a; renren-fast&#xff0c;gulimall-product&#xff0c;gulimall-gateway、nacos 前提条件&#xff1a; gateway、renren-fast已经注册到nacos 注意&#xff1a; 1、renren-fast单独注入nacos依赖&#xff0c;不要注入common…

#2023开放原子全球开源峰会之旅

#2023我在开源峰会 2023开放原子全球开源峰会参会指南 嗨咯&#xff0c;大家好&#xff01; 6月11号&#xff0c;是一年一度的开放原子大会&#xff0c;有幸参加&#xff0c;很开心&#xff01; 文章目录 1、逛展区&#xff08;领周边&#xff09;环节1.1 CSDN展区1.2 阿里云 …

ansible的部署和命令模块

一、 ansible 的概述 1、ansible简介 Ansible是一款为类Unix系统开发的自由开源的配置和自动化工具。 它用Python写成&#xff0c;类似于saltstack和Puppet&#xff0c;但是有一个不同和优点是我们不需要在节点中安装任何客户端。 它使用SSH来和节点进行通信。Ansible基于 …

(九)CSharp-数组

一、矩形数组 1、访问数组元素 class Program{static void Main(string[] args){int[] intArr1 new int[15];intArr1[2] 10;int var1 intArr1[2];int[,] intArr2 new int[5, 10];intArr2[2, 3] 7;int var2 intArr2[2, 3];int[] myIntArray new int[4];for (int i 0; i…

计算字母出现次数【存在括号计算】

计算字母出现次数【存在括号计算】 此代码考虑到了本问题的大多可能情况&#xff0c;闲话少述&#xff0c;代码中的注释很丰富。 代码绝对可以解决你的问题&#xff01; 不行你就评论&#xff0c;回复速度超快 作者时间YaoChongChong2023年6月14日10&#xff1a;40 Descript…

【gcc, cmake, eigen, opencv,ubuntu】五.CMakeLists.txt编写

文章目录 CMakeLists.txt编写1.CMakeLists.txt模板2.设置编程语言版本3.设置编译类型Debug&#xff0c;Release4.设置获取文件列表5.添加include目录6.配置编译选项 CMakeLists.txt编写 1.CMakeLists.txt模板 一个使用opencv 的 CMakeLists.txt # cmake最低版本要求 cmake_m…

该怎么学Python?自学Python的方法和资料整理!

导语 Python 作为一门简洁、易学且功能强大的编程语言&#xff0c;备受程序员和初学者的喜爱。如果你也想学习 Python&#xff0c;但不知从何入手&#xff0c;本文将为你整理一些自学 Python 的方法&#xff0c;助你快速入门并掌握这门语言。 为什么学习Python&#xff1f;&a…

requests库的使用

文章目录 get 请求post 请求get 请求和 post 请求的区别response1. res.headers2. status_code3. json get 请求 参数类型作用urlstr发起请求的地址params字典url为基准地址&#xff0c;不包含查询参数&#xff1b;使用此参数会自动对 params 字典编码&#xff0c;然后和url拼…

函数参数的拓展

函数参数的默认值 C 中可以在函数声明时为参数提供一个默认值 当函数调用时没有提供默认参数的值&#xff0c;则使用默认值 参数的默认值必须在函数声明中指定 当函数声明时没有出现参数的默认值&#xff0c;而定义的时候出现参数的默认值&#xff0c;编译器会报错 当函数声…

看了这几个C语言例子,你一定和我一样连说5个卧槽,声音一次比一次大

曾经我一直以为自己C语言学的还挺好的&#xff0c;直到看到这几个例子。 例1 首先来看一下&#xff0c;大师是如何求圆周率的&#xff0c;一口君实在词穷&#xff0c;first卧槽。 #include <stdio.h>long a10000,b0,c10000,d,e,f[10001],g;void main(){for(;b ! c; f[…

nginx的安装及代理和负载均衡设置

一、通过yum方式进行安装 官网参考地址&#xff1a;https://nginx.org/en/linux_packages.html#RHEL 1.1 安装好依赖 执行下面的命令安装 sudo yum install yum-utils1.2、 先配置好yum源 新建文件/etc/yum.repos.d/nginx.repo&#xff0c;文件内容&#xff1a; [nginx-s…

Spark SQL数据源:Hive表

文章目录 一、Spark SQL支持读写Hive二、Spark配置hive-site.xml三、准备工作&#xff08;一&#xff09;启动Hive的metastore&#xff08;二&#xff09;启动Spark Shell 四、Spark读写Hive数据&#xff08;一&#xff09;导入SparkSession&#xff08;二&#xff09;创建Spar…