导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

图片懒加载

方案

  1. 默认给图片列表中所有图片容器添加上 .lazy 类标识,用来识别当前容器图片未被真正加载
  2. 获取到数据后根据 template 渲染出图片列表,并把真实图片资源url设置到 data-url
  3. 监听页面 onload 和 onscroll 事件
  4. 当事件触发,循环图片列表(带 .lazy 标识的)判断每个图片是否出现在屏幕中
  5. 如果出现,则把 data-url 真实url设置到图片的 src 属性上
  6. 删除 data-url.lazy 标识
  7. 为了避免 onscroll 频繁触发判断函数,加上 throttle 节流

实现

lazy.png

Untitled

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图片懒加载</title>
  <link rel="stylesheet" href="index.css">
</head>
<body>
  <div class="container">
  </div>
  <!-- 模板 -->
  <script type="text/template" id="tpl">
    <div class="img-view lazy">
      <img src="./lazy.png" alt="{{title}}" data-url="{{url}}">
      <p>{{title}}</p>
    </div>
  </script>
  <script src="<https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js>"></script>
  <script src="utils.js"></script>
  <script src="index.js"></script>
</body>
</html>

utils.js 工具函数

function debounce(fn, delay, triggerNow) {
  let timer = null;
  return function() {
    if (timer) window.clearTimeout(timer);
    if (triggerNow) {
      let exec = !timer;
      timer = setTimeout(() => {
        timer = null;
      }, delay);
      if (exec) {
        fn.apply(this, arguments);
      }
    } else {
      timer = setTimeout(() => {
        fn.apply(this, arguments);
        timer = null;
      }, delay);
    }
  }
}

function throttle(fn, delay) {
  let canUse = true;
  return function() {
    if (canUse) {
      fn.apply(this, arguments);
      canUse = false;
      setTimeout(() => {
        canUse = true;
      }, delay);
    }
  }
}

index.js 核心实现

;(function() {
  let oContainer = document.querySelector('.container'),
      photos = [],
      tpl = document.getElementById('tpl').innerHTML;
      console.log(tpl);

  function init() {
    // 获取数据
    getData(() => {
      oContainer.innerHTML = renderList();
      imgLazyLoad();
    });
    // 绑定事件
    bindEvent();
  }

  function bindEvent() {
    // 监听页面加载完+滚动事件
    // 注意 throttle 节流
    window.onload = window.onscroll = throttle(imgLazyLoad, 500);
  }

  // 判断图片是否需要被加载
  function imgLazyLoad() {
    // 图片 .img-view 容器默认加上 lazy class 用来表示当前容器中图片需要被加载
    var oList = document.querySelectorAll('.img-view.lazy'),
        cHeight = document.documentElement.clientHeight,
        sTop = document.documentElement.scrollTop || document.body.scrollTop;
    console.log("剩余需懒加载图片个数:", oList.length);
    oList.forEach(item => {
      let img = item.getElementsByTagName('img')[0];
      let dataUrl = img.getAttribute('data-url');
      // 如果图片 出现在了屏幕内
      // 则把真实图片url从属性"data-url"中取出设置到src
      // 再删除 "data-url" 属性和 img-view 容器上的 lazy 标识
      if (item.offsetTop < cHeight + sTop) {
        img.setAttribute('src', dataUrl);
        img.removeAttribute('data-url');
        item.classList.remove('lazy');
      }
    });
  }

  // 获取mock数据
  function getData(cb) {
    axios.get('<https://jsonplaceholder.typicode.com/photos>').then(res => {
      photos = res.data;
      cb && cb();
    });
  }

  // 解析模板,绑定数据,生成字符串列表
  function renderList() {
    let list = '';
    photos.forEach(photo => {
      list += tpl.replace(/\\{\\{(.*?)\\}\\}/g, (node, key) => {
        return {
          url: photo.url,
          title: photo.title
        }[key];
      });
    });
    return list;
  }

  init();
})();

预加载

预加载是在页面加载时提前加载未使用的资源,以缩短后续请求的延迟。它可以在用户需要访问资源时,提供更快的响应时间。

预加载常用于加载下一个页面所需的资源,例如,提前加载下一个页面的图片、脚本、样式表等。这样,当用户导航到下一个页面时,这些资源已经被浏览器缓存,可以立即使用,提升页面的加载速度。

实现预加载的方法也有多种,其中一种常见的方式是使用 <link> 元素的 rel 属性,设置为 "preload",并指定要预加载的资源的 URL。例如:

<link rel="preload" href="image.jpg" as="image">
<link rel="preload" href="script.js" as="script">
<link rel="preload" href="style.css" as="style">

以上示例中,通过设置 rel="preload"as 属性来指定要预加载的资源类型,可以是 "image""script""style" 等。浏览器会在页面加载过程中提前加载这些资源,以便在需要时立即使用。

方案

  1. 内存中创建 img 标签
  2. 监听 img onload 事件
  3. 图片加载完后再添加到页面中

实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>预加载</title>
  <style>
    .container {
      width: 300px;
    }
  </style>
</head>
<body>
  <div class="container"></div>
  <script>
    let oDiv = document.querySelector('.container'),
        imgArr = [
          "<https://placekitten.com/200/100>",
          "<https://placekitten.com/300/200>",
          "<https://placekitten.com/400/300>",
          "<https://placekitten.com/500/400>",
        ];

    imgArr.forEach(imgSrc => {
      let img = new Image();
      img.style.width = "100%";
      img.src = imgSrc;
      img.onload = function() {
        oDiv.appendChild(img);
      }
    });
  </script>
</body>
</html>