前言
网页在前端的展示主要集中在文字、图片、表格、公式、音频、视频等元素。笔者准备以元素为基准写一个系列,以期以点成线,用这样的方式“重走前端之路”。本文是系列的第二篇。
图片是网页的重要组成部分,业务场景中常见的图片应用无外乎图片上传、图片加载、图片压缩、图片裁剪、DOM 转图片、图片缩放等等,本文以“图片”为基点,讲述图片在不同应用场景下的解决方案。
图片格式
资源优化是性能优化中收益最高的,简单且见效快,因此你需要知道不同格式图片的适用场景。
- jpg:适用于内容图片多为照片之类的。
- png:适用于装饰图片,通常更适合用无损压缩。
- gif:基本上除了 gif 动画外不要使用。
- webP:大大减小图片的体积,但是移动端有兼容性问题。
图片上传
图片的形式可以是 base64 的字符形式也可以是 File 的形式。但因为 base64 有长度的限制,因此较大的图片推荐使用 File 形式。不仅仅是在上传的场景中,图片的裁剪等场景也适用。
base 64
根据 base64 的编码原理,base64图片大小比原文件大小大 1/3,所以说 base64 一般用于上传一些非常小的小图。
FileReader 将上传的本地图片转成 base64
function file2DataUrl(file, callback) {
var reader = new FileReader();
reader.onload = function () {
callback(reader.result);
};
reader.readAsDataURL(file); // 转换成 base64
}
file 对象
正常情况,file 对象数据是放在POST请求的 body 里面,并且是 form-data 编码。
function upload(url, file, callback) {
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append('file', file);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 上传成功
callback && callback(xhr.responseText);
} else {
throw new Error(xhr);
}
}
xhr.open('POST', url, true);
xhr.send(fd);
}
图片懒加载
前端性能优化很重要的一个手段就是图片懒加载。图片懒加载有利于减少网络加载,提升用户感知体验。
懒加载的原理
图片的 src 属性决定了图片是否加载。图片懒加载就是 src 的按需获取。可以在 src 上放占位图的地址,将图片的真实路径存储到自定义数据属性 data-src 上,然后在需要的时候按需加载。通常会有滚动到页面底部加载图片的应用场景。
滚动监听
其主要实现的原理是对滚动事件的监听,当发现滚动条高度 + 滚动条高度 = document 的高度时,就是达到页面的底部了,是需要重新加载图片的时机。这种方法的缺点是,由于scroll事件密集发生,计算量很大,容易造成性能问题。
window.onscroll= function(){
//文档内容实际高度(包括超出视窗的溢出部分)
var scrollHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
//滚动条滚动距离
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
//窗口可视范围高度
var clientHeight = window.innerHeight || Math.min(document.documentElement.clientHeight,document.body.clientHeight);
if(clientHeight + scrollTop >= scrollHeight){
console.log("===加载图片……===");
}
}
function lazyload() {
var images = document.getElementsByTagName('img')
var len = images.length
var n = 0 //存储图片加载到的位置,避免每次都从第一张图片开始遍历
return function () {
var seeHeight = document.documentElement.clientHeight
for (var i = n; i < len; i++) {
if (images[i].getBoundingClientRect().top < seeHeight) {
当图片的视口 top出现在视口中
if (images[i].getAttribute('src') === 'images/default.jpg') {
images[i].src = images[i].getAttribute('data-src')
}
n = n + 1
}
}
}
}
原生 API
通过依赖浏览器原生 API IntersectionObserver 来检测元素是否进入视图,Chrome 51+ 已经支持。
<img src="placeholder.png" src="img-1.jpg">
<img src="placeholder.png" src="img-2.jpg">
<img src="placeholder.png" src="img-3.jpg">
上面代码中,图像默认显示一个占位符,data-src属性是惰性加载的真正图像。
function query(selector) {
return Array.from(document.querySelectorAll(selector));
}
var observer = new IntersectionObserver(
function(entries) {
entries.forEach(function(entry) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target); // 停止观察
});
}
);
query('.lazy-loaded').forEach(function (item) {
observer.observe(item); // 开始观察
});
图片加载失败
图片会有加载失败的情况,一方面我们需要考虑用户体验上的优化,另一方面开发需要感知到线上问题,能够进一步去核实原因,这个通过监控去做就好。
HTML 方案
<img src="xxx" onerror="javascript:this.src='images/logoError.png';">
// 避免 logoError 图片也不存在时循环调用
<img src="images/logo.png" onerror="notfound();"/>
<script type="text/javascript">
function notfound() {
var img = event.srcElement;
img.src = "images/logoError.png";
img.onerror = null; //解绑onerror事件
}
</script>
CSS 方案
<img src="xxx" class="logo">
.logo{
position:relative;
width:100px;
height:130px;
}
// 只有 image 没有加载出来的时候 以下才有效果
.logo:after{
position:absolute;
width:100px;
height:130px;
content:"";
display:block;
top:0;
left:0;
background:url('/img/default.png');
background-size:100%;
}
图片压缩
压缩图片原理
图片压缩是一个比较复杂的事情,想要有好的压缩效果,一般是由后端大佬通过压缩算法来实现的。然而对于比较简单的需求,前端的压缩思路通常是通过 canvas 来操作图片压缩。将图片转化成 Image 对象,再将写入到 Canvas 画布,最后由 Canvas 对象 API 对图片的大小和尺寸输出调整,以实现压缩目的。
图片的格式的复杂性也是影响图片压缩效果的因素之一:
- gif压缩后一般都会失真
- svg通常用在矢量图标上很少用在场景图片上
- webp由于兼容性问题很少被使用
- 前端图片大多是jpg和png
了解这些特性之后,可以权衡利弊及成本,大舍大拿有的时候方能干大事。
手动压缩
工具 | 开源 | 收费 | API | 免费体验 | 压缩原理 |
---|---|---|---|---|---|
TinyJpg | TinyPng | ❌ | ✅ | ✅ | 可压缩类型较少,压缩质感很好,有数量限制,有体积限制 |
- 上传下载全靠手动
- 只能压缩jpg和png
- 每次只能压缩20张
- 每张体积最大不能超过5M
- 可视化处理信息不是特别齐全
自动压缩
库
compression.js
使用简单,参数只有输入图片,压缩比例,输出图片。很少的代码量即可实现压缩和预览的效果。
webpack loader & plugin
image-webpack-loader 及 imagemin-webpack-plugin
它们都是基于 imagemin 开发的。imagemin的某些依赖托管在国外服务器,在 npm i xxx 安装它们时会默认走 GitHub Releases 的托管地址。压缩质感损失得比较严重,图片体积越大越明显,压缩出来的图片总有几张是失真的,而且总体压缩率不是很高。
tinyimg-webpack-plugin
利用的就是 TinyJpg|TinyPng 的API,只不过封装成了插件。
PPDuck3
客户端压缩工具。
DOM 转图片
Dom 转图片某种程度上和屏幕截图是一个道理,最终都会通过 Canvas 去转成图片。
关于截图,这里讲个题外话。iframe 有个特别大的缺点就是无法支持跨域屏幕截图,所以这也是为什么微前端方案里面不提倡用 iframe 的原因之一。
html2canvas
html2canvas 能够实现在用户浏览器端直接对整个或部分页面进行截屏。html2canvas 脚本将当页面渲染成一个Canvas 图片,通过读取 DOM 并将不同的样式应用到这些元素上实现。转换过程是DOM→Canvas→Image。
PS:注意这个库无法转换 svg 的 DOM 结构。
它不需要来自服务器任何渲染,整张图片都是在客户端浏览器创建。当浏览器不支持Canvas时,将采用Flashcanvas 或 ExplorerCanvas 技术代替实现。以下浏览器能够很好的支持该脚本:Firefox 3.5+, Google Chrome, Opera新的版本, IE9 以上的浏览器。
rasterizehtml
通过遍历 DOM 克隆一份副本,利用 SVG 的 foreignObject把 DOM 作为外部资源嵌套在 SVG 中,将此SVG 在 Canvas 上重新绘制,并根据 DOM 的样式应用在对应的绘制元素上,再通过 Canvas 生成图片。转换过程是:DOM→SVG的ForeignObject→Canvas→Image。
PS:无法渲染如lazyload等通过JS加载的资源以及内联background-image或JS操作background-image
phantomjs
phantomjs 是一个运行在服务端的无界面的浏览器。
PhantomJS是一个基于 webkit 的 JavaScript API。它使用 QtWebKit 作为核心浏览器的功能,使用webkit 来编译解释执行JavaScript代码。任何你可以在基于 webkit 浏览器做的事情,它都能做到。它不仅是个隐形的浏览器,提供了诸如CSS选择器、支持Web标准、DOM操作、JSON、HTML5、Canvas、SVG等,同时也提供了处理文件I/O的操作,从而使你可以向操作系统读写文件等。PhantomJS 的用处可谓非常广泛,诸如网络监测、网页截屏、无需浏览器的 Web 测试、页面访问自动化等。
需要在服务端进行截屏,所以还需要一个可以访问的 h5 页面,另外页面如果是服务端渲染,最好不要用 js 渲染,截图可能有问题。
另外一个在服务端的字体需要预先设置,比如微软雅黑、 华文苹方 服务器上如果没有就需要进行安装。不然截图的字体和你的效果图不一致。
优点:服务端截屏,可以做成通用的服务。
缺点:会增加前端同学的工作量,因为需要做服务,还需要做 h5 页面布局。当然也看怎么设计架构,如果足够通用也只需要写一次就够了。剩下的就是做 h5 页面了,这个就快多了。
图片裁剪
Croppie
这个库还有vue版本及原生版本的,相对轻量,也是一个老牌库。
react-cropper
鼠标左键拖拽选中切割区域
文档见 cropper.js(同一个作者,react-cropper属于cropper.js的移植)
react-image-crop
这个组件,提供了兼容 pc 与移动设备的图片裁剪区域选择功能。直接支持 react。
antd-img-crop
这个是熟悉的 antd 提供的配套组件,直接包裹 Upload 使用。
ucrop
安卓原生裁剪库。
图片缩放
react-zmage
一个基于 React 的可缩放图片控件
react-viewer | react-viewer-mobile
同一个作者基于 viewer 搞了两套,如果你有同时支持PC和Mobile的需求是一个比较好的选择,API 都是一样的。
图片性能
雪碧图
雪碧图,CSS Sprites,是一种 CSS 图像合成技术,主要用于小图片显示。雪碧图的优点是把诸多小图片合成一张大图,利用backround-position属性值来确定图片呈现的位置,这样就能减少 http 请求,达到性能优化的效果。
iconfont
iconfont(字体图标),即通过字体的方式展示图标,多用于渲染图标、简单图形、特殊字体等。
使用 iconfont 时,由于只需要引入对应的字体文件即可,这种方法可有效减少 HTTP 请求次数,而且一般字体体积较小,所以请求传输数据量较少。与直接引入图片不同,iconfont 可以像使用字体一样,设置大小、颜色及其他样式,且不存在失真的情况。
写在最后
本文对图片的常见场景的解决方案进行了梳理,对于库的使用由于篇幅有限不做说明,使用也都非常简单,详情可参考官方文档。下一篇讲“公式”。
以前觉得终身学习是知道越来越多的东西,后来才发现其实更多的成长是认知的成长。一本书《红楼梦》不同年龄段的人去读认知高度是不一样的。就像闭包在今天去看它,很快会想到 hooks,但是几年前这个概念都没有...我的文章也仅做抛砖引玉...下期见!