手把手实现图片懒加载+封装vue懒加载组件

栏目: JavaScript · 发布时间: 7年前

内容简介:图片对页面加载速度影响非常大当页面图片比较多,加载速度慢,非常影响用户体验思考一下,页面有可能有几百张图片,但是首屏上需要展示的可能就一张而已,其他的那些图片能不能晚一点再加载,比如用户往下滚动的时候……

1、为什么要懒加载或者预加载

图片对页面加载速度影响非常大

当页面图片比较多,加载速度慢,非常影响用户体验

思考一下,页面有可能有几百张图片,但是首屏上需要展示的可能就一张而已,其他的那些图片能不能晚一点再加载,比如用户往下滚动的时候……

这是为什么要用懒加载的原因

那预加载呢? 这个非常语义化,预备,提前…… 就是让用户感觉到你加载图片非常快,甚至用户没有感受到你在加载图片

2、懒加载原理

图片先用占位符表示,不要将图片地址放到 src 属性中,而是放到其它属性( data-original )中 页面加载完成后,监听窗口滚动,当图片出现在视窗中时再给它赋予真实的图片地址,也就是将 data-original 中的属性拿出来放到src属性中 在滚动页面的过程中,通过给 scroll 事件绑定 lazyload 函数,不断的加载出需要的图片

注意:请对 lazyload 函数使用防抖与节流,不懂这两的可以自己去查

3、懒加载方式

1)纯粹的延迟加载,使用setTimeOut或setInterval

这种方式,本质上不算懒加载 加载完首屏内容后,隔一段时间,去加载全部内容 但这个时间差已经完成了用户对首屏加载速度的期待

2)条件加载

用户点击或者执行其他操作再加载 其实也包括的滚动可视区域,但大部分情况下,大家说的懒加载都是只可视区域的图片懒加载,所以就拿出来说了

3)可视区加载

这里也分为两种情况:

1、页面滚动的时候计算图片的位置与滚动的位置

2、通过新的API: IntersectionObserver API (可以自动"观察"元素是否可见) Intersection Observer API - Web API 接口 | MDN

4、懒加载代码实现

1、核心原理

将非首屏的图片的src属性设置一个默认值,监听事件 scroll 、 resize 、 orientationchange ,判断元素进入视口viewport时则把真实地址赋予到 src 上

2、img标签自定义属性相关

<img class="lazy" src="[占位图]" data-src="[真实url地址]" data-srcset="[不同屏幕密度下,不同的url地址]" alt="I'm an image!">
复制代码

如上, data-* 属于自定义属性, ele.dataset.* 可以读取自定义属性集合 img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片,比如 <img src="image-128.png" srcset="image-256.png 2x" />

3、判断元素进入视口viewport

常用的方式有两种

1)、图片距离顶部距离 < 视窗高度 + 页面滚动高度(太LOW了~)

imgEle.offsetTop < window.innerHeight + document.body.scrollTop
复制代码

2) getBoundingClientRect (很舒服的一个API)

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置,具体参考文档 Element.getBoundingClientRect() - Web API 接口 | MDN

function isInViewport(ele) {
    // 元素顶部 距离 视口左上角 的距离top <= 窗口高度 (反例:元素在屏幕下方的情况)
    // 元素底部 距离 视口左上角 的距离bottom > 0 (反例:元素在屏幕上方的情况)
    // 元素display样式不为none
    const notBelow = ele.getBoundingClientRect().top <= window.innerHeight ? true : false;
    const notAbove = ele.getBoundingClientRect().bottom >= 0 ? true : false;
    const visable = getComputedStyle(ele).display !== "none" ? true : false;
    return notBelow && notAbove && visable ? true : false;
  }
复制代码

3) Intersection Observer (存在兼容性问题,但帅啊)

由于兼容性问题,暂时不写,具体可参考文档 Intersection Observer - Web API 接口 | MDN

4、具体实现(demo)

核心内容都在上面分析完了,下面就是整合一下,

1)适合简单的HTML文件或者服务端直出的首页

注意 DOMContentLoaded ,在DOM解析完之后立马执行,不适合前后端分离的单页应用,因为SPA应用一般来说图片数据是异步请求的,在 DOMContentLoaded 的时候,页面上未必完全解析完JS和CSS,这时候 let lazyImages = [].slice.call(document.querySelectorAll("img.lazy")); 拿到的不是真正首屏的所有图片标签

