JavaScript手写专题——图片懒加载、滚动节流、防抖手写

图片懒加载场景:在一些图片量比较大的网站(比如电商网站首页,或者团购网站、小游戏首页等),如果我们尝试在用户打开页面的时候,就把所有的图片资源加载完毕,那么很可能会造成白屏、卡顿等现象,因为图片真的太多了,一口气处理这么多任务,浏览器做不到啊!我们再想,用户真的需要这么多图片吗?不对,用户点开页面的瞬间,呈现给他的只有屏幕的一部分(我们称之为首屏)。只要我们可以在页面打开的时候把首屏的图片资源加载出来,用户就会认为页面是没问题的。至于下面的图片,我们完全可以等用户下拉的瞬间再即时去请求、即时呈现给他。这样一来,性能的压力小了,用户的体验却没有变差——这个延迟加载的过程,就是 Lazy-Load

搭建图片懒加载场景

通常我们访问网页的时候会出现页面的场景,没来得及被图片填充完全的网页,是用大大小小的空 div 元素来占位的。一旦我们通过滚动使得这个 div 出现在了可见范围内,那么 div 元素的内容就会发生变化

可以设置这样一个html页面

使用data-语法给img标签添加自定义属性,比如使用data-src给img预制一个属性,存储当前图片将要显示的图片路径。之后当元素在可视窗口时通过js将data-src替换给src属性。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Lazy-Load</title>
    <style>
      .container {
        display: flex;
        flex-wrap: wrap;
      }
      .img {
        width: 400px;
        height: 400px;
        margin: 10px;
        background: gray;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="img">
        <img alt="加载中1" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中2" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中3" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中4" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中5" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中6" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中7" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中8" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中9" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中10" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中11" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中12" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中13" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中14" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中15" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中16" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中17" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中18" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中19" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中20" class="pic" data-src="./images/image.avif" />
      </div>
    </div>
  </body>
</html>

懒加载计算滚动到可视窗口

在懒加载的实现中,有两个关键的数值:一个是当前可视区域的高度,另一个是元素距离可视区域顶部的高度

当前可视区域的高度, 在和现代浏览器及 IE9 以上的浏览器中,可以用 window.innerHeight 属性获取。在低版本 IE 的标准模式中,可以用 document.documentElement.clientHeight 获取,这里我们兼容两种情况:

const viewHeight = window.innerHeight || document.documentElement.clientHeight 

元素距离可视区域顶部的高度,我们这里选用 getBoundingClientRect() 方法来获取返回元素的大小及其相对于视口的位置。对此 MDN 给出了非常清晰的解释:

该方法的返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的 CSS 边框集合 。

DOMRect 对象包含了一组用于描述边框的只读属性——lefttoprightbottom,单位为像素。除了 widthheight 外的属性都是相对于视口的左上角位置而言的。

 lazyload方法

通过图片距离顶部的高度与内容区域的高度进行比较。如果图片没到可视区域,那么imgs[i].getBoundingClientRect().top将大于内容区域高度viewHight。如果图片在可视范围,那么viewHeight-imgs[i].getBoundingClientRect().top的差值将大于0

<script>
    // 获取所有的图片标签
    const imgs = document.getElementsByTagName('img')
    // 获取可视区域的高度
    const viewHeight = window.innerHeight || document.documentElement.clientHeight
    // num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
    let num = 0
    function lazyload(){
        for(let i=num; i<imgs.length; i++) {
            // 用可视区域高度减去元素顶部距离可视区域顶部的高度
            let distance = viewHeight - imgs[i].getBoundingClientRect().top
            // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
            if(distance >= 0 ){
                // 给元素写入真实的src,展示图片
                imgs[i].src = imgs[i].getAttribute('data-src')
                // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
                num = i + 1
            }
        }
    }
    // 监听Scroll事件
    window.addEventListener('scroll', lazyload, false);
</script>

加载效果图

可以看右侧img的src属性,一开始是没有的,只要alt图片占位。当鼠标滚动到可视区域时src的属性才被替换为真实图片地址。

 

需要注意的是,这个 scroll 事件,是一个危险的事件——它太容易被触发了。试想,用户在访问网页的时候,是不是可以无限次地去触发滚动?尤其是一个页面死活加载不出来的时候,疯狂调戏鼠标滚轮(或者浏览器滚动条)的用户可不在少数啊!

再回头看看我们上面写的代码。按照我们的逻辑,用户的每一次滚动都将触发我们的监听函数。函数执行是吃性能的,频繁地响应某个事件将造成大量不必要的页面计算。因此,我们需要针对那些有可能被频繁触发的事件作进一步地优化。这里就引出了两位主角——throttledebounce

 节流throttle优化懒加载

频繁触发回调导致的大量计算会引发页面的抖动甚至卡顿。为了规避这种情况,我们需要一些手段来控制事件被触发的频率。就是在这样的背景下,throttle(事件节流)和 debounce(事件防抖)出现了。

throttle 的中心思想在于:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。 

如果在delay秒后立即执行,可以使用时间戳判断。通过new Date方法获取当前时间戳,与上次执行的时间戳进行比较。如果差值超过了delay时间,那么执行fn。这里fn和last都是闭包的变量,throttle执行后,内部的function中仍然能够访问。

  function throttle(fn, delay) {
    let last = 0;
    return function () {
      let args = arguments;
      let now = +new Date();
      if (now - last >= delay) {
        fn.apply(this, args);
        last = now;
      }
    };
  }

 节流还可以通过定时器异步处理。这个方法要比上面慢,因为setTimeout是异步函数,不会在delay后立即执行,而是等待事件循环处理后执行。

  function throttle2(fn, delay) {
    let timer = null;
    return function () {
      let context = this;//记住this
      let args = arguments;//参数
      if (!timer) {
        timer = setTimeout(() => {
          fn.apply(context, args);//执行fn
          timer = null;
        }, delay);
      }
    };
  }

给鼠标滚动事件添加节流函数

  const throttleScroll = throttle(lazyLoad, 3000);
  window.addEventListener("scroll", throttleScroll);

节流效果

可以看到下方,虽然鼠标在滚动,但是页面还是延迟加载

实现防抖debounce

防抖的中心思想在于:我会等你到底。在某段时间内,不管你触发了多少次回调,我都只认最后一次。

 function debounce(fn, delay) {
    let timer = null;
    return function () {
      let context = this;
      let args = arguments;
      clearTimeout(timer); //每次都清空定时器
      timer = setTimeout(() => {
        //定时器执行fn
        fn.apply(context, args);
      }, delay);
    };
  }

 鼠标滚动添加防抖效果

  const debounceScroll = debounce(lazyLoad, 1000);
  window.addEventListener("scroll", debounceScroll);

防抖效果

鼠标疯狂滚动,当鼠标停下来的时候延迟delay加载图片

但是debouce有个问题,debounce 的问题在于它“太有耐心了”。试想,如果用户的操作十分频繁——他每次都不等 debounce 设置的 delay 时间结束就进行下一次操作,于是每次 debounce 都为该用户重新生成定时器,回调函数被延迟了不计其数次。频繁的延迟会导致用户迟迟得不到响应,用户同样会产生“这个页面卡死了”的观感。

为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应。这个 throttledebounce “合体”思路,已经被很多成熟的前端库应用到了它们的加强版 throttle 函数的实现中

有底线的防抖——防抖和节流结合体

delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应

function debounce2(fn, delay) {
    let last = 0;
    let timer = null;
    return function (...args) {
      const context = this;
      const now = +new Date();
      if (now - last < delay) {
        //防抖
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(context, args);
          last = now;
        }, delay);
      } else {
        fn.apply(context, args);
        last = now;
      }
    };
  }

 效果如下,即使鼠标滚动没有停止,到了指定时间一定会执行

