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代替图片
场景:适用于图片大小小于2KB,页面上引用图片总数不多的情况 原理:将图片转换为base64编码字符串inline到页面或css中 优势:减少http的请求次数,并可以放到后台数据库中,只传输字符串,有较多的构建工具可以直接实现 劣势:这种方法仅限于图片总数较少,而且图片大小小于2KB的情况。否则图片字符串会变得很长很长
3、图片处理
1.图片懒加载
图片是网页中流量占比最多的部分,也是需要重点优化的部分。
图片延迟加载的原理就是先不设置img的src属性,等合适的时机(比如滚动、滑动、出现在视窗内等)再把图片真实url放到img的src属性上。常用插件:基于jQ的Lazyload插件
2.图片压缩
好用的网站:
http://alloyteam.github.io/gopng/
4、JS/CSS按需打包
按需打包是webpack独特的优势,如果有需要通过此种方式来管理模块之间的依赖关系,强烈推荐引入!
插件化
:webpack本身非常灵活,提供了丰富的插件接口。基于这些接口,webpack开发了很多插件作为内置功能。
速度快
:webpack使用异步IO以及多级缓存机制。所以webpack的速度是很快的,尤其是增量更新。
丰富的Loaders
:loaders用来对文件做预处理。这样webpack就可以打包任何静态文件。
高适配性
:webpack同时支持AMD
/CommonJs
/ES6
模块方案。webpack会静态解析你的代码,自动帮你管理他们的依赖关系。此外,webpack对第三方库的兼容性很好。
代码拆分
:webpack可以将你的代码分片,从而实现按需打包。这种机制可以保证页面只加载需要的JS代码,减少首次请求的时间。
优化
:webpack提供了很多优化机制来减少打包输出的文件大小,不仅如此,它还提供了hash机制,来解决浏览器缓存问题。
开发模式友好
:webpack为开发模式也提供了很多辅助功能。比如SourceMap、热更新等。
使用场景多
:webpack不仅适用于web应用
场景,也适用于Webworkers
、Node.js
场景
webpack 如何最佳配置?
webpack官方提供的配置方法是通过module.exports返回一个json,但是这种场景不灵活,不能适配多种场景。
比如要解决:production模式和development模式,webpack的配置是有差异的,大致有两种思路。
1、两份配置文件webpack.config.production.js/webpack.config.development.js
,然后不同场景下,使用不同的配置文件。
2、通过module.exports返回函数,该函数能接受参数。
相对来说,第一种更简单,但是重复配置多;第二种更灵活,推荐第二种方式。
那么,按返回函数的方式的配置代码架子如下:
module.exports = function(env) { return { context: config.context, entry: config.src, output: { path: path.join(config.jsDest, project), filename: '[name].js', chunkFilename: '[name].[chunkhash:8].js', publicPath: '/assets/' + project + '/' }, devtool: "eval", watch: false, profile: true, cache: true, module: { loaders: getLoaders(env) }, resolve: { alias: getAlias(env) }, plugins: getPlugins(env) }; }
其中关键的配置这儿简单介绍如下,后续的系列博客会根据每个点详细介绍。
context
:上下文。
entry
:入口文件,是所有依赖关系的入口,webpack从这个入口开始静态解析,分析模块之间的依赖关系。
output
:打包输出的配置。
devtools
:SourceMap选项,便于开发模式下调试。
watch
:监听模式,增量更新,开发必备!
profile
:优化。
cache
:webpack构建的过程中会生成很多临时的文件,打开cache可以让这些临时的文件缓存起来,从而更快的构建。
module.loaders
:如前文介绍,loaders用来对文件做预处理。这样webpack就可以打包任何静态文件。
resolve.alias
:模块别名,这样可以更方便的引用模块。
plugins
:如前文介绍,webpack的一些内置功能均是以插件的形式提供。
webpack和gulp区别:
gulp是基于流的构建工具:all in one的打包模式,输出一个js文件和一个css文件,优点是减少http请求,万金油方案。
webpack是模块化管理工具,使用webpack可以对模块进行压缩、预处理、打包、按需加载等。
如何减小请求大小?
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)); });
2、gzip压缩
gzip压缩也是比较常规的优化手段。前端并不需要做什么实际的工作,后台配置下服务器就行,效果非常明显。如果你发现你的网站还没有配置gzip,那么赶紧行动起来吧。
gzip压缩效果
那么gzip压缩的效果有多明显呢?保守估计,在已经完成JS/CSS/HTML压缩的基础上,还能降低60-80%左右的大小。
但需要注意,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,有兴趣的可以去看看源码。
知识点
整体来说,涉及的知识面比较广:包括webpack & gulp的构建系统、图片的webp优化、服务器的gzip配置、浏览器的加载顺序、图片延迟加载方案等等。