2. 图片性能优化

图片性能优化

图片懒加载

  1. 如何判断图片出现在了当前视口 (即如何判断我们能够看到图片)
  2. 如何控制图片的加载

原生实现

html 复制代码
<img src="shanyue.jpg" loading="lazy" />

loading="lazy" 延迟加载图像,直到它和视口接近到一个计算得到的距离(由浏览器定义)。目的是在需要图像之前,避免加载图像所需要的网络和存储带宽。这通常会提高大多数典型用场景中内容的性能。

  • lazy:对资源进行延迟加载。
  • eager:立即加载资源。
  • auto:浏览器自行判断决定是否延迟加载资源。

通过相对计算获取元素位置

图片顶部到文档顶部的距离 > 浏览器可视窗口高度 + 滚动条滚过的高度 ,此时的图片就是不可见的,如果图片顶部到文档顶部的距离 < 浏览器可视窗口高度 + 滚动条滚过的高度那么该图片就应该出现在可视区域内了。

但你还记得我们前面提到的注意事项吗?如果用户直接滑到页面底部,那么这个判断条件对所有的图片都为真,还是会造成性能问题。所以我们要再加上一条判断条件 图片的高度 + 图片顶部到文档顶部的距离 > 滚动条滚过的高度,以确保图片确实在可视区域内,而不只是被滑过。

  • 待加载图片的高度:img.clientHeight
  • 图片顶部到文档顶部的距离:img.offsetTop
  • 浏览器窗口滚动过的距离:document.documentElement.scrollTopdocument.body.scrollTop
  • 浏览器可视窗口高度:document.documentElement.clientHeightwindow.innerHeight
javascript 复制代码
const imgs = document.querySelectorAll('img')
function lazyLoad(imgs) {
  console.log('lazyLoad')
  // 浏览器可视窗口的高度
  const windowHeight = window.innerHeight
  // 可视窗口滚动过的距离
  const scrollHeight = document.documentElement.scrollTop
  for (let i = 0; i < imgs.length; i++) {
    if (windowHeight + scrollHeight > imgs[i].offsetTop && imgs[i].clientHeight + imgs[i].offsetTop > document.documentElement.scrollTop && !imgs[i].src) {
      imgs[i].src = imgs[i].dataset.src
    }
  }
}
// 进入页面时执行一次加载
lazyLoad(imgs)
// 监听滚动事件,当滚动到可视区域时加载图片
// 此处可以添加防抖/节流优化 window.onscroll = throttle(lazyLoad, 500)
window.onscroll = function () {
  lazyLoad(imgs)
}

Element.getBoundingClientRect()

getBoundingClientRect 返回值是一个 DOMRect 对象,这个对象是由该元素的 getClientRects() 方法返回的一组矩形的集合, 即:是与该元素相关的 CSS 边框集合 。DOMRect 对象包含了一组用于描述边框的只读属性------left、top、right 和 bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。

有了这个 API 后我们很同意获取图片的 top 值,当 top 值小于可视区的高度的时候就可以任何图片进入了可视区,直接加载图片即可。

javascript 复制代码
document.addEventListener('DOMContentLoaded', () => {
  const lazyImages = document.querySelectorAll('img.lazyload')

  const lazyLoad = () => {
    lazyImages.forEach((img) => {
      if (img.getBoundingClientRect().top <= window.innerHeight && img.getBoundingClientRect().bottom >= 0 && getComputedStyle(img).display !== 'none') {
        img.src = img.dataset.src
        img.classList.remove('lazyload')
      }
    })
    if (lazyImages.length === 0) {
      document.removeEventListener('scroll', lazyLoad)
      window.removeEventListener('resize', lazyLoad)
      window.removeEventListener('orientationchange', lazyLoad)
    }
  }

  document.addEventListener('scroll', lazyLoad)
  window.addEventListener('resize', lazyLoad)
  window.addEventListener('orientationchange', lazyLoad)
})

使用 IntersectionObserver

