整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

一个迅速提升网站用户体验和SEO效果的方法(实证有效

一个迅速提升网站用户体验和SEO效果的方法(实证有效)

站页面速度提升,用户停留时间会变长,跳出率会降低。

尤其在移动端更加明显,谷歌和百度都做过类似的分析和研究,结论很接近:

当页面加载速度从1秒变成3秒时,跳出率提升了32%。

当页面加载速度从1秒变成5秒时,跳出率提升了90%。

换句话说,如果你的移动端页面5秒才能打开,90%的人都会关掉页面离开。

国外几个网站测试过速度下降对业务指标的负面影响

Bing - 网页速度打开慢2秒,(收入/用户)比下降4.3%。

Google - 400毫秒的延迟导致(搜索量/用户)下降0.59%。

雅虎 - 400毫秒的延迟导致全站流量下降5-9%。

提升页面速度的好处:

Shopzilla - 网站提速5秒,转换率提升了7-12%,SEM投放会话量增加了一倍,所需服务器减少了一半。

Mozilla - 着陆页打开速度比原来快了2.2秒后,下载转化率提升了15.4%,因此每年多增加6000万次Firefox下载。

Netflix - 仅仅因为使用了Gzip压缩,页面速度就提升了13-25%,带宽费用减少了50%。

谷歌博客2010年就说过:“我们已经决定把网站的打开速度作为搜索排名因素。”

如何提升页面打开速度呢?

页面打开速度受非常多因素影响,简单的可以划分为“前端”和“后端”两部分。

“后端”包含了服务器性能、功能实现方法、页面模块、缓存时间、CDN加速等等,受后端技术和硬件条件的制约和影响。

这部分工作一般由研发工程师和运维来负责解决。实现成本或高获低,存在不少不确定性。

而“前端”部分则相对清晰,按照优化规范的指导,前端工程师可以立即对网页进行“瘦身”。

转载Github上翻译的前端优化指南如下。(待补充完善本文)

前端优化指南

避免 内联式/嵌入式 代码

你可以通过三种方式在HTML页面中引入CSS或Javascript代码:

1) 内联式: 在HTML标签的style属性中定义样式,在onclick这样的属性中定义Javascript代码;

2) 嵌入式: 在页面中使用<style>标签定义样式,使用<script>标签定义Javascript代码;

3) 引用外部文件: 在<link>标签中定义href属性引用CSS文件,在<script>标签中定义src属性引入Javascript文件.

尽管前两种方式减少了HTTP请求数,可是实际上却增加了HTML文档的体积。不过,当你的页面中的CSS或者Javascript代码足够少,反而是开启一个HTTP请求的花费要更大时,采用这两种方式却是最有用的。因此,你需要测试评估这种方式是否真的提升了速度。同时也要考虑到你的页面的目标和它的受众:如果你期望人们只会访问它一次,例如对一些临时活动来说,你决不会期望有回访客出现,那么使用内联式/嵌入式代码能够帮助减少HTTP请求数。

> 尽量避免在你的HTML中手工编写CSS/JS(首选的方法是通过工具实现这个过程的自动化)。

第三种方式不仅使你的代码更有序,而且使得浏览器能够缓存它。这种方式在大多数的情况下都是首选,特别是一些大文件和多页面的情况。

> 小工具 / 参考

样式在上,脚本在下

当我们把样式放在<head>标签中时,浏览器在渲染页面时就能尽早的知道每个标签的样式,我们的用户就会感觉这个页面加载的很快。

<head> <meta charset="UTF-8"> <title>Browser Diet</title> <!-- CSS --> <link rel="stylesheet" href="style.css" media="all"></head>

但是如果我们将样式放在页面的结尾,浏览器在渲染页面时就无法知道每个标签的样式,直到CSS被下载执行后。

另一方面,对于Javascript来说,因为它在执行过程中会阻塞页面的渲染,所以我们要把它放在页面的结尾。

<body> <p>Lorem ipsum dolor sit amet.</p> <!-- JS --> <script src="script.js"></script></body>

> 参考

尝试async

为了解释这个属性对于性能优化是多么有用,我们应该先明白,当不使用它时会发生什么。

<script src="example.js"></script>

使用上面这种方式时,页面会在这个脚本文件被完全下载、解析、执行完后才去渲染之后的HTML,在这之前会一直处于阻塞状态。这就意味着会增加你的页面的加载时间。有时这种行为是我们希望的,而大多数时候则不想要。

<script async src="example.js"></script>

使用上面这种方式时,脚本的加载是异步的,不会影响到这之后的页面解析。脚本会在下载完之后立即执行。需要注意的是,如果有多个使用这种方式异步加载的脚本,他们是没有特定的执行顺序的。

> 参考

压缩你的样式表

为了保持代码的可读性,最好的方法是在代码中添加注释和使用缩进:

.center { width: 960px; margin: 0 auto;}/* --- Structure --- */.intro { margin: 100px; position: relative;}

但是对于浏览器来说,这些都是不重要的。正因为如此,通过自动化工具压缩你的CSS是非常有用的。

.center{width:960px;margin:0 auto}.intro{margin:100px;position:relative}

这样做能够减小文件的大小,从而得到更快的下载、解析和执行。

