面试官:图片加载优化中,你是如何实现预加载和懒加载的?

一、图片预加载

预加载图片是提高用户体验的一个很好方法。图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度。这对图片画廊及图片占据很大比例的网站来说十分有利,它保证了图片快速、无缝地发布,也可帮助用户在浏览你网站内容时获得更好的用户体验。通常情况下,平常见得比较多的是这种写法。

(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

加载条件:

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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值