1.图片的懒加载
方案一:clientHeight、scrollTop 和 offsetTop
首先给图片一个占位资源:
<img src="default.jpg" data-src="http://www.xxx.com/target.jpg" />
接着,通过监听 scroll 事件来判断图片是否到达视口:
let img = document.getElementsByTagName("img");
let num = img.length;
let count = 0;
lazyload();
window.addEventListener('scroll', lazyload);
function lazyload() {
let viewHeight = document.documentElement.clientHeight;
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(let i = count; i <num; i++) {
if(img[i].offsetTop < scrollHeight + viewHeight) {
if(img[i].getAttribute("src") !== "default.jpg") continue;
img[i].src = img[i].getAttribute("data-src");
count ++;
}
}
}
当然,最好对 scroll 事件做节流处理,以免频繁触发:
window.addEventListener('scroll', throttle(lazyload, 200));
方案二:getBoundingClientRect
现在我们用另外一种方式来判断图片是否出现在了当前视口, 即 DOM 元素的 getBoundingClientRect API。
上述的 lazyload 函数改成下面这样:
function lazyload() {
for(let i = count; i <num; i++) {
if(img[i].getBoundingClientRect().top < document.documentElement.clientHeight) {
if(img[i].getAttribute("src") !== "default.jpg") continue;
img[i].src = img[i].getAttribute("data-src");
count ++;
}
}
}
方案三: IntersectionObserver
这是浏览器内置的一个API,实现了监听window的scroll事件、判断是否在视口中以及节流三大功能。
我们来具体试一把:
let img = document.getElementsByTagName("img");
const observer = new IntersectionObserver(changes => {
for(let i = 0, len = changes.length; i < len; i++) {
let change = changes[i];
if(change.isIntersecting) {
const imgElement = change.target;
imgElement.src = imgElement.getAttribute("data-src");
observer.unobserve(imgElement);
}
}
})
Array.from(img).forEach(item => observer.observe(item));
这样就很方便地实现了图片懒加载,当然这个IntersectionObserver也可以用作其他资源的预加载,功能非常强大。
2.事件的防抖和节流
节流
节流的核心思想: 如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器任务。
这就好比公交车,10 分钟一趟,10 分钟内有多少人在公交站等我不管,10 分钟一到我就要发车走人!
代码如下:
function throttle(fn, interval) {
let flag = true;
return function(...args) {
let context = this;
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(context, args);
flag = true;
}, interval);
};
};
写成下面的方式也是表达一样的意思:
const throttle = function(fn, interval) {
let last = 0;
return function (...args) {
let context = this;
let now = +new Date();
if(now - last < interval) return;
last = now;
fn.apply(this, args)
}
}
防抖
核心思想: 每次事件触发则删除原来的定时器,建立新的定时器。
多次触发,那么只认最后一次,从最后一次触发开始计时。
function debounce(fn, delay) {
let timer = null;
return function (...args) {
let context = this;
if(timer) clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
}
}
合并版节流
现在我们可以把防抖和节流放到一起,为什么呢?因为防抖有时候触发的太频繁会导致一次响应都没有,我们希望到了固定的时间必须给用户一个响应,
function throttle(fn, delay) {
let last = 0, timer = null;
return function (...args) {
let context = this;
let now = new Date();
if(now - last < delay){
clearTimeout(timer);
setTimeout(function() {
last = now;
fn.apply(context, args);
}, delay);
} else {
last = now;
fn.apply(context, args);
}
}
}