资源的压缩与合并
为什么我们要做资源的压缩与合并:
- 减少请求资源的大小
- 减少http请求的数量
HTML压缩
- 使用在线工具进行压缩
- 使用html-minifier等npm工具
CSS压缩
- 使用在线工具压缩
- 使用clean-css等npm工具
JS压缩与混淆
- 使用在线工具压缩(不适合现代压缩方案)
- 使用Webpack对JS在构建时进行压缩
CSS JS 文件合并
合并的情况是比较小的,因为我们现在主流的方案是渐进式。
- 若干个小文件进行合并(减少http多次请求)
- 无冲突,服务相同的模块(避免命名冲突)
图片优化
图片格式优化
- JPEG/JPG
优点:有损压缩,较高的压缩比,色彩保存完整。
缺点:1. 边缘纹理较差。2. 行扫描的方式进行渲染,所以我们经常会发现大图片或者网络慢的情况下,是从上往下逐步渲染的。
使用场景:较大的图片展示轮播图之类的。
- PNG
优点:边缘纹理较好。
缺点:体积较大。
使用场景:适用于比较小的图片。支持透明图片。
- WebP
体积比PNG小,其他比较类似PNG。谷歌推出的,所有兼容性一般。和PNG差距不会特别明显。
- 后面会具体讲到
SVG
图片优化方案
1. 图片懒加载
- 原生懒加载
- 第三方懒加载
因为我是vue技术栈的,所以在懒加载方面,着重介绍下element-ui的源码,代码版本是2.15.7
在初始化的时候,当用户传入了lazy属性为true,开启懒加载。
mounted() {
if (this.lazy) { // 如果开启懒加载
this.addLazyLoadListener();
} else {
this.loadImage();
}
},
先对父元素的scroll事件做了监听,并对滚动条事件做了节流处理。
// 这个方法主要是做 懒加载 的兼容和节流等处理
addLazyLoadListener() {
if (this.$isServer) return;
const { scrollContainer } = this; // 开启懒加载后,监听 scroll 事件的容器
let _scrollContainer = null;
// 做各种判断,最终把 scroll 事件的容器 赋值给 _scrollContainer
if (isHtmlElement(scrollContainer)) {
_scrollContainer = scrollContainer;
} else if (isString(scrollContainer)) {
_scrollContainer = document.querySelector(scrollContainer);
} else {
_scrollContainer = getScrollContainer(this.$el);
}
if (_scrollContainer) {
this._scrollContainer = _scrollContainer;
// 滚动条事件节流
this._lazyLoadHandler = throttle(200, this.handleLazyLoad);
// on方法是一个兼容的dom监听事件,兼容addEventListener事件
on(_scrollContainer, 'scroll', this._lazyLoadHandler);
this.handleLazyLoad();
}
},
接下来我们看下滚动事件的回调函数handleLazyLoad做了什么?
// 懒加载核心函数
handleLazyLoad() {
// isInContainer 判断dom元素是否在指定容器的视口中
if (isInContainer(this.$el, this._scrollContainer)) {
this.show = true;
this.removeLazyLoadListener();
}
},
上面我们看到只走了一个isInContainer函数判断,所以这个isInContainer函数就是重点了
// 判断dom元素是否在指定容器的视口中
export const isInContainer = (el, container) => {
if (isServer || !el || !container) return false;
// getBoundingClientRect api 其提供了元素的大小及其相对于视口的位置。
// 除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。
const elRect = el.getBoundingClientRect();
let containerRect;
// 如果视口是浏览器窗口,就不需要getBoundingClientRect计算
if ([window, document, document.documentElement, null, undefined].includes(container)) {
containerRect = {
top: 0,
right: window.innerWidth,
bottom: window.innerHeight,
left: 0
};
} else {
containerRect = container.getBoundingClientRect();
}
return elRect.top < containerRect.bottom &&
elRect.bottom > containerRect.top &&
elRect.right > containerRect.left &&
elRect.left < containerRect.right;
};
我们可以从上面那个函数看到,element-ui是通过官方getBoundingClientRect的api,获取到了dom的位置信息。
getBoundingClientRect的位置信息是:除了 width 和 height 以外的属性是相对于视图窗口的左上角来计算的。通过获取到的位置信息,就可以判断出我们传入的el是否在container的区域内。
最后一步我们new Image(),当图片加载完成后,会执行回调函数onload事件,最后修改loading和error为false,最终把图片展示出来
// 加载图片
loadImage() {
if (this.$isServer) return;
// reset status
this.loading = true;
this.error = false;
const img = new Image();
// 图片加载完成
img.onload = e => this.handleLoad(e, img);
img.onerror = this.handleError.bind(this);
// bind html attrs
// so it can behave consistently
Object.keys(this.$attrs)
.forEach((key) => {
const value = this.$attrs[key];
img.setAttribute(key, value);
});
img.src = this.src;
},
// 图片加载完成回调,展示图片
handleLoad(e, img) {
this.imageWidth = img.width;
this.imageHeight = img.height;
this.loading = false;
this.error = false;
},
懒加载方案拓展方案IntersectionObserver
我们可以发现element-ui采用的是getBoundingClientRect api实现的,除了这种方案外,还有一种较为方便的方案IntersectionObserver
上面那种方法虽然能够实现图片懒加载,但需要自己手动去计算,并且会引起回流与重绘,性能相对来说较差。
示例:
function lazyLoadWithObserver() {
// 推荐使用IntersectionObserver
let observer = new IntersectionObserver((entries, observe) => {
entries.forEach(item => {
// 获取当前正在观察的元素
let target = item.target
if(item.isIntersecting && target.dataset.src) {
target.src = target.dataset.src
// 删除data-src属性
target.removeAttribute('data-src')
// 取消观察
observe.unobserve(item.target)
}
})
})
let imgs = document.querySelectorAll('.img_box')
imgs.forEach(item => {
// 遍历观察元素
observer.observe(item)
})
}
lazyLoadWithObserver()
2. 渐进式图片
主要是利用JPEG的一种保存方式。
JPEG的保存方式是有两种的:
- Baseline JPEG(基准式) 从上至下渲染
- Progressive JPEG(渐进式) 从模糊到清晰
虽然等待的总事件更长,但是体验过程中比较舒适。
渐进式解决方案
- progressive-image
- ImageMagick
- libjpeg
- jpegtran
- jpeg-recompress
- imagemin
3. 使用响应式图片
- Srcset属性的使用(同样的图片不同尺寸)
- Size属性的使用
- picture的使用
字体优化
什么是FOIT和FOUT?
- 字体未下载完成时,浏览器隐藏或自动降级,导致字体闪烁
- Flash Of Invisible Text
- Flash Of Unstyled Text
在字体加载后,会覆盖之前的字体,中间的过程中会发生字体闪烁。
解决方案 font-display
font-display的属性
- auto 默认值
- block
- swap
- fallback 最常使用
- optional 最常使用
参考资料: