图片懒加载

方案

  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();
})();

预加载

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