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

news2025/2/9 1:39:21

思路

  • 页面 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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python】前方弹幕高能:教你一键实现自动化指定直播间发送弹幕,为你喜欢的女主播疯狂打call叭~

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 不知道你们平时看不看直播&#xff0c;每次看到界面中的滚动弹幕&#xff0c;还挺有意思。 无中生事—— 前几天看到喜欢的主播打比赛呢~ 精彩时刻恨不得双手打字打call的飞快&#xff0c;那激动的无语轮次了都&#…

Android生态下的Kotlin有哪些更新#GoogleIO 2023

Android生态下Kotlin有哪些更新#GoogleIO 2023 自Android官方宣布Kotlin作为Android开发的第一语言&#xff0c;过去将近6年时间。下面是GoogleIO 2023宣布的Android生态的4项重要更新。 Kotlin编译器2.0版本 如果你是一位Kotlin开发者&#xff0c;你可能会对听到这个消息感…

PMP®增持CSPM-2等级证书,免重新考试与学习,申请简单!

好消息&#xff01;好消息&#xff01;好消息&#xff01; 2023年起&#xff0c;持有PMP证书的朋友可以直接增持一个同等级证书CSPM-2&#xff0c;不用重新考试&#xff0c;不用重新学习&#xff0c;原PMP证书不影响正常使用&#xff0c;相当于多了一个国标项目管理领域的证书…

前端和后端分别是什么?

从技术工具来看&#xff1a; 前端&#xff1a;常见的 html5、JavaScript、jQuery... 后端&#xff1a;spring、tomcet、JVM&#xff0c;MySQL... 毕竟&#xff0c;如果这个问题问一个老后端&#xff0c;他掰掰手指可以给你罗列出一堆的名词来&#xff0c;比如设计模式、数据库…

Golang gin middleware的编写与使用 context.Next函数

中间件 在web应用服务中&#xff0c;完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。 在实际的业务开发和处理中&#xff0c;会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度…

【软件分析/静态分析】chapter3 课程03/04 数据流分析的应用(Data Flow Analysis)

&#x1f517; 课程链接&#xff1a;李樾老师和谭天老师的&#xff1a;南京大学《软件分析》课程03&#xff08;Data Flow Analysis I&#xff09;_哔哩哔哩_bilibili 南京大学《软件分析》课程04&#xff08;Data Flow Analysis II&#xff09;_哔哩哔哩_bilibili 这篇文章总结…

识别一切模型RAM(Recognize Anything Model)及其前身 Tag2Text 论文解读

img 总览 大家好&#xff0c;我是卷了又没卷&#xff0c;薛定谔的卷的AI算法工程师「陈城南」~ 担任某大厂的算法工程师&#xff0c;带来最新的前沿AI知识和工具&#xff0c;欢迎大家交流~ 继MetaAI 的 SAM后&#xff0c;OPPO 研究院发布识别一切模型&#xff08;Recognize Any…

MySQL如何保证数据的可靠性(保证数据不丢失)

1. 结论&#xff1a; 只要redo log 和 binlog 保证持久化到磁盘&#xff0c;就能确保MySQL异常重启后&#xff0c;数据可以恢复。 2. 机制 WAL机制&#xff0c;&#xff08;Write Ahead Log&#xff09;&#xff1a; 事务先写入日志&#xff0c;后持久化到磁盘。 3. binlog…

华为OD机试真题 JavaScript 实现【非严格递增连续数字序列】【2022Q4 100分】

一、题目描述 输入一个字符串仅包含大小写字母和数字&#xff0c;求字符串中包含的最长的非严格递增连续数字序列的长度&#xff0c;比如122889属于非严格递增连续数字序列。 二、输入描述 输入一个字符串仅包含大小写字母和数字&#xff0c;输入的字符串最大不超过255个字符…

合金氢化物动力学与瞬时流量计算

在经典的合金氢化物动力学描述中&#xff0c;有一种是用JMAK方程来描述和拟合合金的吸放氢过程&#xff0c;方程很简洁&#xff1a;&#xff0c;其中是反应程度或者百分比&#xff0c;表示合金氢化物吸氢或者放氢的程度&#xff0c;是该合金吸氢或放氢的一种特征常数&#xff0…