对于使用预处理器例如 Sass, Less, and Stylus, 你可以通过配置缩小编译输出的CSS代码。

> 小工具 / 参考

合并多个CSS文件

对于样式的组织和维护,另一个好方法是将他们模块化。

<link rel="stylesheet" href="structure.css" media="all"><link rel="stylesheet" href="banner.css" media="all"><link rel="stylesheet" href="layout.css" media="all"><link rel="stylesheet" href="component.css" media="all"><link rel="stylesheet" href="plugin.css" media="all">

然而,这样每个文件就是一个HTTP请求(我们都知道,浏览器的并行下载数是有限的)。

<link rel="stylesheet" href="main.css" media="all">

所以,合并你的CSS文件。文件数量的减少就会带来请求数量的减少和更快的页面加载速度。

Want to have the best of both worlds? Automate this process through a build tool.

> 小工具 / 参考

使用 标签而不是 @import

有两种方式可以引入一个外部的样式表:通过 <link> 标签:

<link rel="stylesheet" href="style.css">

或者通过 @import 指令 (使用在一个外部样式表中或者页面内嵌的 <style> 标签中):

@import url('style.css');

当你在一个外部样式表中使用第二种方式时,浏览器无法通过并行下载的方式下载这个资源,这样就会导致其他资源的下载被阻塞。

> 参考

异步加载第三方内容

嵌入一个Youtube视频或者一个like/tweet按钮,有人没有加载过这样的第三方内容吗?

问题在于,不管是用户端的还是服务器端的连接,都无法保证这些代码是正常有效的工作的。这些服务有可能临时dowan掉或者是被用户或者其公司的防火墙阻止。

为了避免这些在页面加载时成为问题,或者更严重的是,阻塞了全部页面的加载,总是应该异步加载这些代码 (或者使用 Friendly iFrames).

var script=document.createElement('script'), scripts=document.getElementsByTagName('script')[0];script.async=true;script.src=url;scripts.parentNode.insertBefore(script, scripts);

另外,如果你想加载多个第三方插件,你可以使用这个代码来实现异步的加载。

> 视频 / 参考

缓存数组长度

循环无疑是和Javascript性能非常相关的一部分。试着优化循环的逻辑,从而让每次循环更加的高效。

要做到这一点,方法之一是存储数组的长度,这样的话,在每次循环时都不用重新计算。

