多图片优化方式总结
图片懒加载
图片懒加载,长列表最大的问题就是图片太多,如果一次性把图片全部请求了,那么页面渲染速度会很慢,如果用户点不到,还会造成很大的浪费,甚至会有性能瓶颈。
为什么要使用懒加载呢?为了加速页面的加载速度,减少不必要的请求,可以将未出现在可视区域的图片暂不加载,等到滚动到可视区域后再去加载。这样提升了性能和提高了用户体验。
实现原理:初始状态,所有图片都有一个默认的 src, 指向本地的一个 默认图片 default.png
, 并且把img的真实的地址放在 data-src
上。当滚动时,判断元素是否在可视区域,如果在可视区域,那么再把 data-src
上的值写入真正的 src 中。
实现方法
判断元素是否在可视区域的方法
方法一
通过 document.documentElement.clientHeight
获取屏幕可视窗口高度
通过 element.offsetTop
获取元素相对于文档顶部的距离
通过 document.documentElement.scrollTop
获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离
然后判断 offsetTop - scrollTop < clientHeight ,代表在可视区。
方法二
通过getBoundingClientRect()
方法来获取元素的大小以及位置
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
这个方法返回一个名为ClientRect
的DOMRect
对象,包含了top
、right
、botton
、left
、width
、height
这些值。
假设const bound = el.getBoundingClientRect();
来表示图片到可视区域顶部距离;
并设 const clientHeight = window.innerHeight;
来表示可视区域的高度。
随着滚动条的向下滚动,bound.top
会越来越小,也就是图片到可视区域顶部的距离越来越小,当bound.top===clientHeight
时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。
也就是说,在bound.top<=clientHeight
时,图片是在可视区域内的。
function isInSight(el) { const bound = el.getBoundingClientRect(); const clientHeight = window.innerHeight; //如果只考虑向下滚动加载 //const clientWidth = window.innerWeight; return bound.top <= clientHeight + 100; // +100是为了提前加载。 }
特别简单的一个实例
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <style type="text/css"> *{margin:0;padding: 0;} img {display: block;width: 100%;height: auto;} </style> </head> <body> <img src="img/default.png" data-src="img/img1.png"> <img src="img/default.png" data-src="img/img2.png"> <img src="img/default.png" data-src="img/img3.png"> <img src="img/default.png" data-src="img/img4.png"> <img src="img/default.png" data-src="img/img5.png"> <img src="img/default.png" data-src="img/img6.png"> <img src="img/default.png" data-src="img/img7.png"> <img src="img/default.png" data-src="img/img8.png"> <img src="img/default.png" data-src="img/img9.png"> <img src="img/default.png" data-src="img/img10.png"> <script type="text/javascript"> var imgs = document.querySelectorAll('img'),len = imgs.length; var n = 0;//存储图片加载到的位置,避免每次都从第一张图片开始遍历 window.onscroll = function() { var seeHeight = document.documentElement.clientHeight; var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; for (let i = n; i < len; i++) { if(imgs[i].offsetTop < seeHeight + scrollTop) { if (imgs[i].getAttribute('src') == 'img/default.png'){ imgs[i].src = imgs[i].getAttribute('data-src'); } n = i + 1; console.log('n = ' + n); } } }; </script> </body> </html>
函数节流
在类似于滚动条滚动等频繁的DOM操作时,总会提到“函数节流、函数去抖”。
所谓的函数节流,也就是让一个函数不要执行的太频繁,减少一些过快的调用来节流。
基本步骤:
- 获取第一次触发事件的时间戳
- 获取第二次触发事件的时间戳
- 时间差如果大于某个阈值就执行事件,然后重置第一个时间
function throttle(fn, mustRun = 500) { const timer = null; let previous = null; return function() { const now = new Date(); const context = this; const args = arguments; if (!previous){ previous = now; } const remaining = now - previous; if (mustRun && remaining >= mustRun) { fn.apply(context, args); previous = now; } } }
这里的mustRun
就是调用函数的时间间隔,无论多么频繁的调用fn
,只有remaining>=mustRun
时fn
才能被执行。
简单封装
const checkVisible = (ele) => { let scrollTop = document.documentElement.scrollTop || document.body.scrollTop let clientHeight = document.documentElement.clientHeight return ele.offsetTop - scrollTop < clientHeight } let imgs = document.getElementsByTagName("img"), nums = imgs.length const lazyLoad = () => { for (let i = 0; i < nums; i++) { if (checkVisible(imgs[i]) && imgs[i].getAttribute("src") === "default.png") { imgs[i].src = imgs[i].getAttribute("data-src") } } } function throttle(fn, mustRun = 500) { const timer = null; let previous = null; return function() { const now = new Date(); const context = this; const args = arguments; if (!previous){ previous = now; } const remaining = now - previous; if (mustRun && remaining >= mustRun) { fn.apply(context, args); previous = now; } } }
从图片本身优化
一般问题出在哪,就得追本溯源。目前我们能够用到的有以下几种方法。
- 更换图片格式;
- 通过特别的方式在保持清晰度的条件下来压缩图片,目前常用的是在https://tinypng.com/该网站压缩图片,一般压缩率很高;
- 由 png,jpg,jpeg,gif 转换为更好的 webp。具体webp详看(https://www.zhihu.com/question/27201061)
WebP 的优势在与它更好的图像数据压缩算法,能够将图片转换为更小的体积,具有无损和有损的压缩模式。如果是选择了有损压缩,也拥有肉眼无法识别差异的图像质量。虽然它在页面渲染的时候浏览器比jpg会花稍长的时间解析它的算法,但是权衡它所带来的体积的减少来看,WebP 还是最优秀的。目前Google、Facebook、腾讯、阿里、美团的等国内外互联网公司广泛应用了webp,超过70%的浏览器已经支持webp,Safari和Foxmail也在进行支持webp的测试。
面临困难:
- 目前并不是所有浏览器都支持WebP,因此需要解决浏览器适配问题。
- 对于已上线运营的网站,采用WebP需要替换大量图片,工作量太大。
针对 WebP 兼容性问题,可以从两个方面入手
1.从服务端考虑:
如果浏览器支持 WebP ,那么会在 request header accept里,发送image/webp
, 服务器收到以后根据这个来去返回给客户端图片。请求头里有,则发送webp的,如果没有,就发送普通格式的图片。
不过这个对服务器要求比较高,并且现在图片大多放在 CDN
上,CDN
去做这种策略可能会稍微麻烦点。
2.从前端考虑:
前端去检测浏览器是否支持 WebP, 支持就发送 WebP 的图片请求,不支持就发送 jpg、png等。下面是一行代码判断是否支持WebP。
var isSupportWebp = !!\[\].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0;
!!\[\].map
主要是判断是否是IE9+,以免toDataURL
方法会挂掉。如果你直接对数组原型扩展了map方法,则需要使用!!\[\].map
以外的方法进行判断,例如!!window.addEventListener
等。