throttledebounce 不仅是我们日常开发中的常用优质代码片段,更是前端面试中不可不知的高频考点。“看懂了代码”、“理解了过程”在本节都是不够的,重要的是把它写到自己的项目里去,亲自体验一把节流和防抖带来的性能提升。

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

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

相关文章

内网安全-隧道技术SSHDNSICMPSMB上线通讯LinuxMac 简单总结

第126天&#xff1a;内网安全-隧道技术&SSH&DNS&ICMP&SMB&上线通讯Linux&Mac_内网安全-隧道技术_ssh_dns_icmp_smb_上线通讯linux_mac-CSDN博客 内网渗透—隧道技术_隧道技术csdn-CSDN博客 #SMB 隧道&通讯&上线 判断&#xff1a;445 通讯 上…

Azure Windows2012升级2016

Azure Windows2012升级2016 在自己电脑配置Azure PowerShell前置条件PowerShell 登录到 Azure Azure 中运行 Windows Server 的 VM 的就地升级前置条件&#xff0c;生成一块OS磁盘将生成的OS磁盘附件到需升级的服务器执行就地升级到 Windows Server 2016 升级后配置故障恢复 在…

一觉醒来 AI科技圈发生的大小事儿 05月09日

&#x1f4f3;AlphaFold 3 重磅问世&#xff0c;全面预测蛋白质与所有生命分子相互作用及结构&#xff0c;准确性远超以往水平 Google DeepMind发布了AlphaFold3模型&#xff0c;能够联合预测蛋白质、核酸、小分子等复合物结构&#xff0c;准确性显著提高&#xff0c;对跨生物…