var arr=new Array(1000), len, i;for (i=0; i < arr.length; i++) { // Bad - size needs to be recalculated 1000 times}for (i=0, len=arr.length; i < len; i++) { // Good - size is calculated only 1 time and then stored}

> Results on JSPerf

> 注解:虽然现代浏览器引擎会自动优化这个过程,但是不要忘记还有旧的浏览器

在迭代document.getElementsByTagName('a')等类似方法生成的HTML节点数组(NodeList)时,缓存数组长度尤为关键。这些集合通常被认为是“活的”,也就是说,当他们所对应的元素发生变化时,他们会被自动更新。

var links=document.getElementsByTagName('a'), len, i;for (i=0; i < links.length; i++) { // Bad - each iteration the list of links will be recalculated to see if there was a change}for (i=0, len=links.length; i < len; i++) { // Good - the list size is first obtained and stored, then compared each iteration}// Terrible: infinite loop examplefor (i=0; i < links.length; i++) { document.body.appendChild(document.createElement('a')); // each iteration the list of links increases, never satisfying the termination condition of the loop // this would not happen if the size of the list was stored and used as a condition}

> 参考

避免使用document.write

这个(坏)方法已经被开发者抛弃了很多年, 但是在某些情况下仍然是需要的,例如在一些Javascript文件的同步回退中。

举例来说,如果发现Google的CDN没有响应,HTML5 Boilerplate则会通过这个方法来调用本地的jQuery库。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script><script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.0.min.js"><\/script>')</script>

> 注意:如果在window.onload事件中或之后执行document.write方法,会将当前页面替换掉。

<span>foo</span><script> window.onload=function() { document.write('<span>bar</span>'); };</script>

这段代码执行后的结果是页面中只会呈现出bar字符,而不是期望的出现foobar。在window.onload事件之后执行也是同样的结果。

<span>foo</span><script> setTimeout(function() { document.write('<span>bar</span>'); }, 1000); window.onload=function() { // ... };</script>

这段代码执行的结果和上一段代码的结果相同。

> 参考

最小化重绘和回流

当有任何属性或元素发生改变时,都会引起DOM元素的重绘和回流。

当一个元素的布局不变,外观发生改变时,就会引起重绘。Nicole Sullivan描述这个就像是样式的改变,例如改变background-color。

回流的代价是最高的,当改变一个页面的布局时就会发生回流,例如改变一个元素的宽。

毫无疑问,应当避免过多的重绘和回流,所以,对于下面的代码:

var div=document.getElementById("to-measure"), lis=document.getElementsByTagName('li'), i, len;for (i=0, len=lis.length; i < len; i++) { lis[i].style.width=div.offsetWidth + 'px';}

应当变为:

var div=document.getElementById("to-measure"), lis=document.getElementsByTagName('li'), widthToSet=div.offsetWidth, i, len;for (i=0, len=lis.length; i < len; i++) { lis[i].style.width=widthToSet + 'px';}

当你设置style.width时,浏览器需要重新计算布局。通常,浏览器暂时是不需要知道改变了元素的样式的,直到它需要更新屏幕时,正因为如此,改变多个元素的样式只会产生一次回流。然而,在第一个例子中,我们每次请求offsetWidth时,都会使浏览器重新计算布局。

如果需要得到页面中的布局数据,那么请参照第二个例子,将这些操作放在任何会改变布局的设置前。

> 示例 / 参考

避免不必要的DOM操作

当你获得DOM而又什么都不做时,这简直就是在杀死宝贵的生命。

说真的,浏览器遍历DOM元素的代价是昂贵的。虽然Javascript引擎变得越来越强大,越来越快速,但是还是应该最大化的优化查询DOM树的操作。

最简单的替代方案就是,当一个元素会出现多次时,将它保存在一个变量中,这样的话你就没必要每次都去查询DOM树了。

// really bad!for (var i=0; i < 100; i++) { document.getElementById("myList").innerHTML +="<span>" + i + "</span>";}
// much better :)var myList="";for (var i=0; i < 100; i++) { myList +="<span>" + i + "</span>";}document.getElementById("myList").innerHTML=myList;
// much *much* better :)var myListHTML=document.getElementById("myList").innerHTML;for (var i=0; i < 100; i++) { myListHTML +="<span>" + i + "</span>";}

> Results on JSPerf

压缩你的脚本

和CSS一样,为了保持代码的可读性,最好的方法是在代码中添加注释和使用缩进:

BrowserDiet.app=function() { var foo=true; return { bar: function() { // do something } };};

但是对于浏览器来说,这些都是不重要的。正因为如此,请记住用自动化工具压缩你的Javascript代码。

BrowserDiet.app=function(){var a=!0;return{bar:function(){}}}

这样做能够减小文件的大小,从而得到更快的下载、解析和执行。

> 小工具 / 参考

将多个JS文件合并

对于脚本的组织和维护,另一个好方法是将他们模块化。

<script src="navbar.js"></script><script src="component.js"></script><script src="page.js"></script><script src="framework.js"></script><script src="plugin.js"></script>

然而,这样每个文件就是一个HTTP请求(我们都知道,浏览器的并行下载数是有限的)。

<script src="main.js"></script>

所以,合并你的JS文件。文件数量的减少就会带来请求数量的减少和更快的页面加载速度。

想要两全其美?通过构建工具自动化这个过程吧。

> 小工具 / 参考

jQuery Selectors

在使用jQuery时,选择器也是一个重要的问题。有许多方法可以从DOM中选取元素,但这不意味着这些方法有相同的性能,你可以用classes、IDs或者find()、children()等方法选取元素。

在这些方法中,使用ID选择器是最快的,因为它是原生DOM操作。

$("#foo");

> Results on JSPerf

JavaScript中使用for,而不是each

原生Javascript中的函数执行几乎总是要比jQuery快一些。正因为如此,请使用Javascript的for循环,不要使用jQuery.each方法。

但是请注意,虽然for in是原生的,可是在许多情况下,它的性能要比jQuery.each差一些。

在for循环迭代时,请记得缓存集合的长度。

for ( var i=0, len=a.length; i < len; i++ ) { e=a[i];}

在社区中,关于while和for循环的反向使用问题是一个热门话题,而这经常被认为是最快的迭代方式。然而实际上,这通常只是为了防止循环不够清晰。

// 逆转 whilewhile ( i-- ) { // ...}// 逆转 forfor ( var i=array.length; i--; ) { // ...}

> Results on JSPerf / 参考

使用CSS Sprites

这个技术就是将各种图片整合到一个文件中去。

然后通过CSS去定位它们。

.icon-foo { background-image: url('mySprite.png'); background-position: -10px -10px;}.icon-bar { background-image: url('mySprite.png'); background-position: -5px -5px;}

这么做的结果就是,减少了HTTP请求数,避免延迟页面上的其他资源。

在使用sprite时,应当避免在每个图片之间的空隙过大。这个虽然不会影响到文件的大小,但是会影响到内存的消耗。

尽管每个人都知道sprites,但是这种技术并没有被广泛使用—或许是由于开发者没有使用自动化工具去生成。 我们着重介绍了一些工具,或许可以帮到你。

> 小工具 / 参考

适当时可以使用Data URI显示图片

这种技术是CSS Sprites的替代方法。

Data-URI是指使用图片的数据代替通常使用的图片URI,在下面的例子中,我们就使用它减少了HTTP请求数。

使用前:

.icon-foo { background-image: url('foo.png');}

使用后:

.icon-foo { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D');}

所有的现代浏览器和IE8及以上版本的IE都支持这个方法,图片需要使用base64方法编码。

这种技术和CSS Sprites技术都是可以使用构建工具得到的。使用构建工具的好处是不用手工去进行图片的拼合替换,在开发时使用单独的文件就可以。

然而坏处是,随着你的HTML/CSS文件的增大增多,你必须考虑你可能会有一个非常大的图片。如果你在HTTP请求中没有使用gzip技术压缩你的HTML/CSS,那么我们不推荐使用这种方法,因为减少HTTP请求数得到的大文件对于速度来说可能带来相反的结果。

> 小工具 / 参考

不要在<img>标签中调整图像

总是在img标签中设置width和height属性。这样可以防止渲染过程中的重绘和回流。

<img width="100" height="100" src="logo.jpg" alt="Logo">

知道这个之后,一个开发者将一个700x700px的图像设置为50x50px来显示。

但是这个开发者不知道的是,大量的没有用的数据也发送到了客户端。

所以请记住:你可以在标签中定义一个图片的寬高,但不意味着你应该通过这么做来(等比)缩放大图。

> 参考

优化你的图片。

图片文件中包含许多对于Web来说没有用的东西。举例来说,一个JPEG图片中可能包含一些Exif元数据(数据,相机型号,坐标等等)。一个PNG图片会包含有关颜色,元数据的信息,有时甚至还包含一个缩略图。这些只会增加文件的大小,而对于浏览器来说却毫无用处。

有很多工具能够帮你从图片中去除这些信息,并且不会降低图片的质量。我们把这个称做无损压缩。

另一种优化图片的方式是,以图片质量为代价进行压缩。我们称之为有损压缩。举例来说,当你导出一个JPEG图片时,你可以选择导出的图片质量(从0到100)。考虑到性能,总是选择可接受范围内的最低值。在PNG图片中,另一个常见的有损技术是减少颜色数量,或者将PNG-24格式转换为PNG-8格式。

为了提升用户的体验,你还应该将你的JPEG文件转换为渐进式的。现在大多数的浏览器都支持渐进式JPEG文件,并且这种格式的文件创建简单,没有明显的性能损失问题。页面中的这种格式的图片能够更快的展现(看例子).

> 小工具 / 参考

诊断工具检查你的优化成果,查看优化建议

如果你想知道这个世界上的Web性能,那么你一定要给你的浏览器安装YSlow 从现在起,它们将是你最好的朋友。

或者你可以选择使用在线工具,访问WebPageTest, HTTP Archive或者PageSpeed。

以上一些网站有可能需要科学上网,国内可以使用百度统计后台的测速工具。

百度统计后台菜单底部有一个“网站速度诊断”,添加网址就可以测速,还会给出诊断优化建议,有网通和电信两条线路。

附录

不少团队在前端优化方面也做了很多实践,非常有学习价值:

前端优化实践总结 | http://Aotu.io「凹凸实验室」

https://aotu.io/notes/2016/04/12/jcloud-opt/index.html

移动H5前端性能优化指南 - 前端技术 - 腾讯ISUX

https://isux.tencent.com/h5-performance.html

Web 前端优化专题 - DBA Notes

http://dbanotes.net/web-performance.html

雅虎前端优化35条规则翻译

https://github.com/creeperyang/blog/issues/1

浅析渲染引擎与前端优化-京东

https://jdc.jd.com/archives/2806

言:对于大多数前端工程师来说,图片就是UI设计师(或者自己)切好的图,你要做的只是把图片丢进项目中,然后用以链接的方式呈现在页面上,而且我们也经常把精力放在项目的打包优化构建上,如何分包,如何抽取第三方库……..有时我们会忘了,图片才是一个网站最大头的那块加载资源(见下图),虽然图片加载可以不不阻碍页面渲染,但优化图片,绝对可以让网站的体验提升一个档次。

1.选择图片格式

如果效果真的需要图片来表现,那么选择图片格式是优化的第一步。我们经常听到的词语包括矢量图、标量图、SVG、有损压缩、无损压缩等等,我们首先说明各种图片格式的特点

图片格式压缩方式透明度动画浏览器兼容适应场景JPEG有损压缩不支持不支持所有复杂颜色及形状、尤其是照片 渐进式吃cpuGIF无损压缩支持支持所有简单颜色,动画PNG无损压缩支持不支持所有需要透明时,但是体积太大APNG无损压缩支持支持FirefoxSafariiOS Safari需要半透明效果的动画WebP有损压缩支持支持ChromeOperaAndroid ChromeAndroid Browser复杂颜色及形状浏览器平台可预知SVG无损压缩支持支持所有(IE8以上)简单图形,需要良好的放缩体验需要动态控制图片特效


2.从图片大小开始优化

压缩图片可以使用统一的压缩工具 — imagemin,它是一款可以集成多个压缩库的工具,支持jpg,png,webp等等格式的图片压缩,比如pngquant,mozjpeg等等,作为测试用途,我们可以直接安装imagemin-pngquant来尝试png图片的压缩

  1. png压缩
  npm install imagemin
  npm install imagemin-pngquant
``

先安装imagemin库,再安装对应的png压缩库
```js
    const imagemin=require('imagemin');
    const imageminPngquant=require('imagemin-pngquant');

    (async ()=> {
        await imagemin(['images/*.png'], 'build/images', {
            plugins: [
                imageminPngquant({ quality: '65-80' })
            ]
        });

        console.log('Images optimized');
    })();

quailty一项决定压缩比率,65-80貌似是一个在压缩率和质量之间实现平衡的数值


  1. PG/JPEG压缩与渐进式图片 压缩jpg/jpeg图片的方式与png类似,imagemin提供了两个插件:jpegtrain和mozjpeg供我们使用。一般我们选择mozjpeg,它拥有更丰富的压缩选项:
npm install imagemin-mozjpeg
    const imagemin=require('imagemin');
    const imageminMozjpeg=require('imagemin-mozjpeg');

    (async ()=> {
        await imagemin(['images/*.jpg'], 'build/images', {
            use: [
                imageminMozjpeg({ quality: 65, progressive: true })
            ]
        });

        console.log('Images optimized');
    })();
    

注意到我们使用了progressive:true选项,这可以将图片转换为渐进式图片,关于渐进式图片,它允许在加载照片的时候,如果网速比较慢的话,先显示一个类似模糊有点小马赛克的质量比较差的照片,然后慢慢的变为清晰的照片:

渐进式图片 Progressive JPEG

Progressive JPEG文件包含多次扫描,这些扫描顺寻的存储在JPEG文件中。打开文件过程中,会先显示整个图片的模糊轮廓,随着扫描次数的增加,图片变得越来越清晰。这种格式的主要优点是在网络较慢的情况下,可以看到图片的轮廓知道正在加载的图片大概是什么。在一些网站打开较大图片时,你就会注意到这种技术。



非渐进式的图片(Baseline JPEG)

这种类型的JPEG文件存储方式是按从上到下的扫描方式,把每一行顺序的保存在JPEG文件中。打开这个文件显示它的内容时,数据将按照存储时的顺序从上到下一行一行的被显示出来,直到所有的数据都被读完,就完成了整张图片的显示。如果文件较大或者网络下载速度较慢,那么就会看到图片被一行行加载的效果,这种格式的JPEG没有什么优点,因此,一般都推荐使用Progressive JPEG。

基本JPEG和渐进JPEG该什么时候使用?

当您的JPEG图像低于10K时,最好保存为基本JPEG(估计有75%的可能性会更小) 对于超过10K的文件,渐进式JPEG将为您提供更好的压缩(在94%的情况下) Chrome + Firefox + IE9浏览器下,渐进式图片加载更快,而且是快很多,至于其他浏览器,与基本式图片的加载一致,至少不会拖后腿。

渐进式图片也有不足,就是吃CPU吃内存。

总结一下两者的区别:

渐进式jpeg(progressive jpeg)图片及其相关 简单来说,渐进式图片一开始就决定了大小,而不像Baseline图片一样,不断地从上往下加载,从而造成多次回流,但渐进式图片需要消耗CPU去多次计算渲染,这是其主要缺点。 当然,交错式png也可以实现相应的效果,但目前pngquant没有实现转换功能,但是ps中导出png时是可以设置为交错式的。

那我们怎么查看图片是渐进式还是基本的呢

通过对比保存的图片格式(格式在线分析:https://exif.tuchong.com/)

也有一些网上推荐的转化工具

https://www.imgonline.com.ua/eng/compress-image.php

http://www.imagemagick.org/script/download.php


说了这么多,是不是感觉很啰嗦,接下来我们在实际项目中如何操作

实际项目中,总不能UI丢一个图过来你就跑一遍压缩代码吧?幸好imagemin有对应的webpack插件,在webpack遍地使用的今天,我们可以轻松实现批量压缩:

先安装imagemin-webpack-plugin

npm install imagemin-webpack-plugin
    import ImageminPlugin from 'imagemin-webpack-plugin'
    import imageminMozjpeg from 'imagemin-mozjpeg'

    module.exports={
      plugins: [
        new ImageminPlugin({
          plugins: [
            imageminMozjpeg({
              quality: 100,
              progressive: true
            })
          ]
        })
      ]
    }

接着在webpack配置文件中,引入自己需要的插件,使用方法完全相同。具体可参考github的文档imagemin-webpack-plugin

同时我们推荐几种比较好用的图片压缩工具

1. docsmall在线图片压缩

https://docsmall.com/

国内公司开发在线图片压缩工具

服务器在国内,上传速度很快

页面简洁无广告,美观大方

压缩率很好,基本能压缩到原来的一半以下

压缩出的图片画质很清晰,跟原图几乎没有差别

对png、jpg格式的支持都很好

还有针对PDF的压缩功能

2. tinypng

https://tinypng.com/

国外团队开发的在线图片压缩网站,有口皆碑

唯一的问题就是上传速度不够快,毕竟是国外的

界面全英文,对英语不好的朋友来说不够友好

3. 智图

https://zhitu.isux.us/

腾讯的一个团队出品

可以自定义压缩比例,如果压出来的体积不够小,你还可以选择一个更高的压缩率

保证图片体积够小


3.通过图片按需加载减少请求压力

图片按需加载是个老生常谈的话题,传统做法自然是通过监听页面滚动位置,符合条件了再去进行资源加载,我们看看如今还有什么方法可以做到按需加载。

使用强大的IntersectionObserver IntersectionObserver提供给我们一项能力:可以用来监听元素是否进入了设备的可视区域之内,这意味着:我们等待图片元素进入可视区域后,再决定是否加载它,毕竟用户没看到图片前,根本不关心它是否已经加载了。 这是Chrome51率先提出和支持的API,而现在,各大浏览器对它的支持度已经有所改善(除了IE,全线崩~) 废话不多说,上代码: 首先,假设我们有一个图片列表,它们的src属性我们暂不设置,而用data-src来替代:

    <li>
      <img class="list-item-img" alt="loading" data-src='a.jpg'/>
    </li>
    <li>
      <img class="list-item-img" alt="loading" data-src='b.jpg'/>
    </li>
    <li>
      <img class="list-item-img" alt="loading" data-src='c.jpg'/>
    </li>
    <li>
      <img class="list-item-img" alt="loading" data-src='d.jpg'/>
    </li>

这样会导致图片无法加载,这当然不是我们的目的,我们想做的是,当IntersectionObserver监听到图片元素进入可视区域时,将data-src”还给”src属性,这样我们就可以实现图片加载了:

    const observer=new IntersectionObserver(function(changes) {
      changes.forEach(function(element, index) {
       // 当这个值大于0,说明满足我们的加载条件了,这个值可通过rootMargin手动设置
        if (element.intersectionRatio > 0) {
          // 放弃监听,防止性能浪费,并加载图片。
          observer.unobserve(element.target);
          element.target.src=element.target.dataset.src;
        }
      });
    });
    function initObserver() {
      const listItems=document.querySelectorAll('.list-item-img');
      listItems.forEach(function(item) {
       // 对每个list元素进行监听
        observer.observe(item);
      });
    }
    initObserver();

运行代码并观察控制台的Network,会发现图片随着可视区域的移动而加载,我们的目的达到了。


IntersectionObserver

浏览器兼容

还是Chrome的黑科技——loading属性

从新版本Chrome(76)开始,已经默认支持一种新的html属性——loading,它包含三种取值:auto、lazy和eager(ps: 之前有文章说是lazyload属性,后来chrome的工程师已经将其确定为loading属性,原因是lazyload语义不够明确),我们看看这三种属性有什么不同:

  1. auto:让浏览器自动决定是否进行懒加载,这其中的机制尚不明确。
  2. lazy:明确地让浏览器对此图片进行懒加载,即当用户滚动到图片附近时才进行加载,但目前没有具体说明这个“附近”具体是多近。
  3. eager:让浏览器立刻加载此图片



这个现象跟chrome的lazy-loading功能的实现机制有关:

首先,浏览器会发送一个预请求,请求地址就是这张图片的url,但是这个请求只拉取这张图片的头部数据,大约2kb,具体做法是在请求头中设置range: bytes=0-2047,



而从这段数据中,浏览器就可以解析出图片的宽高等基本维度,接着浏览器立马为它生成一个空白的占位,以免图片加载过程中页面不断跳动,这很合理,总不能为了一个懒加载,让用户牺牲其他方面的体验吧?这个请求返回的状态码是206,表明:客户端通过发送范围请求头Range抓取到了资源的部分数据,详细的状态码解释可以看看这篇文章

然后,在用户滚动到图片附近时,再发起一个请求,完整地拉取图片的数据下来,这个才是我们熟悉的状态码200请求。

可以预测到,如果以后这个属性被普遍使用,那一个服务器要处理的图片请求连接数可能会变成两倍,对服务器的压力会有所增大,但时代在进步,我们可以依靠http2多路复用的特性来缓解这个压力,这时候就需要技术负责人权衡利弊了

要注意,使用这项特性进行图片懒加载时,记得先进行兼容性处理,对不支持这项属性的浏览器,转而使用JavaScript来实现,比如上面说到的IntersectionObserver:

    if ("loading" in HTMLImageElement.prototype) {
      // 支持loading
    } else {
      // .....
    }

4.通过占位图解决网速较慢视觉空白问题

当网速慢的时候,图片还没加载完之前,用户会看到一段空白的时间,在这段空白时间,就算是渐进式图片也无法发挥它的作用,我们需要更友好的展示方式来弥补这段空白,有一种方法简单粗暴,那就是用一张占位图来顶替,这张占位图被加载过一次后,即可从缓存中取出,无须重新加载,但这种图片会显得有些千篇一律,并不能很好地做到preview的效果。

这里介绍另一种占位图做法——css渐变色背景,原理很简单,当img标签的图片还没加载出来,我们可以为其设置背景色,比如:

<img src="a.jpg" style="background: red;"/>

这样会先显示出红色背景,再渲染出真实的图片,重点来了,我们此时要借用工具为这张图片"配制"出合适的渐变背景色,以达到部分preview的效果,我们可以使用 https://calendar.perfplanet.com/2018/gradient-image-placeholders/ 这篇文章中推荐的工具GIP进行转换 ,这里附上在线转换的地址 https://tools.w3clubs.com/gip/

经过转换后,我们得到了下面这串代码:

background: linear-gradient(
      to bottom,
      #1896f5 0%,
      #2e6d14 100%
    )

5.响应式图片的实践

我们经常会遇到这种情况:一张在普通笔记本上显示清晰的图片,到了苹果的Retina屏幕或是其他高清晰度的屏幕上,就变得模糊了。

这是因为,在同样尺寸的屏幕上,高清屏可以展示的物理像素点比普通屏多,比如Retina屏,同样的屏幕尺寸下,它的物理像素点的个数是普通屏的4倍(2 * 2),所以普通屏上显示清晰的图片,在高清屏上就像是被放大了,自然就变得模糊了,要从图片资源上解决这个问题,就需要在设备像素密度为2的高清屏中,对应地展示一张两倍大小的图。

而通常来讲,对于背景图片,我们可以使用css的@media进行媒体查询,以决定不同像素密度下该用哪张倍图,例如:

.bg {
    background-image: url("bg.png");
    width: 100px;
    height: 100px;
    background-size: 100% 100%;
}
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2)
{
    .bg {
        background-image: url("bg@2x.png") // 尺寸为200 * 200的图
    }
}

这么做有两个好处,一是保证高像素密度的设备下,图片仍能保持应有的清晰度,二是防止在低像素密度的设备下加载大尺寸图片造成浪费。

那么如何处理img标签呢?

我们可以使用HTML5中img标签的srcset来达到这个效果,看看下面这段代码:

<img width="320"  src="bg@2x.png" srcset="bg.png 1x;bg@2x.png 2x"/>

这段代码的作用是:当设备像素密度,也就是dpr(devicePixelRatio)为1时,使用bg.png,为2时使用二倍图bg@2x.png,依此类推,你可以根据需要设置多种精度下要加载的图片,如果没有命中,浏览器会选择最邻近的一个精度对应的图片进行加载。 要注意:老旧的浏览器不支持srcset的特性,它会继续正常加载src属性引用的图像。

要同时适配不同像素密度、不同大小的屏幕,应该怎么办呢?

<picture>
  <source media="(max-width: 500px)" srcset="cat-vertical.jpg">
  <source media="(min-width: 501px)" srcset="cat-horizontal.jpg">
  <img src="cat.jpg" alt="cat">
</picture>

就要用到标签。它是一个容器标签,内部使用和,指定不同情况下加载的图像。

上面代码中,标签内部有两个标签和一个标签。

标签的media属性给出媒体查询表达式,srcset属性就是标签的srcset属性,给出加载的图像文件。sizes属性其实这里也可以用,但由于有了media属性,就没有必要了。

浏览器按照标签出现的顺序,依次判断当前设备是否满足media属性的媒体查询表达式,如果满足就加载srcset属性指定的图片文件,并且不再执行后面的标签和标签。

标签是默认情况下加载的图像,用来满足上面所有都不匹配的情况。

上面例子中,设备宽度如果不超过500px,就加载竖屏的图像,否则加载横屏的图像。

标签的type属性

除了响应式图像,标签还可以用来选择不同格式的图像。比如,如果当前浏览器支持 Webp 格式,就加载这种格式的图像,否则加载 PNG 图像。

<picture>
  <source type="image/svg+xml" srcset="logo.xml">
  <source type="image/webp" srcset="logo.webp"> 
  <img src="logo.png" alt="ACME Corp">
</picture>

上面代码中,标签的type属性给出图像的 MIME 类型,srcset是对应的图像 URL。

浏览器按照标签出现的顺序,依次检查是否支持type属性指定的图像格式,如果支持就加载图像,并且不再检查后面的标签了。上面例子中,图像加载优先顺序依次为 svg 格式、webp 格式和 png 格式。

6.自动优化:CDN

使用CDN对图片自动进行优化,我在国外的CDN提供商处很少见到这类服务,倒是国内的两大新秀CDN七牛和又拍在这方面都做了大量工作。其工作方式为,向CDN请求图片的URL参数中包含了图片处理的参数(格式、宽高等),CDN服务器根据请求生成所需的图片,发送到用户浏览器。

七牛云存储的图片处理接口极其丰富,覆盖了图片的大部分基本操作,例如:

图片裁剪,支持多种裁剪方式(如按长边、短边、填充、拉伸等) 图片格式转换,支持JPG, GIF, PNG, WebP等,支持不同的图片压缩率 图片处理,支持图片水印、高斯模糊、重心处理等

当然其他cdn对于图像处理也有很丰富的处理,相关文档里也介绍很详细,可以参考cdn文档

阿里云

腾讯

我们通过如下URL请求,裁剪正中部分,等比缩小生成200x200缩略图:

http://qiniuphotos.qiniudn.com/gogopher.jpg?imageView2/1/w/200/h/200

七牛cdn

7.对Base64Url的反思

首先复习一下Base64的概念,Base64就是一种基于64个可打印字符来表示二进制数据的方法,编码过程是从二进制数据到字符串的过程,在web应用中我们经常用它来做啥呢——传输图片数据。HTML中,img的src和css样式的background-image都可以接受base64字符串,从而在页面上渲染出对应的图片。正是基于浏览器的这项能力,很多开发者提出了将多张图片转换为base64字符串,放进css样式文件中的“优化方式”,这样做的目的只有一个——减少HTTP请求数。但实际上,在如今的应用开发中,这种做法大多数情况是“负优化”效果,接下来让我们细数base64 Url的“罪状”:

第一、让css文件的体积失去控制

当你把图片转换为base64字符串之后,字符串的体积一般会比原图更大,一般会多出接近3成的大小,如果你一个页面中有20张平均大小为50kb的图片,转它们为base64后,你的css文件将可能增大1.2mb的大小,这样将严重阻碍浏览器的关键渲染路径:

css文件本身就是渲染阻塞资源,浏览器首次加载时如果没有全部下载和解析完css内容就无法进行渲染树的构建,而base64的嵌入则是雪上加霜,这将把原先浏览器可以进行优化的图片异步加载,变成首屏渲染的阻塞和延迟。

或许有人会说,webpack的url-loader可以根据图片大小决定是否转为base64(一般是小于10kb的图片),但你也应该担心如果页面中有100张小于10kb的图片时,会给css文件增加多少体积。

第二、让浏览器的资源缓存策略功亏一篑

假设你的base64Url会被你的应用多次复用,本来浏览器可以直接从本地缓存取出的图片,换成base64Url,将造成应用中多个页面重复下载1.3倍大小的文本,假设一张图片是100kb大小,被你的应用使用了10次,那么造成的流量浪费将是:(100 1.3 10) - 100=1200kb。

第三、低版本浏览器的兼容问题

这是比较次要的问题,dataurl在低版本IE浏览器,比如IE8及以下的浏览器,会有兼容性问题,详细情况可以参考这篇文章。

第四、不利于开发者工具调试与查看

无论哪张图片,看上去都是一堆没有意义的字符串,光看代码无法知道原图是哪张,不利于某些情况下的比对。 说了这么多 既然这种方案缺点这么多,为啥它会从以前就被广泛使用呢?这要从早期的http协议特性说起,在http1.1之前,http协议尚未实现keep-alive,也就是每一次请求,都必须走三次握手四次挥手去建立连接,连接完又丢弃无法复用,而即使是到了http1.1的时代,keep-alive可以保证tcp的长连接,不需要多次重新建立,但由于http1.1是基于文本分割的协议,所以消息是串行的,必须有序地逐个解析,所以在这种请求“昂贵”,且早期图片体积并不是特别大,用户对网页的响应速度和体验要求也不是很高的各种前提结合下,减少图片资源的请求数是可以理解的。

但是,在越来越多网站支持http2.0的前提下,这些都不是问题,h2是基于二进制帧的协议,在保留http1.1长连接的前提下,实现了消息的并行处理,请求和响应可以交错甚至可以复用,多个并行请求的开销已经大大降低,我已经不知道还有什么理由继续坚持base64Url的使用了。

总结

图片优化的手段总是随着浏览器特性的升级,网络传输协议的升级,以及用户对体验要求的提升而不停地更新迭代,几年前适用的或显著的优化手段,几年后不一定仍然如此。因地制宜,多管齐下,才能将其优化做到极致!

S方法:
$("body").attr('style','overflow-y:hidden') //这个是解决竖状滚动条
//横向 需要把
overflow-y改成overflow-x即可
CSS办法:

一、防止图片撑破DIV方法一

原始处理方法是将要展示的图片进行处理。比如你的DIV宽度为500px像素,那你上传的图片或放入网页的图片宽度就要小于500px,也就是你图片需要图片软件剪切、等比例缩小方法处理后再上传、放入网页中解决撑破撑开DIV问题。

常见很多大型图片站点、新闻站点都是将照片图片进行处理适应网页宽度情况下,进行图片编辑处理的。

二、防止图片撑开DIV方法二

如果不处理照片方法适应DIV有限宽度,那可以对DIV设置隐藏超出内容方法。只需要对DIV设置宽度后加入CSS样式“overflow:hidden”即可解决隐藏图片比DIV过宽部分解决撑破DIV问题

三、解决方法三

对图片img标签中只加入宽度即可解决。这样可以等比例缩小图片,不会影响图片画面质量。

比如你的网页DIV宽度为500px,那你上传图片后对img标签设置width等于500以下即可。
<img src="图片路径" width="小于你的DIV宽度" />即可解决图片过宽导致DIV SPAN撑破,这样好处可以等比例放大缩小图片

四、CSS解决撑破方法四

这种方法使用CSS直接对div内的img进行宽度设置,这样不好是如果图片过小会影响网页浏览图片时候效果。

Div结构:<div class="dc5"><img src="图片路径" /></div>
对应CSS代码:.dc5 img{宽度值+单位}

五、CSS解决图片撑破撑开DIV方法五

使用max-width(最大宽度),比如你DIV宽度为500px,那你对应DIV样式再加入最大宽度CSS样式“max-width="500px"”即可解决,但是在IE6浏览器不兼容此属性,谨慎使用。

六、解决图片撑破DIV层方法总结与推荐

1)、最大宽度(max-width)+overflow:hidden。我们这样设置可以让IE6版本以上浏览器支持最大宽度样式,也让IE6下隐藏图片超出宽度而撑开DIV得到解决,此方法比较方便和实用。

2)、只使用overflow:hidden属性,如方法二

3)、图片使用上传时候软件处理下,以适应DIV布局宽度,如方法一

以上为推荐解决IMG图片撑破有限DIV宽度方法,根据实际情况大家可以任意选择适合自己解决网页中图片撑破DIV层方法。