html 复制代码
<img data-src="xxx.jpg" class="lazyload" />

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const lazyImages = document.querySelectorAll('img.lazyload')

    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            const image = entry.target
            image.src = image.dataset.src
            img.classList.remove('lazyload')
            observer.unobserve(image)
          }
        })
      })
      lazyImages.forEach((img) => {
        observer.observe(img)
      })
    } else {
      lazyImages.forEach((img) => {
        img.src = img.dataset.src
      })
    }
  })
</script>

监听元素的重叠度 IntersectionObserver

javascript 复制代码
var observer = new IntersectionObserver(callback[, options]); 

IntersectionObserverdisconnect()方法终止对所有目标元素可见性变化的观察。

IntersectionObserverobserve() 方法向 IntersectionObserver

对象观察的目标集合添加一个元素。一个观察者有一组阈值和一个根(root),但是可以监视多个目标元素的可见性变化(遵循阈值和根的设置)。

IntersectionObservertakeRecords() 方法返回一个IntersectionObserverEntry 对象数组,每个对象包含目标元素自上次相交检查以来所经历的相交状态变化------可以显式地通过调用此方法或隐式地通过观察器的回调获得。

IntersectionObserverunobserve() 方法命令 IntersectionObserver停止对一个元素的观察。

javascript 复制代码
const ob = new IntersectionObserver( (entries) => {
   	const entry = entries[0]
   	if (entry.isIntersecting) {
     	console.log('加载更多')
  	 }  
   },   
   {
   // root 监听元素的祖先元素Element对象,其边界盒将被视作视口。目标在根的可见区域的任何不可见部分都会被视为不可见。
   root: null,

   // rootMargin 一个在计算交叉值时添加至根的边界盒 (bounding_box) 中的一组偏移量,类型为字符串 (string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和 CSS 中的margin 属性等同; 可以参考 intersection root 和 root margin 来深入了解 margin 的工作原理及其语法。默认值是"0px 0px 0px 0px"。

   // threshold 规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组 0.0 到 1.0 之间的数组。若指定值为 0.0,则意味着监听元素即使与根有 1 像素交叉,此元素也会被视为可见。若指定值为
1.0,则意味着整个元素都在可见范围内时才算可见。
   threshold: 0   
   })

const dom = document.querySelector('.loading') 
ob.observe(dom) 

使用库

lazysizes、lazyload

图片预加载

javascript 复制代码
const images = [
	'https://picsum.photos/id/237/400/400.jpg?grayscale&blur=2',
	'https://picsum.photos/id/238/400/400.jpg?grayscale&blur=2'
]

function preloadImages(max = 3) {
  const _images = [...images]
  function loadImage() {
    const src = _images.shift()
    return new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.as = 'image'
      link.href = src
      document.head.appendChild(link)
      link.onload = resolve
      link.onerror = reject
      setTimeout(reject, 10000)
    })
  }

  function _loadImage() {
    loadImage().finally(() => {
      if (_images.length) {
        loadImage()
      }
    })
  }

  for (let i = 0; i < max; i++) {
    _loadImage()
  }
}
相关推荐
灯火不休ᝰ几秒前
前端处理pdf文件流,展示pdf
前端·pdf
智践行3 分钟前
Trae开发实战之转盘小程序
前端·trae
最新资讯动态9 分钟前
DialogHub上线OpenHarmony开源社区,高效开发鸿蒙应用弹窗
前端
lvbb6618 分钟前
框架修改思路
前端·javascript·vue.js
树上有只程序猿20 分钟前
Java程序员需要掌握的技术
前端
从零开始学安卓23 分钟前
Kotlin(三) 协程
前端
阿镇吃橙子27 分钟前
一些手写及业务场景处理问题汇总
前端·算法·面试
庸俗今天不摸鱼27 分钟前
【万字总结】前端全方位性能优化指南(九)——FSP(First Screen Paint)像素级分析、RUM+合成监控、Lighthouse CI
前端·性能优化
逆袭的小黄鸭28 分钟前
JavaScript:作用域与作用域链的底层逻辑
前端·javascript·面试