57、基于51单片机智能硬币分拣分类机电子存钱罐报警系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+参考PPT+元器件清单等)

摘 要 近年来&#xff0c;随着我国经济的发展和社会的进步&#xff0c;邮政事业得到了空前发展。邮政通信网的技术含量不断增加&#xff0c;技术装备水平也在不断的提高&#xff0c;邮件处理已基本实现机械化&#xff0c;并且朝着自动化的方向迈进。本文着眼于我国当前邮政事…

Unity编辑器扩展-第一集-在菜单栏加入自己的按钮

一、概述 unity自己本身就是一个大的程序&#xff0c;我们看见的所有功能&#xff0c;都是用程序写出来的&#xff0c;但是根据各行各业不同的需求&#xff0c;有些时候我们制作时&#xff0c;想要自己编辑一些原有的功能。 二、本节目标效果展示 1.在菜单栏加入属于自己的一…

【前端基础篇】CSS选择器 和 CSS属性

前言&#xff1a;CSS 简介 CSS 概述 CSS ( Cascading Style Sheet ) 层叠样式表&#xff0c;用来修饰 HTML&#xff0c;使得效果更加多样化CSS 在 HTML4.0 中引入&#xff0c;一般在开发过程中&#xff0c;会使用单独的 CSS 文件进行开发&#xff0c;然后将这个独立 CSS 文件引…

Unity编辑器扩展-第二集-按钮排序/分组/放入右键菜单

第一集链接&#xff1a;Unity编辑器扩展-第一集-在菜单栏加入自己的按钮_菌菌巧乐兹的博客-CSDN博客 一、本节目标效果展示 1.按钮排序 变成 2.按钮分组 仔细看&#xff0c;有个灰色的杠杠 3.放入右键菜单 4.皮一下 二、按钮排序具体流程 第一集讲&#xff0c;如果想放入…

Java自定义泛型类、泛型接口、泛型方法以及 泛型擦除的细节

体会&#xff1a;使用泛型的主要优点是能够在编译时而不是在运行时检测错误。 /*** 自定义泛型类*/ public class Order<T> {String orderName;int orderId;//类的内部结构就可以使用类的泛型T orderT;public Order(){//编译不通过 // T[] arr new T[10];//编译…

Unity基础3——Resources资源动态加载

一、特殊文件夹 &#xff08;一&#xff09;工程路径获取 // 注意 该方式 获取到的路径 一般情况下 只在 编辑模式下使用 // 我们不会在实际发布游戏后 还使用该路径 // 游戏发布过后 该路径就不存在了 print(Application.dataPath);&#xff08;二&#xff09;Resources 资…

如何自动生成正交法测试用例?

目录 引言 正交法实验 自动生成正交用例 引言 正交法测试用例是一种高效且可靠的方法&#xff0c;能够最大限度地减少测试工作量&#xff0c;同时保证覆盖所有可能的组合情况。通过了解如何优化这些测试用例的生成过程&#xff0c;可以提高产品的质量&#xff0c;降低故障率…

机器人视觉梳理(上)

原创 | 文BFT机器人 01 机器人视觉的概念 在智能制造过程中&#xff0c;通过传统的编程来执行某一特定动作的机器人&#xff08;机械手、机械手臂、机械臂等&#xff0c;未作特殊说明时&#xff0c;不作严格区分&#xff0c;统一称为机器人&#xff09;&#xff0c;将难以满足制…

【CV大模型SAM(Segment-Anything)】如何保存分割后的对象mask?并提取mask对应的图片区域?

上一篇文章【CV大模型SAM&#xff08;Segment-Anything&#xff09;】真是太强大了&#xff0c;分割一切的SAM大模型使用方法:可通过不同的提示得到想要的分割目标中详细介绍了大模型SAM&#xff08;Segment-Anything&#xff09;的不同使用方法&#xff0c;后面有很多小伙伴给…

【JVM 监控工具】使用JConsole监控进程、线程、内存、cpu、类情况

文章目录 前言一、如何启动JConsole二、如何设置JAVA程序运行时可以被JConsolse连接分析三、JConsole如何连接远程机器的JAVA程序&#xff08;举例说明&#xff09;四、性能分析概述内存线程类VM摘要MBean 五、使用Jconsole监控某方法的性能总结 前言 Jconsole是JDK自带的监控…