代码随想录算法训练营第36期DAY22

DAY22 654最大二叉树 自己做的时候忽略了&#xff1a;nums.length>1的题给条件。所以每次递归都要判断是否size()>1&#xff0c;不要空的。 /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *rig…

让数据更「高效」一点!IvorySQL在Neon平台上的迅速部署和灵活应用

IvorySQL本身就是一个100%兼容PostgreSQL最新内核的开源数据库系统&#xff0c;而Neon Autoscaling Platform通常支持多种数据库和应用程序。将IvorySQL集成到该平台后&#xff0c;可以进一步增强与其他系统和应用程序的兼容性&#xff0c;同时更全面的体验IvorySQL的Oracle兼容…

深入探究 Spring Boot Starter:从概念到实践

序言 Spring Boot Starter 是 Spring Boot 生态系统中的一个核心概念&#xff0c;它为开发者提供了一种便捷的方式来捆绑和配置应用程序所需的依赖项。本文将深入探讨 Spring Boot Starter 的概念、原理以及如何创建自定义的 Starter。 一、什么是 Spring Boot Starter Spri…

docker 安装elasticsearch8.X

docker 安装elasticsearch8.X 安装elasticsearch8.X前言安装elasticsearch安装elasticsearch-analysis-ik安装kibana 安装elasticsearch8.X 前言 由于需要安装elasticsearch、IK分词插件、kibana。所以需要保持这三者的版本一致性。 elasticsearch 8.12.2 kibana 8.12.2 ela…

科沃斯梦碎“扫地茅”,钱东奇跌落“风口”

昔日“扫地茅“不香了&#xff0c;科沃斯跌落神坛。 4月27日&#xff0c;科沃斯发布2023年报显示&#xff1a;2023年&#xff0c;科沃斯的营收为155.02亿元&#xff0c;同比增加1.16%&#xff1b;同期&#xff0c;净利为6.10亿元&#xff0c;同比减少63.96%。科沃斯的经营业绩…

Mysql数据在磁盘上的存储结构

一. 前言 一行数据的存储格式大致如下所示: 变长字段的长度列表&#xff0c;null值列表&#xff0c;数据头&#xff0c;column01的值&#xff0c;column02的值&#xff0c;column0n的值… 二. 变长字段 在MySQL里有一些字段的长度是变长的&#xff0c;是不固定的&#xff0c;…

可视化-实验五-Pyecharts工具包的使用及文本数据可视化

1.2.1 pyecharts的数据类型以及新的数据导入逻辑 由于pyecharts背后封装的js库&#xff0c;会涉及到数据类型转化。它暂时要求输入数据必须是python的基础数据类型&#xff0c;比如字符串&#xff0c;列表&#xff0c;字典&#xff0c;而不能是序列这样的数据类型。因此序列输入…