document.addEventListener("DOMContentLoaded", () => {
  // 获取所有class为lazy的img标签
  let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
  // 这个active是节流throttle所用的标志位,这里用到了闭包知识
  let active = false;

  const lazyLoad = () => {
    // throttle相关:200ms内只会执行一次lazyLoad方法
    if (active) return;
    active = true;

    setTimeout(() => {
      lazyImages.forEach(lazyImage => {
        // 判断元素是否进入viewport
        if (isInViewport(lazyImage)) {
          // <img class="lazy" src="[占位图]" data-src="[真实url地址]" data-srcset="[不同屏幕密度下,不同的url地址]" alt="I'm an image!">
          // ele.dataset.* 可以读取自定义属性集合,比如data-*
          // img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片  比如<img src="image-128.png" srcset="image-256.png 2x" />
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          // 删除class  防止下次重复查找到改img标签
          lazyImage.classList.remove("lazy");
        }
        // 更新lazyImages数组,把还没处理过的元素拿出来
        lazyImages = lazyImages.filter(image => {
          return image !== lazyImage;
        });
        // 当全部处理完了,移除监听
        if (lazyImages.length === 0) {
          document.removeEventListener("scroll", lazyLoad);
          window.removeEventListener("resize", lazyLoad);
          window.removeEventListener("orientationchange", lazyLoad);
        }
      })

      active = false;
    }, 200);
  }

  document.addEventListener("scroll", lazyLoad);
  document.addEventListener("resize", lazyLoad);
  document.addEventListener("orientationchange", lazyLoad);
})
复制代码

2)、适合单页应用的写法(模拟封装vue的懒加载)

① 核心实现

  • 因为是demo,所以执行时机放到vue的全局 mounted 钩子里面(这样的首屏体验其实是不好的),不过足够理解就好了 
  • 跟上面不同的地方:l et lazyImages = [].slice.call(document.querySelectorAll("img.lazy")); 的获取时机放在了定时器里面,不是一开始就拿到全局的 lazyImages ,而是每次刷新时才拿到还没处理过的
function LazyLoad() {
  // 这个active是节流throttle所用的标志位,这里用到了闭包知识
  let active = false;

  const lazyLoad = () => {
    // throttle相关:200ms内只会执行一次lazyLoad方法
    if (active) return;
    active = true;

    setTimeout(() => {
      // 获取所有class为lazy的img标签,这里由于之前已经把处理过的img标签的class删掉了  所以不会重复查找
      let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

      lazyImages.forEach(lazyImage => {
        // 判断元素是否进入viewport
        if (isInViewport(lazyImage)) {
          // <img class="lazy" src="[占位图]" data-src="[真实url地址]" data-srcset="[不同屏幕密度下,不同的url地址]" alt="I'm an image!">
          // ele.dataset.* 可以读取自定义属性集合,比如data-*
          // img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片  比如<img src="image-128.png" srcset="image-256.png 2x" />
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          // 删除class  防止下次重复查找到改img标签
          lazyImage.classList.remove("lazy");
        }

        // 当全部处理完了,移除监听
        if (lazyImages.length === 0) {
          document.removeEventListener("scroll", lazyLoad);
          window.removeEventListener("resize", lazyLoad);
          window.removeEventListener("orientationchange", lazyLoad);
        }
      })

      active = false;
    }, 200);
  }

  document.addEventListener("scroll", lazyLoad);
  document.addEventListener("resize", lazyLoad);
  document.addEventListener("orientationchange", lazyLoad);
}
复制代码

② 在全局中的`mounted`钩子中执行

const vm = new Vue({
  el: '.wrap',
  store,
  mounted: function () {
    LazyLoad();
  }
});
复制代码

③ 封装 img-lazy 组件

<template>
  <img :class="['lazy', className]" :src="defaultImg" :data-src="url" :data-srcset="`${url} 1x`" alt="fordeal">
</template>

<script>
  export default {
    props: {
      url: {
        type: String
      },
      defaultImg: {
        type: String,
        default: [默认图片]
      className: {
        type: String,
        default: ''
      }
    }
  }
</script>
复制代码

④ 使用

<img-lazy className="image" :url="item.display_image" />复制代码

以上实现的只是比较粗糙的版本,要真正实现性能大幅提升优化还需要处理较多的细节,本文旨在让帮助部分同学了解基本原理,有了宏观的认识后,可以尝试去读一下相关这种懒加载插件的源码,能学到不少东西。

感谢

感谢您耐心看到这里,希望有所收获!

我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步,更多文章请看 amandakelake的Github博客


以上所述就是小编给大家介绍的《手把手实现图片懒加载+封装vue懒加载组件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Test Driven Development

Test Driven Development

Kent Beck / Addison-Wesley Professional / 2002-11-18 / USD 49.99

Quite simply, test-driven development is meant to eliminate fear in application development. While some fear is healthy (often viewed as a conscience that tells programmers to "be careful!"), the auth......一起来看看 《Test Driven Development》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具