图片延迟加载的原理是什么?
图片延迟加载的原理就是先不设置img的src属性,等合适的时机(比如滚动、滑动等)再把图片真实url放到img的src属性上。
过多的图片会严重影响网页的加载速度,移动网络下的流量消耗巨大,延迟加载几乎是标配。
图片延迟加载的使用场景有哪些?
- 好奇心日报首页和列表页都有很多固定宽高的图片。
- 好奇心日报文章详情页的图片,这些图片需要自适应宽度且保持宽高比(防止页面抖动)。
固定宽高延迟加载
这个比较简单,设置好固定宽高,直接使用最简单的延迟加载即可
淘宝mobile首页的延迟加载有个点做得特别好,滚动结束后只加载当前视窗可见的图片,不会加载滚动超过视窗的图片,也不会加载还没滚动到的视窗图片。
非固定宽高的延迟加载
目前大概有两种方案,各有优劣,具体看情况使用:
第一种方案使用padding-top或者padding-bottom来实现固定宽高比。优点是纯css方案,缺点是html冗余,对输出到第三方不友好
<div style="padding-top:75%">
<img data-src="" alt="" class="lazyload">
<div>
第二种方案在页面初始化阶段利用ratio设置实际宽高值,优点是html干净,对输出到第三方友好,缺点是依赖js,理论上会至少抖动一次
<img data-src="" alt="" class="lazyload" data-ratio="0.75">
更进一步结合srcset
除了上面说的延迟加载,我们可以更进一步的引入srcset,通过设置srcset来保证加载最匹配的图片,这样对于一倍屏,二倍屏,三倍屏来说,可以做到不浪费流量且效果最好。
都有哪些延迟加载开源方案?
依赖于jquery
<img class="lazy" data-original="img/example.jpg" width="640" height="480">
// 初始化
$("img.lazy").lazyload();
原生js,不依赖于jquery/zepto
自动监测可能发生变化的lazyload节点,不需要额外初始化
支持响应式图片srcset
性能高,改善SEO
// 引入js文件
<script src="lazysizes.min.js" async=""></script>
// 非响应式 例子
<img data-src="image.jpg" class="lazyload" />
// 响应式 例子,自动计算合适的图片
<img
data-sizes="auto"
data-src="image2.jpg"
data-srcset="image1.jpg 300w,
image2.jpg 600w,
image3.jpg 900w" class="lazyload" />
// iframe 例子
<iframe frameborder="0"
class="lazyload"
allowfullscreen=""
data-src="//www.youtube.com/embed/ZfV-aYdU4uE">
</iframe>
依赖jquery/zepto
<!-- 直接赋予图片宽高 -->
<img class="lazy" data-original="img/example.jpg" width="640" height="480">
<!-- 或:通过css赋予图片宽高 -->
<style>
.lazy{width:640px;height:480px;}
</style>
<img class="lazy" data-original="img/example.jpg">
<!-- 或:自适应宽度的图片样式(常用于移动端) -->
<style>
.lazy{width:100%;height:0;padding-top:75%;background-size:100%;}
/* 假设高宽比为 480:640,即75%,并使用背景图的方式将图片铺在padding-top区域内
(padding的百分比是宽度的百分比而不是高度的,即使是padding-top|padding-bottom) */
</style>
<div class="lazy" data-original="img/example.jpg"><div>
<!-- 请参阅examples/enabled_image_full_width.html -->
<!-- 初始化 -->
$(".lazy").lazyload();
微信如何实现延迟加载?
研究了微信延迟加载的代码,还解决了一个问题,那就是常见于移动端的自适应宽度的延迟加载,即根据情况具体计算宽高。
// 源码
<img
data-s="300,640"
data-type="jpeg"
data-src="http://mmbiz.qpic.cn/mmbiz/meG6Vo0MeviaLibiaARRszfMpiaXtejcktPB2fK6uP13R4RS9Y7fHtk5bUd7A9R9zRyZ1nupW8ZVjHwBiaZUa3SkcPg/0?wx_fmt=jpeg"
data-ratio="0.8003597122302158"
data-w=""
/>
// 解析后的代码
<img
data-s="300,640"
data-type="jpeg"
data-src="http://mmbiz.qpic.cn/mmbiz/meG6Vo0MeviaLibiaARRszfMpiaXtejcktPBLbT37dSYzNyhwDTiac0WiaribF0Vt7I3Zd7AG9xXSCUoch61KicnYnfqIw/0?wx_fmt=jpeg"
data-ratio="0.8003597122302158"
data-w=""
src="http://mmbiz.qpic.cn/mmbiz/meG6Vo0MeviaLibiaARRszfMpiaXtejcktPBLbT37dSYzNyhwDTiac0WiaribF0Vt7I3Zd7AG9xXSCUoch61KicnYnfqIw/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&retryload=1"
style="width: 414px !important; visibility: visible !important; height: 331.349px !important;"
/>
/* 其中
data-s:表示可选的图片尺寸大小
data-type:表示图片类型
data-src:表示图片链接
data-ratio:表示长宽比
*/
由于源码是压缩模式的,做简单的猜测如下:
- 对于延迟加载,微信采用的模式和正常的延迟加载的模式类似,即用data-src存储真实的图片链接
- 为了解决移动端的自适应宽度的问题,微信存储了长宽比,然后进入页面就计算在不同设备里的真实宽高,并设置在style
图片响应式解决方案
背景
众所周知,retina屏的设备像素比是2,而普通屏幕像素比为1,对于图片这种位图像素已定的资源,如果不加以处理的话,要么全都用1倍图,在retina屏上观看就会模糊;要么都是用2倍图,那么在普通屏幕上浏览的时候就会白白浪费流量消耗下载速度。
简单介绍下css像素和位图像素的区别:
- 无论是retina屏还是普通屏幕,图片展示的区域大小是相同的,这也就是css像素与设备无关。
- retina屏一个1x1的css像素区域对应着2x2的物理像素,也就是每个css像素宽的区域,在retina屏上是可以再分成两半来显示的,即dpr=2的retina屏上的最小css像素分辨率是0.5px。
理论上,一个位图像素是对应一个物理像素的时候展示完美:
1、假如在retina屏使用1倍图:也就是1个位图大小的区域去用4个物理像素渲染,被分割的位图只能就近取色,就会导致图片显示模糊;
2、同样的,如果在普通屏幕上使用2x图,那么就会一个物理像素对应4个位图像素,就会通过一定算法,给该物理像素一个近似的值,(downsampling过程),图片不会模糊,但会缺失一些锐度。
目前主流方案(使用二倍图):
img{
max-width:100%;
height:auto;
}
而图片展示的情况在如今也是应用的越来越多,所以要找到两者完美匹配的方法,也就是——响应式图片。
CSS解决方案——媒体查询
最大缺点:只能用于css,所以也就限定了只有background中的图片可以使用此方法。
简介一下:
@media
only screen and (-webkit-min-device-pixel-ratio:2),
only screen and (-min-moz-device-pixel-ratio:2),//版本低于16的Firefox
only screen and (min-resolution:2dppx),
only screen and (min-resolution:192dpi){
...
}
像七牛这样专业的图片处理应用都可以根据需求生成一倍图,这样对于不同分辨率的显示器,也可以使用不同分辨率的图片。另外的不足是使用媒体查询多了不少代码,个中利弊,具体情况下再权衡吧。
resolution :定义设备的分辨率。
resolution兼容性
dppx:也是设备像素比,和dpr一样。
dpi:(Dots Per Inch)每英寸点数。
1dppx=96dpi
【小科普:1参考像素即为从一臂之遥看解析度为96DPI的设备输出即1英寸96点)时,1点(即1/6英寸)的视角。 】
另外,在最新的以webkit为内核的浏览器中,支持支持CSS4的background-image新规范草案image-set,在移动端也勉强可以接受吧。
image-set属性兼容性
selector {
background-image: url(no-image-set.png);
background: image-set(url(foo-lowres.png) 1x,url(foo-highres.png) 2x) center;
}
不支持image-set的浏览器会解析background-image中的背景图像;支持image-set的浏览器就会根据是否为retina屏选择相应的背景图,因此这个方案是可以实现向下兼容的。
HTML解决方案——srcset+sizes+w标识符
这是HTML5推出的属性,srcset可以根据显示器分辨率智能加载最佳显示的图片。
srcset属性兼容性
<img class="image" src="mm-width-128px.jpg"
srcset="mm-width-128px.jpg 128w, mm-width-256px.jpg 256w, mm-width-512px.jpg 512w"
sizes="(max-width: 360px) 340px, 128px">
srcset :指向提供的图片资源,为用户提供了一种内嵌简单的分辨率媒体查询功能;
sizes : 指定图片宽度,不能使用百分比,可使用:
px,
vw(100vw就是占满父容器宽度,所以要求图片居中宽度为百分比的地方可以使用vw单位,如 sizes=80vw),
calc运算(适用于两边距离固定的情况,如sizes="calc(100vw-20px)"),
媒体查询(如sizes="(min-width:360px) 340px,128px")。
而且最重要的是,sizes属性产生的初衷就是可以在html中实现简单的媒体查询功能,来适应越来越大规模的响应式网站开发。
那么w是个啥?
w是一个衡量宽度的标识符,一定要对应图片的真实宽度,这会使得浏览器正确的选择图片,如果w值和图片宽度不对应时,实际渲染是会有问题的。
拿这段代码来说:
<img class="image" src="test-240.jpg"
srcset="test-240.jpg 240w, test-480.jpg 480w, test-720.jpg 720w"
sizes="240px">
sizes=240px,也就是图片宽度设置为了240px,那么:
当该屏幕dpr==1时,就会选用test-240.jpg;
dpr==1
dpr==2时,可渲染的位图像素宽度就变为了480px,也就选用了test-480.jpg;
dpr==2
dpr==3时,能渲染的位图像素宽度变为了720px,那么浏览器去选择最适合的图片,也就是test-720.jpg;
dpr==3
关于w值设置如果不正确,会出现什么后果,我在这篇文章中写了详细的案例。
现代浏览器对该属性的支持是越来越多了,这个方案应该会是个潮流。
在移动端andriod browser的支持度实在是太差劲了,PC端对于一些fashion的网址试一试。
javascript解决方案
- 基于jquery的HiSRC插件,可以基于网速和是否为retina屏来显示图片。
<div class="hisrc">
<img src="placehold.it/200x100.png" data-1x="placehold.it/400x200.png" data-2x="placehold.it/800x400.png">
</div>
然后调用hisrc的方法
$(document).ready(function(){
$(".hisrc img").hisrc();
})
官方文档是这样介绍HiSRC如何工作的:正常情况下会直接加载src中的资源;如果网速较好就会加载data-1x中的资源替代原来src的文件;如果设备像素比又比较高的话,就会加载data-2x中的资源代替原来的src中的图片。
它还提供选项允许我们设置一个网速基准。这个html的结构让我不由得想起了lazyload的解决方案,这俩真的是太相似了,我们完全可以给src中放一个统一的占位图,然后再去选择加载适合浏览器展示的图片。
另外还有用于rails的gem包:hisrc-rails.
所以也可以写成这样
responsive_image_tag("http://placehold.it/100x100", :'1x' => "http://placehold.it/200x200", :'2x' => "http://placehold.it/400x400")
对于这个方案,个人觉得在工程上应用是可以期待的,因为图片都放在七牛那儿,可以图片上传成功后从2倍图中处理出1倍图,然后再向img标签中添加data-1x,data-2x这样的属性,不过呢,这好像把工作几乎全部添加给了后台的哥们儿,想到这儿,好像应用的难度瞬间增大了呢。。。
- picturefill方案,依赖picturefill.js这个脚本文件,并且它还对结构有一定的要求,对格式有特定的要求,最开始这个来自于对<picture>的支持。
<picture>
<source srcset="smaller.jpg, smaller_retina.jpg 2x" media="(max-width: 768px)">
<source srcset="default.jpg, default_retina.jpg 2x">
<img srcset="default.jpg, default_retina.jpg 2x" alt="My default image">
</picture>
<picture> element兼容性
看到该结构要写这么多代码,多少就会产生一点心理抗拒,但是呢本着技术进步的态度,还是再进一步研究下。
由于picture元素是html5的新产物,兼容性上还不是特别好,要想大规模使用可以配合picturefill.js,另外现在picturefill也支持有srcset属性的img。
这里有picturefill在应用<picture>的页面中存在的一些问题 。
//主要是IE9和安卓2.3上存在一些问题,不过IE9通过hack方法也是可以挽救的。
最后,两句话介绍一下服务端解决方案:
Adaptive Images:最大缺点基于PHP和Apache。它是拦截通过服务器的图片请求来生成图片,如果图片是通过第三方的分网网络的也就用不上了。
综上
不过既然picturefill也支持srcset,那么比较srcset属性和picture元素,浏览器对srcset属性的支持是更好的。所以srcset+sizes+w的img元素配合picturefill.js效果应该会不错。只是很可惜,这样的应用案例还没有找到。不过即使picturefill.js不能完美配合srcset方案,仅仅使用srcset+sizes+w就可以应付主流现代浏览器了,重要的是,这个方案完全也是向下兼容的啊。
为什么要优化网页加载速度?
好奇心日报无论是设计还是内容都追求高品质,于是丰富的图文混合成了标配:首页的banner图,文章详情页的配图,研究所有趣的gif图等等。
特别严重的时候,一篇文章有十多个gif图,加载花费的时间10-20秒之长,加载消耗的流量几十M之多,严重影响了用户体验!尤其是Mobile端,一寸流量一寸金;3-5s打不开页面,用户都会直接逃离。所以网页加载速度优化势在必行!
我们都知道一个网页的加载流程大致如下:
1、解析HTML结构。
2、加载外部脚本和样式表文件。
3、解析并执行脚本代码。// 部分脚本会阻塞页面的加载
4、DOM树构建完成。//DOMContentLoaded 事件
5、加载图片等外部文件。
6、页面加载完毕。//load 事件
一句话就是:请求HTML,然后顺带将HTML依赖的JS/CSS/iconfont等其他资源一并请求过来。
那么优化网页的加载速度,最本质的方式就是:减少请求数量 与 减小请求大小。
减少请求数量
1、将小图标合并成sprite图或者iconfont字体文件
2、用base64减少不必要的网络请求
3、图片延迟加载
4、JS/CSS按需打包
5、延迟加载ga统计
6、等等...
减小请求大小
1、JS/CSS/HTML压缩
2、gzip压缩
3、JS/CSS按需加载
4、图片压缩,jpg优化
5、webp优化 & srcset优化
6、等等...
JS/CSS按需打包
和JS/CSS按需加载
是两个不同的概念:JS/CSS按需打包
是预编译发生的事情,保证只打包当前页面相关的逻辑。JS/CSS按需加载
是运行时发生的事情,保证只加载当前页面第一时间使用到的逻辑。
接下来我们将结合两个本质的优化方式介绍具体的实践方法。
如何减少请求数量?
1、合并图标,减少网络请求
合并图标是减少网络请求的常见的优化手段,网页中的小图标特征是体积小、数量多,而浏览器同时发起的并行请求数量又是有限制的,所以这些小图标会严重的影响网页的加载速度,阻碍关键内容的请求和呈现
sprite图
合并sprite图是慢工细活儿,并没有特别大的技术含量,但却是每个前端开发都必须掌握的技术。
刚入门的前端直接手动切图拼图即可。
经验丰富的前端可以尝试利用构建工具实现自动化,推荐使用。gulp.spritesmith插件。
// 构建视图文件
gulp.task('sprites', function() {
var spriteData = gulp.src(config.src)
.pipe(plumber(handleErrors))
.pipe(newer(config.imgDest))
.pipe(logger({ showChange: true }))
.pipe(spritesmith({
cssName: 'sprites.css',
imgName: 'sprites.png',
cssTemplate: path.resolve('./gulp/lib/template.css.handlebars')
}));
var imgStream = spriteData.img
.pipe(buffer())
.pipe(gulp.dest(config.imgDest));
var cssStream = spriteData.css
.pipe(gulp.dest(config.cssDest));
return merge([imgStream, cssStream]);
});
sprite图不适合无线端的响应式场景,所以越来越作为一个备用方案。
iconfont字体文件
iconfont字体文件是用字体编码的形式来实现图标效果,既然是文字,那就可以随意设置颜色设置大小,相对来说比sprite方案更好。但是它只适用于纯色图标。推荐使用 阿里巴巴矢量图标库
2、用base64减少不必要的网络请求
base64码兼容性
上文提到的sprite图和iconfont字体文件,对于有些场景并不适合,比如:
1、小背景图,无法放到精灵图中,通常循环平铺小块来设置大背景。
2、小gif图,无法放到精灵图中,发请求又太浪费。
base64使用场景
注意:cssnano压缩css的时候,对于部分规则的base64 uri不能识别,会出现误伤,如下图,cssnano压缩的时候会将//
压缩为/
:
cssnano压缩base64
原因是:cssnano会跳过data:image
/data:application
后面的字符串,但是不会跳过data:img
,所以如果你使用的工具生成的是data:img
,建议换工具或者直接将其修改为data:image
。
3、图片延迟加载
图片是网页中流量占比最多的部分,也是需要重点优化的部分。
图片延迟加载的原理就是先不设置img的src属性,等合适的时机(比如滚动、滑动、出现在视窗内等)再把图片真实url放到img的src属性上。更多内容请移步上一篇博文: 图片延迟加载方案
固定宽高值的图片
固定宽高值的图片延迟加载比较简单,因为宽高值都可以设置在css中,只需考虑src的替换问题,推荐使用lazysizes。
// 引入js文件
<script src="lazysizes.min.js" async=""></script>
// 非响应式 例子
<img src="" data-src="image.jpg" class="lazyload" />
// 响应式 例子,自动计算合适的图片
<img
data-sizes="auto"
data-src="image2.jpg"
data-srcset="image1.jpg 300w,
image2.jpg 600w,
image3.jpg 900w" class="lazyload" />
// iframe 例子
<iframe frameborder="0"
class="lazyload"
allowfullscreen=""
data-src="//www.youtube.com/embed/ZfV-aYdU4uE">
</iframe>
注意:浏览器解析img标签的时候,如果src属性为空,浏览器会认为这个图片是坏掉的图,会显示出图片的边框,影响市容。
第一块是初始状态,第四块是成功状态,第二块第三块是影响市容的状态
lazysizes延迟加载过程中会改变图片的class:默认lazyload,加载中lazyloading,加载结束:lazyloaded。结合这个特性我们有两种解决上述问题办法:
1、设置opacity:0,然后在显示的时候设置opacity:1。
// 渐现 lazyload
.lazyload,
.lazyloading{
opacity: 0;
}
.lazyloaded{
opacity: 1;
transition: opacity 500ms; //加上transition就可以实现渐现的效果
}
2、用一张默认的图占位,比如1x1的透明图或者灰图。
<img class="lazyload"
src="data:image/gif;base64,R0lGODlhAQABAAA
AACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
data-src="真实url"
alt="<%= article.title %>">
此外,为了让效果更佳,尤其是文章详情页中的大图,我们可以加上loading效果。
.article-detail-bd {
.lazyload {
opacity: 0;
}
.lazyloading {
opacity: 1;
background: #f7f7f7 url(/images/loading.gif) no-repeat center;
}
}
固定宽高比的图片
固定宽高比的图片延迟加载相对来说复杂很多,比如文章详情页的图片,由于设备的宽度值不确定,所以高度值也不确定,这时候工作的重心反倒放到了如何确定图片的高度上。
为什么要确定图片的高度呢?因为单个图片的加载是从上往下,所以会导致页面抖动,不仅用户体验很差,而且对于性能消耗很大,因为每次抖动都会触发reflow(重绘)事件,之前的博文 网站性能优化 之 渲染性能 也分析过重绘对于性能的消耗问题。
固定宽高比的图片抖动问题,有下列两种主流的方式可以解决:
1、第一种方案使用padding-top或者padding-bottom来实现固定宽高比。优点是纯CSS方案,缺点是HTML冗余,并且对输出到第三方不友好。
<div style="padding-top:75%">
<img data-src="" alt="" class="lazyload">
<div>
2、第二种方案在页面初始化阶段利用ratio设置实际宽高值,优点是html干净,对输出到第三方友好,缺点是依赖js,理论上会至少抖动一次。
<img data-src="" alt="" class="lazyload" data-ratio="0.75">
那么,这个padding-top: 75%;
和data-ratio="0.75"
的数据从哪儿来呢?在你上传图片的时候,需要后台给你返回原始宽高值,计算得到宽高比,然后保存到data-ratio上。
好奇心日报采用的第二种方案,主要在于第一种方案对第三方输出不友好:需要对img设置额外的样式,但第三方平台通常不允许引入外部样式。
确定第二种方案之后,我们定义了一个设置图片高度的函数:
// 重置图片高度,仅限文章详情页
function resetImgHeight(els, placeholder) {
var ratio = 0,
i, len, width;
for (i = 0, len = els.length; i < len; i++) {
els[i].src = placeholder;
width = els[i].clientWidth; //一定要使用clientWidth
if (els[i].attributes['data-ratio']) {
ratio = els[i].attributes['data-ratio'].value || 0;
ratio = parseFloat(ratio);
}
if (ratio) {
els[i].style.height = (width * ratio) + 'px';
}
}
}
我们将以上代码的定义和调用都直接放到了HTML中,就为了一个目的,第一时间计算图片的高度值,降低用户感知到页面抖动的可能性,保证最佳效果。
// 原生代码
<img alt=""
data-ratio="0.562500"
data-format="jpeg"
class="lazyload"
data-src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg"
src="">
// 解析之后的代码
<img alt=""
data-ratio="0.562500"
data-format="jpeg"
class="lazyloaded"
data-src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg"
src="http://img.qdaily.com/uploads/20160807124000WFJNyGam85slTC4H.jpg"
style="height: 323.438px;">
我们不仅保存了宽高比,还保存了图片格式,是为了后期可以对gif做进一步的优化。
注意事项
1、避免图片过早加载,把临界值调低一点。在实际项目中,并不需要过早就把图片请求过来,尤其是Mobile项目,过早请求不仅浪费流量,也会因为请求太多,导致页面加载速度变慢。
2、为了最好的防抖效果,设置图片高度的JS代码内嵌到HTML中以便第一时间执行。
3、根据图片宽度设置高度时,使用clientWidth而不是width。这是因为Safari中,第一时间执行的JS代码获取图片的width失败,所以使用clientWidth解决这个问题。
4、JS/CSS按需打包
按需打包是webpack独特的优势,如果有需要通过此种方式来管理模块之间的依赖关系,强烈推荐引入!webpack门槛较高,可以看看我之前的博客:
webpack 入门
webpack 模块化机制
好奇心日报是典型的多页应用,为了缓存通用代码,我们使用webpack按需打包的同时,还利用webpack的CommonsChunkPlugin 插件
抽离出公用的JS/CSS代码,便于缓存,在请求数量和公用代码的缓存之间做了一个很好的平衡。
5、延迟加载ga统计
async & defer属性
html5中给script标签引入了async和defer属性。
带有async属性的script标签,会在浏览器解析时立即下载脚本同时不阻塞后续的document渲染和script加载等事件,从而实现脚本的异步加载。
带有defer属性的script标签,和async拥有类似的功能。并且他们有可以附带一个onload事件<script src="" defer onload="init()">
。
async和defer的区别在于:async属性会在脚本下载完成后无序立即执行,defer属性会在脚本下载完成后按照document结构顺序执行。
由于defer和async的兼容性问题,我们通常使用动态创建script标签
的方式来实现异步加载脚本,即document.write('<script src="" async></script>');
,该方式也可以避免阻塞。
ga统计代码
ga统计代码采用就是动态创建script标签
方案。
该方法不阻塞页面渲染,不阻塞后续请求,但会阻塞window.onload事件,页面的表现方式是进度条一直加载或loading菊花一直转。
所以我们延迟执行ga初始化代码,将其放到window.onload函数中去执行,可以防止ga脚本阻塞window.onload事件。从而让用户感受到更快的加载速度。
将ga加载绑定到onload事件上
如何减小请求大小?
1、JS/CSS/HTML压缩
这也是常规手段,就不介绍太多,主要的方式有:
1、通过构建工具实现,比如webpack/gulp/fis/grunt等。
2、后台预编译。
3、利用第三方online平台,手动上传压缩。
无论是第二种还是第三种方式,都有其局限性,第一种方法是目前的主流方式,凭借良好的插件生态,可以实现丰富的构建任务。
在好奇心日报的项目中,我们使用webpack & gulp作为构建系统的基础。
简单介绍一下JS/CSS/HTML压缩方式和一些注意事项
JS压缩
JS压缩
:使用webpack的UglifyJsPlugin
插件,同时做一些代码检测。
new webpack.optimize.UglifyJsPlugin({
mangle: {
except: ['$super', '$', 'exports', 'require']
}
})
CSS压缩
CSS压缩
:使用cssnano压缩,同时使用postcss做一些自动化操作,比如自动加前缀、属性fallback支持、语法检测等。
var postcss = [
cssnano({
autoprefixer: false,
reduceIdents: false,
zindex: false,
discardUnused: false,
mergeIdents: false
}),
autoprefixer({ browers: ['last 2 versions', 'ie >= 9', '> 5% in CN'] }),
will_change,
color_rgba_fallback,
opacity,
pseudoelements,
sorting
];
HTML压缩
HTML压缩
:使用htmlmin压缩HTML,同时对不规范的HTML写法纠正。
// 构建视图文件-build版本
gulp.task('build:views', ['clean:views'], function() {
return streamqueue({ objectMode: true },
gulp.src(config.commonSrc, { base: 'src' }),
gulp.src(config.layoutsSrc, { base: 'src' }),
gulp.src(config.pagesSrc, { base: 'src/pages' }),
gulp.src(config.componentsSrc, { base: 'src' })
)
.pipe(plumber(handleErrors))
.pipe(logger({ showChange: true }))
.pipe(preprocess({ context: { PROJECT: project } }))
.pipe(gulpif(function(file) {
if (file.path.indexOf('.html') != -1) {
return true;
} else {
return false;
}
}, htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyJS: true,
minifyCSS: true,
ignoreCustomFragments: [/<%[\s\S]*?%>/,
/<\?[\s\S]*?\?>/,
/<meta[\s\S]*?name="viewport"[\s\S]*?>/]
})))
.pipe(gulp.dest(config.dest));
});
某个第三方平台要求<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">
必须写成小数点格式,而htmlmin默认会将小数格式化为整数,所以额外添加了排除项:/<meta[\s\S]*?name="viewport"[\s\S]*?>/
。到现在都没懂这个第三方平台咋想的!
条件编译
由于好奇心日报项目较多,我们费了很大的心思抽离出前端项目,实现了前后分离。但有些场景下,我们为了将相关代码维护在一个文件中,同时又针对不同项目执行不同的逻辑,这时候,强烈推荐使用 gulp-preprocess插件 来实现条件编译。
条件编译
2、gzip压缩
gzip压缩也是比较常规的优化手段。前端并不需要做什么实际的工作,后台配置下服务器就行,效果非常明显。如果你发现你的网站还没有配置gzip,那么赶紧行动起来吧。
gzip压缩原理
如果浏览器支持gzip压缩,在发送请求的时候,请求头中会带有Accept-Encoding:gzip
。然后服务器会将原始的response
进行gzip压缩,并将gzip压缩后的response
传输到浏览器,紧接着浏览器进行gzip解压缩,并最终反馈到网页上。
支持gzip压缩的请求头
gzip压缩效果
那么gzip压缩的效果有多明显呢?保守估计,在已经完成JS/CSS/HTML压缩的基础上,还能降低60-80%左右的大小。
gzip压缩效果
但需要注意,gzip压缩会消耗服务器的性能,不能过度压缩。
所以推荐只对JS/CSS/HTML等资源做gzip压缩。图片的话,托管到第三方的图片建议开启gzip压缩,托管到自己应用服务器的图片不建议开启gzip压缩。
3、JS/CSS按需加载
和前面提到的按需打包不同。JS/CSS按需打包
是预编译发生的事情,保证只打包当前页面相关的逻辑。JS/CSS按需加载
是运行时发生的事情,保证只加载当前页面第一时间使用到的逻辑。
那么怎么实现按需加载呢?好奇心日报使用webpack提供的require
及require.ensure
方法来实现按需加载,值得一提的是,除了指定的按需加载文件列表,webpack还会自动解析回调函数的依赖及指定列表的深层次依赖,并最终打包成一个文件。
webpack按需加载
上诉代码的实现效果是:只有当点击登录按钮的时候,才会去加载登录相关的JS/CSS资源。资源在加载成功后自动执行。
4、图片压缩,jpg优化
托管到应用服务器的图片压缩
可以手动处理,也可以通过gulp子任务来处理。
手动处理的话,推荐一个网站 tinypng ,虽然是有损压缩,但压缩效果极好。
gulp子任务处理的话,推荐使用gulp-imagemin插件,自动化处理,效果也还不错。
// 图片压缩
gulp.task('images', function() {
return gulp.src(config.src)
.pipe(plumber(handleErrors))
.pipe(newer(config.dest))
.pipe(logger({ showChange: true }))
.pipe(imagemin()) // 压缩
.pipe(gulp.dest(config.dest));
});
托管到第三方平台的图片压缩
比如七牛云平台,他们会有一套专门的方案来对图片压缩,格式转换,裁剪等。只需要在url后面加上对应的参数即可,虽然偶尔会有一些小bug,但整体来说,托管方案比用自家应用服务器方案更优。
改变参数,实现不同程度的压缩
jpg优化
除了对图片进行压缩之外,对透明图床没有要求的场景,强烈建议将png转换为jpg,效果很明显!
如下图,将png格式化为jpg格式,图片相差差不多8倍!
png转jpg,体积相差八倍
再次强调,可以转换成jpg的图片,强烈建议转换成jpg!
5、webp优化 & srcset优化
webp优化
粗略看一眼,卧槽,兼容性这么差,也就安卓浏览器及chrome浏览器对它的支持还算给力。
webp兼容性
另一方面,webp优化能在jpg的基础上再降低近50%的大小。其优化效果明显。此外,如果浏览器支持webpanimation,还能对gif做压缩!
普通图片webp优化
gif图片优化
兼容性差,但效果好!最终好奇心决定尝试一下。
1、判断浏览器对webp及webpanimation的兼容性。
2、如果浏览器支持webp及webpanimation,将其替换成webp格式的图片。
鉴于浏览器对webp的支持比较局限,我们采用渐进升级的方式来优化:对于不支持webp的浏览器,不做处理;对于支持webp的浏览器,将图片src替换成webp格式。
那么如何判断webp兼容性呢?
// 检测浏览器是否支持webp
// 之所以没写成回调,是因为即使isSupportWebp=false也无大碍,但却可以让代码更容易维护
(function() {
function webpTest(src, name) {
var img = new Image(),
isSupport = false,
className, cls;
img.onload = function() {
isSupport = !!(img.height > 0 && img.width > 0);
cls = isSupport ? (' ' + name) : (' no-' + name);
className = document.querySelector('html').className
className += cls;
document.querySelector('html').className = className.trim();
};
img.onerror = function() {
cls = (' no-' + name);
className = document.querySelector('html').className
className += cls;
document.querySelector('html').className = className.trim();
};
img.src = src;
}
var webpSrc = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoB\
AAEAAwA0JaQAA3AA/vuUAAA=',
webpanimationSrc = 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAA\
SAAAAAAAAAAAAQU5JTQYAAAD/AABBTk1GJgAAAAAAAAAAAA\
AAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA';
webpTest(webpSrc, 'webp');
webpTest(webpanimationSrc, 'webpanimation');
})();
借鉴modernizr,实现了检测webp/webpanimation兼容性的函数,从代码中可以看出,检测原理就是模拟下载对应格式的图片,在异步函数中可以得到兼容性结果。
接下来就是替换url为webp格式
// 获取webp格式的src
function _getWebpSrc(src) {
var dpr = Math.round(window.devicePixelRatio || 1),
ratio = [1, 1, 1.5, 2, 2, 2],
elHtml = document.querySelector('html'),
isSupportWebp = (/(^|\s)webp(\s|$)/i).test(elHtml.className),
isSupportWebpAnimation = (/(^|\s)webpanimation(\s|$)/i).test(elHtml.className),
deviceWidth = elHtml.clientWidth,
isQiniuSrc = /img\.qdaily\.com\//.test(src),
format = _getFormat(src),
isGifWebp, isNotGifWebp, regDetailImg;
if (!src || !isQiniuSrc || !format || format == 'webp') {
return src;
}
isNotGifWebp = (format != 'gif' && isSupportWebp);
isGifWebp = (format == 'gif' && isSupportWebpAnimation);
// 根据屏幕分辨率计算大小
src = src.replace(/\/(thumbnail|crop)\/.*?(\d+)x(\d+)[^\/]*\//ig, function(match, p0, p1, p2) {
if(dpr > 1){
p1 = Math.round(p1 * ratio[dpr]);
p2 = Math.round(p2 * ratio[dpr]);
match = match.replace(/\d+x\d+/, p1 + 'x' + p2)
}
return match;
});
if(isNotGifWebp || isGifWebp) {
// 替换webp格式,首页/列表页
src = src.replace(/\/format\/([^\/]*)/ig, function(match, p1) {
return '/format/webp';
});
}
}
注意事项
1、window的屏幕像素密度不一定是整数,mac浏览器缩放之后,屏幕像素密度也不是整数。所以获取dpr一定要取整:dpr = Math.round(window.devicePixelRatio || 1);
。
2、ratio = [1, 1, 1.5, 2, 2, 2]
表示:1倍屏使用1倍图,2倍屏使用1.5倍图,3倍屏以上都用2倍图。这儿的规则可以按实际情况来设置。
3、webp优化更适合托管到第三方的图片,简单修改参数就可以获取不同的图片。
devicePixelRatio兼容性
srcset兼容性
srcset兼容性
如上所述,在对webp优化的时候,我们顺道模拟实现了srcset:根据屏幕像素密度来设置最适合的图片宽高。
lazysizes原本提供了srcset选项,也可以借用lazysizes的方案来实现srcset,有兴趣的可以去看看源码。