一、图片预加载
预加载图片是提高用户体验的一个很好方法。图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度。这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速、无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验。通常情况下,平常见得比较多的是这种写法。
(1)多张图片
通过实例化Image对象 调用onload方法来实现(onload之后)
var oDiv = document.getElementsByClassName('div')[0],
img = ['https://zza.jpg', 'https://zzb.jpg'];
img.forEach(function(elem){
var oImg = new Image();
oImg.src = elem;
oImg.style.width = '100%';
oImg.onload = function(){
oDiv.appendChild(oImg);
}
})
(2)单张照片
var oDiv = document.getElementsByTagName('div')[0],
var oImg = new Image();
oImg.src = 'https://zzz.jpg';
oImg.style.width = '100%';
oImg.onload = function(){
oDiv.appendChild(oImg);
}
接下来将归纳一下实现预加载的三种方法。
CSS+JavaScript实现预加载
实现预加载图片有很多方法,包括使用CSS、JavaScript及两者的各种组合。这些技术可根据不同设计场景设计出相应的解决方案,十分高效。单纯使用CSS,可容易、高效地预加载图片,代码如下:
#preload-01 { background: url(http://img.netbian.com/file/2020/1123/small5027584d7ae249306584b186d876b4381606145734.jpg) no-repeat -9999px -9999px; }
#preload-02 { background: url(http://img.netbian.com/file/2020/1123/small5887a5aa6ff9e7b7932e89ef616d24251606131346.jpg) no-repeat -9999px -9999px; }
#preload-03 { background: url(http://img.netbian.com/file/2020/1123/smalledec4b73a9febffb61a0857b7a1221be1606131242.jpg) no-repeat -9999px -9999px; }
将这三个ID选择器应用到(X)HTML元素中,我们便可通过CSS的background属性将图片预加载到屏幕外的背景上。只要这些图片的路径保持不变,当它们在Web页面的其他地方被调用时,浏览器就会在渲染过程中使用预加载(缓存)的图片。简单、高效,不需要任何JavaScript。该方法虽然高效,但仍有改进余地。使用该法加载的图片会同页面的其他内容一起加载,增加了页面的整体加载时间。为了解决这个问题,我们增加了一些JavaScript代码,来推迟预加载的时间,直到页面加载完毕。代码如下:
function preloader() {
if (document.getElementById) {
document.getElementById("preload-01").style.background = "url(http://http://img.netbian.com/file/2020/1123/small5027584d7ae249306584b186d876b4381606145734.jpg) no-repeat -9999px -9999px";
document.getElementById("preload-02").style.background = "url(http://img.netbian.com/file/2020/1123/small5887a5aa6ff9e7b7932e89ef616d24251606131346.jpg) no-repeat -9999px -9999px";
document.getElementById("preload-03").style.background = "url(http://img.netbian.com/file/2020/1123/smalledec4b73a9febffb61a0857b7a1221be1606131242.jpg) no-repeat -9999px -9999px";
}
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(preloader);
在该脚本的第一部分,获取使用类选择器的元素,并为其设置了background属性,以预加载不同的图片。
该脚本的第二部分,使用addLoadEvent()函数来延迟preloader()函数的加载时间,直到页面加载完毕。如果JavaScript无法在用户的浏览器中正常运行,图片不会被预加载,当页面调用图片时,正常显示即可。
仅使用JavaScript实现预加载
上述方法有时确实很高效,但它在实际实现过程中会耗费太多时间。
下面将提供两种这样的预加载方法,它们可以很漂亮地工作于所有现代浏览器之上。JavaScript代码段1 只需简单编辑、加载所需要图片的路径与名称即可,很容易实现:
<div class="hidden">
<script type="text/javascript">
<!--//--><![CDATA[//><!--
var images = new Array()
function preload() {
for (i = 0; i < preload.arguments.length; i++) {
images[i] = new Image()
images[i].src = preload.arguments[i]
}
}
preload(
"http://domain.tld/gallery/image-001.jpg",
"http://domain.tld/gallery/image-002.jpg",
"http://domain.tld/gallery/image-003.jpg"
)
//--><!]]>
</script>
</div>
该方法尤其适用预加载大量的图片。画廊网站很适合使用该技术,将该脚本应用到登录页面,只要用户输入登录帐号,大部分画廊图片将被预加载。
JavaScript代码段2 该方法与上面的方法类似,也可以预加载任意数量的图片。将下面的脚本添加入任何Web页中,根据程序指令进行编辑即可。
<div class="hidden">
<script type="text/javascript">
<!--//--><![CDATA[//><!--
if (document.images) {
img1 = new Image();
img2 = new Image();
img3 = new Image();
img1.src = "http://domain.tld/path/to/image-001.gif";
img2.src = "http://domain.tld/path/to/image-002.gif";
img3.src = "http://domain.tld/path/to/image-003.gif";
}
//--><!]]>
</script>
</div>
正如所看,每加载一个图片都需要创建一个变量,如img1 = new Image();
,及图片源地址声明,如img3.src = "../path/to/image-003.gif";
。
参考该模式,可根据需要加载任意多的图片。
继续对该方法进行改进。将该脚本封装入一个函数中,并使用 addLoadEvent(),延迟预加载时间,直到页面加载完毕。
function preloader() {
if (document.images) {
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.src = "http://domain.tld/path/to/image-001.gif";
img2.src = "http://domain.tld/path/to/image-002.gif";
img3.src = "http://domain.tld/path/to/image-003.gif";
}
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(preloader);
使用Ajax实现预加载
该方法利用DOM,不仅仅预加载图片,还会预加载CSS、JavaScript等相关的东西。
使用Ajax的优越之处在于JavaScript和CSS的加载不会影响到当前页面。该方法简洁、高效。
window.onload = function() {
setTimeout(function() {
// XHR to request a JS and a CSS
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.js');
xhr.send('');
xhr = new XMLHttpRequest();
xhr.open('GET', 'http://domain.tld/preload.css');
xhr.send('');
// preload image
new Image().src = "http://domain.tld/preload.png";
}, 1000);
};
上面代码预加载了“preload.js”、“preload.css”和“preload.png”。1000毫秒的超时是为了防止脚本挂起,而导致正常页面出现功能问题。下面,我们看看如何用JavaScript来实现该加载过程:
window.onload = function() {
setTimeout(function() {
// reference to <head>
var head = document.getElementsByTagName('head')[0];
// a new CSS
var css = document.createElement('link');
css.type = "text/css";
css.rel = "stylesheet";
css.href = "http://domain.tld/preload.css";
// a new JS
var js = document.createElement("script");
js.type = "text/javascript";
js.src = "http://domain.tld/preload.js";
// preload JS and CSS
head.appendChild(css);
head.appendChild(js);
// preload image
new Image().src = "http://domain.tld/preload.png";
}, 1000);
};
这里,我们通过DOM创建三个元素来实现三个文件的预加载。正如上面提到的那样,使用Ajax,加载文件不会应用到加载页面上。
二、图片懒加载
这样做能防止页面一次性向服务器发送大量请求,导致服务器响应面,页面卡顿崩溃等。
实现思路
-
首先正常渲染,但是真正的路径要放到data-src中去
-
获取当前窗口高度,滚动高度
-
滚动时进行判读,如果当前元素距离窗口高度小于窗口高度+滚动高度则将data-src中的数据放到src中(记得要加上防抖或者节流)
-
删除data-src属性
-
让n来控制循环开始数(防止滚回去还会操作src的问题)
-
控制页面刷新后让滚动条复位(要加延迟否则可能不生效)
实现代码
jquery实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/2.1.0/jquery.min.js"></script>
<style>
.container {
max-width: 800px;
margin: 0 auto;
}
.container:after {
content: "";
display: block;
clear: both;
}
</style>
</head>
<body>
<div class="container">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1123/smalle51922e8a6da05ef69459dcbec14708a1606144751.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1123/smalledec4b73a9febffb61a0857b7a1221be1606131242.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1123/small7c61f7388b168e272835ec91ead277141606131144.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1120/small1d95e6a89ff1261aa812002b7c063e1e1605802066.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1119/small10dc593beaa5744ddcc5f84c778477691605801327.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1120/small6321d8199e10cbd5b53d69f4bb1b1a861605801612.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1123/small81f009e494f914e94eea3f51f5f824ef1606120977.jpg">
<img src="http://s4.sinaimg.cn/mw690/006uWPTUgy72CNFYNjB93&690" alt="1"
data-src="http://img.netbian.com/file/2020/1123/small5887a5aa6ff9e7b7932e89ef616d24251606131346.jpg">
</div>
<script>
// 一开始没有滚动的时候,出现在视窗中的图片也会加载
start();
// 当页面开始滚动的时候,遍历图片,如果图片出现在视窗中,就加载图片
let clock; //函数节流
$(window).on('scroll', function () {
if (clock) {
clearTimeout(clock);
}
clock = setTimeout(function () {
start()
}, 200)
})
function start() {
$('.container img').not('[data-isLoading]').each(function () {
if (isShow($(this))) {
loadImg($(this));
}
})
}
// 判断图片是否出现在视窗的函数
function isShow($node) {
return $node.offset().top <= $(window).height() + $(window).scrollTop();
}
// 加载图片的函数,就是把自定义属性data-src 存储的真正的图片地址,赋值给src
function loadImg($img) {
$img.attr('src', $img.attr('data-src'));
// 已经加载的图片,我给它设置一个属性,值为1,作为标识
// 弄这个的初衷是因为,每次滚动的时候,所有的图片都会遍历一遍,这样有点浪费,所以做个标识,滚动的时候只遍历哪些还没有加载的图片
$img.attr('data-isLoading', 1);
}
</script>
</body>
</html>
原生js实现
使用scrollTop/innerHeight/offsetTop
基本知识点:
window.innerHeight:浏览器可视区域高度
document.body.scrollTop || document.documentElement.scrollTop:浏览器滚动条滚过高度
img.offsetTop:元素距文档顶部的高度
加载条件:
img.offsetTop < window.innerHeight + document.body.scrollTop;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LozyLoad</title>
<style>
.images{
display: flex;
flex-direction: column;
text-align: center;
width: 500px;
}
.img-item{
height:400px;
width: 400px;
margin: 20px;
}
</style>
</head>
<body>
<div class="images">
<img class="img-item" alt="loading" data-src="./img/img1.png">
<img class="img-item" alt="loading" data-src="./img/img2.png">
<img class="img-item" alt="loading" data-src="./img/img3.png">
<img class="img-item" alt="loading" data-src="./img/img4.png">
<img class="img-item" alt="loading" data-src="./img/img5.png">
</div>
<!--
<script type="text/javascript">
//获取观察器实例 changes是被观察的对象数组
var observer = new IntersectionObserver(function(changes){
console.log(changes);
changes.forEach(function(index,item){
if(item.intersectionRatio > 0 && item.intersectionRatio < 1)
//target:被观察的目标元素,是一个 DOM 节点对象
item.target.src = item.target.dataset.src;
});
});
function addObserver(){
var listItems = document.querySelectorAll('.img-item');
listItems.forEach(function(item){
//实例的observe方法可以指定观察哪个DOM节点
//开始观察 observe的参数是一个 DOM 节点对象
observer.observe(item);
});
}
addObserver();
</script>
-->
<script type="text/javascript">
var imgs = document.querySelectorAll('img');
var lazyload = function(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winTop = window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winTop ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
function throttle(method,delay){
var timer = null;
return function(){
var context = this, args=arguments;
clearTimeout(timer);
timer=setTimeout(function(){
method.apply(context,args);
},delay);
}
}
window.onscroll = throttle(lazyload,200);
</script>
</body>
</html>
使用IntersectionObserver方法
MDN介绍主页:https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver
基本知识:
构造函数IntersectionObserver接收两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。
这个构造函数的返回值是一个观察器实例。构造函数的返回值是一个观察器实例,实例的observe方法可以指定观察哪个DOM节点。
observe的参数可以是一个DOM节点对象。如果要观察多个节点,就要多次调用这个方法。
callback
函数的参数(entries
)是一个数组,每个成员都是一个IntersectionObserverEntry
对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries
数组就会有两个成员
intersectionRatio:目标元素的可见比例,完全可见时为1,完全不可见时小于等于0。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LozyLoad</title>
<style>
.images{
display: flex;
flex-direction: column;
text-align: center;
width: 500px;
}
.img-item{
height:400px;
width: 400px;
margin: 20px;
}
</style>
</head>
<body>
<div class="images">
<img class="img-item" alt="loading" data-src="./img/img1.png">
<img class="img-item" alt="loading" data-src="./img/img2.png">
<img class="img-item" alt="loading" data-src="./img/img3.png">
<img class="img-item" alt="loading" data-src="./img/img4.png">
<img class="img-item" alt="loading" data-src="./img/img5.png">
</div>
<script type="text/javascript">
//获取观察器实例 changes是被观察的对象数组
var observer = new IntersectionObserver(function(changes){
console.log(changes);
changes.forEach(function(index,item){
if(item.intersectionRatio > 0 && item.intersectionRatio < 1)
//target:被观察的目标元素,是一个 DOM 节点对象
item.target.src = item.target.dataset.src;
});
});
function addObserver(){
var listItems = document.querySelectorAll('.img-item');
listItems.forEach(function(item){
//实例的observe方法可以指定观察哪个DOM节点
//开始观察 observe的参数是一个 DOM 节点对象
observer.observe(item);
});
}
addObserver();
</script>
</body>
</html>
原文始发于微信公众号(豆子前端):面试官:图片加载优化中,你是如何实现预加载和懒加载的?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56999.html