RockChip Android13 添加/删除ListPreference方法

概述: 本章将讲述在Android添加或删除ListPreference的几种方法,并以EthernetSettingsActivity为例,添加/删除一项ListPreference: 默认效果图: 添加后效果图: 方法一: 1、全部添加xml 在Activity类中使用addPreferencesFromResource()方法解析XML文件并添加Prefere…

Node.js安装与配置环境 v20.13.1(LTS)

1 下载 Node.js — Run JavaScript Everywhere LTS -- long-term support&#xff0c;长期维护版本 如果要下载其他版本在download里选择下载 2 安装 一路点击next&#xff0c;默认安装路径C:\Program Files\nodejs 3 环境变量配置 1&#xff09;Path环境变量增加nodejs安装…

艾体宝方案 | 加密USB金融解决方案

在现代金融行业中&#xff0c;保护敏感数据和合规性已成为至关重要的任务。为了帮助金融公司应对移动性风险和合规挑战&#xff0c;我们提供了一种高效的加密USB解决方案。 一、为什么金融公司需要加密USB解决方案 1、降低移动性风险 金融服务公司正在迅速过渡到一种模式&a…

将本地托管模型与 Elastic AI Assistant 结合使用的好处

作者&#xff1a;来自 Elastic James Spiteri, Dhrumil Patel 当今公共部门组织利用生成式人工智能解决安全挑战的一种方式。 凭借其筛选大量数据以发现异常模式的能力&#xff0c;生成式人工智能现在在帮助团队保护其组织免受网络威胁方面发挥着关键作用。 它还可以帮助安全专…

短信平台群发服务有什么优点

短信平台群发服务有什么优点 提高营销效率 短信平台群发服务利用自动化技术&#xff0c;可以帮助企业迅速向大量潜在客户营销信息。相比传统的逐一方式&#xff0c;群发服务可以同时大批目标客户&#xff0c;大大提高了营销效率。企业可以轻松地在短时间内覆盖更多的潜在客户&…

JavaSE——异常(2/2)-异常的处理(记录异常并提示 、尝试重新修复)

目录 记录异常并提示 案例演示 流程解析 写法优化 尝试重新修复 开发中对于异常的常见处理方式 一层一层往上抛出异常&#xff0c;并且在最上层捕获异常&#xff0c;分为两种不同的处理方式。 例如&#xff0c;B站网页报错就是采取的第一种方式&#xff1a; 记录异常并…

linux 性能监控命令之dstat

1. dstat 系统默认为安装&#xff0c;直接安装阿里源后&#xff0c;yum install -y dstat安装即可&#xff0c;该命令整合了 vmstat &#xff0c; iostat 和 ifstat&#xff0c;我们先看下效果&#xff1a; 我们先看看具体参数&#xff1a; [rootk8s-master ~]# dstat --help …

C++STL初阶(1):string的使用及初阶原理

此文作为学习stl的笔记&#xff0c;许多普及、概念性的知识点将不再罗列&#xff08;如stl的发展、背景等&#xff09; 便于读者作为复习等方法了解。 0.STL简介&#xff08;笔记向&#xff09; STL不是祖师爷本贾尼实现的&#xff0c;是在惠普实验室中实现的。其作为一个数据结…

加密“发射台”:未来通信的新模式

随着区块链技术的飞速发展&#xff0c;加密“发射台”作为一种新兴的安全通信工具&#xff0c;正逐渐受到关注。本文将从专业角度深入探讨加密“发射台”的概念、原理、应用场景及其未来发展趋势&#xff0c;以期为读者提供有深度和逻辑性的思考。 一、加密“发射台”的概念与…

开源项目介绍-02 Aubio【1】环境配置和使用 @ Ubuntu + Pycharm + Python

前言&#xff1a; aubio 是一组算法和工具&#xff0c;用于标记和变换音乐和声音。它扫描或监听音频信号&#xff0c;并尝试识别音乐事件。例如&#xff0c;当鼓被击打时&#xff0c;它能检测到音符的频率&#xff0c;或者一个有节奏的旋律的节拍是多少。 aubio 的功能包括&a…