【你并不老是须要html2canvas】一,截图适配

1 序章

《你并不老是须要html2canvas》将会是系列文章,逐步讲解如何使用canvas原生api来实现咱们的截图需求,文章将围绕不使用html2canvas须要解决的一系列问题来展开,指望能给你们带来以下收获:javascript

  1. 熟悉canvas原生api(但不像接口文档);
  2. 了解到html2canvas解决了哪些问题,而咱们本身能够怎么解决;
  3. 分析需求,按需使用或制造“轮子”的能力;

2 背景

相信H5截图分享的功能你们都作过,并且会反复作。凡是反复作的事情,我都会想:“下一次怎么作得更好,或者更快”,毕竟我是一个“懒惰”而又“没有耐心”的人。懒惰驱动我提升生产力,没有耐心驱使我在一样一件事情上不断挖掘新的东西,不然容易在重复中失去耐心。html

说到截图,大名鼎鼎的html2canvas - Screenshots with javascript你们确定耳熟能详,强大,易用。感谢开源社区的贡献,确实帮咱们节省了不少力气。前端

// 使用示例
html2canvas(document.querySelector("#capture")).then(canvas => {
    document.body.appendChild(canvas)
});
复制代码

截图效果

但在使用过程当中我也遇到了一些问题:html5

  1. 体积大(gzip 40k,未gzip 161k) - 通用的开源模块大而全也是常态;
  2. 黑盒 - 和预期不符只能搜索求助,比较难定位;
  3. 很差个性化处理 - 例如显示和截图不一致时,须要多维护一套截图用的DOM;
  4. 很差优化绘制耗时 - 例如在重复绘图时,能够把一些固定不变的内容绘制到一个缓存canvas上面,后续把这个canvas绘制到目标canvas便可,这个缓存canvas的内容不用反复绘制;

所以,简单分析截图分享的需求(如上图所示)后,我决定试试本身直接绘制,因而就开始了个人踩坑之旅。若是你也想为了一点点的体积优化而不使用html2canvas,那请作好心理准备解决下列问题java

  1. 适配(1/2/3倍图...)
  2. 图片绘制(头像圆形裁剪...)
  3. 文字绘制(定位,局部高亮,自动换行,自动省略...)
  4. ...

本系列文章将逐步补充和解决我作各类各样的截图需求遇到的问题,也欢迎你们补充到留言里面,我会抽空解决。git

做为系列文章的第一篇,和你们介绍了一下出发点,后续再也不赘述,下面进到正题。github

3 截图适配

3.1 问题是什么

截图适配,主要指布局适配、图片分辨率和字体大小适配。众所周知,不一样的设备显示尺寸、显示精度可能不同,因此一样的代码在不一样的设备上运行会显示不彻底同样的图像,会致使以下截屏问题:chrome

  1. 截图尺寸不同,致使iPhone6不能一屏显示完iPhoneX的截屏:iPhone6下整屏截屏的图片尺寸是375*667,而iPhoneX的尺寸为375*812
  2. 元素大小、位置不一样:由于页面适配(rem、百分比、flex...)的缘故,屏幕尺寸越大,实际显示元素的尺寸也越大,同时元素的top和left位置也会不同。例如375px宽的屏幕居中显示175px宽的图片时,图片left = 100px,而600px宽的屏幕居中显示同样的图片(图片尺寸为175 * 600/375 = 280px)时,图片left = (600 - 280) / 2 = 160px;
  3. 截图显示精度不同,致使iPhoneX看iPhone6的截屏会以为模糊:iPhone6的devicePixelRatio为2,iPhoneX为3;

3.2 解决

解决问题前,咱们先明确咱们的截图分享需求:生成一张“看起来”同样的图片canvas

为了看起来同样,咱们首先会肯定一个截图的尺寸,以750px视觉稿为基准,假设截图是750 * 1280。视觉稿给的750px是以iPhone6为基准的两倍图,因此在iPhone6上完美显示,可是iPhoneX应该使用三倍图,所以750 * 1280在iPhoneX上显示会以为有些模糊(特别是文字,认真看能看到锯齿),所以咱们有两个解决方法:后端

方案 优势 缺点
1. 全部设备都按照三倍图的尺寸绘制(甚至更大尺寸) 生成的图片彻底一致 canvas绘制图片越大,耗时越长
2. 不一样设备安装本身的显示精度进行绘制 在当前设备显示清晰 分享出去的图片在更高显示精度的设备上会感受到模糊

两种方案能够按需挑选,假设须要频繁绘制,优先选择方案2,假设只须要绘制1次,而且对绘制内容有较高的视觉要求,优先选择方案1。方案2能够理解为方案1的一种特例,下面重点说方案1的实现,你们能够推导出方案1怎么实现:

  1. 750px视觉稿为基准,根据当前屏幕尺寸和屏幕显示精度计算视觉稿到屏幕的缩放关系:scale = window.innerWidth / 750 * window.devicePixelRatio;
  2. 在视觉稿中量取绘制元素的大小,位置,绘制的时候根据上面的scale进行缩放便可;
  3. 【方案1】不一样的地方在于:window.innerWidth = 375window.devicePixelRatio = 3;

3.3 实践

理解了适配的原理以后,具体的编码工做就水到渠成了,下面主要是补充操做细节。

3.3.1 图片预加载 & 跨域设置

绘制图片的第一步是加载图片,只有在图片加载完成后,才能把图片内容绘制到canvas上。其次,出于浏览器内容安全考虑,仅有支持跨域访问的图片才能从canvas上导出成base64编码。

// 预加载图片 & 跨域设置
const imgs = ['avatar.png', 'bg_screenshot.jpg', 'container.png'];
const imgEls = {};
let loadCount = 0;
const imgLoad = (callback) => {
	loadCount ++;
	if (loadCount === imgs.length) callback();
}

const preloadImg = (callback) => {
	imgs.forEach((imgUrl) => {
		let img = document.createElement('img');
		img.crossOrigin = 'Anonymous'; // 【重要】设置跨域,服务器须要返回跨域支持
		img.onload = ()=>{
			imgEls[imgUrl] = img;
			imgLoad(callback);
		};
		img.src = './img/' + imgUrl;
	});
}

// 使用
preloadImg(() => {
   console.log('img loaded!'); 
});
复制代码

注意:img.crossOrigin = 'Annoymous只是将该图片请求设置为一个跨域请求,这个时候,若是服务器没有设置合适的CORS,浏览器会提示图片加载失败,所以这里须要后端配合设置:Access-Control-Allow-Origin:*

3.3.2 高清图 & 位置适配

高清图这个概念和两倍图、三倍图的概念同样,不理解的同窗能够去搜索一下。举个例子,iPhone6下window.devicePixelRatio = 2, 而window.innerWidth = 375,所以须要给750px宽的视觉稿才能在手机上看到和视觉稿同样的效果。而iPhoneX是三倍图,所以须要375 * 3的背景图,可是视觉设计师通常只会给二倍图,因此这个时候在iPhoneX下图片显示没有视觉稿那么清晰,若是对视觉有要求,还须要设计师给三倍图(除非是矢量图,咱们能够自由缩放)。

理解了高清图以后,咱们截屏的canvas大小就能够计算出来了,和高清图同样,在window.devicePixelRatio = 2的设备上,咱们会将canvas设置成window.innerWidth的两倍,最后生成的图片就至关因而两倍图,能在该设备清晰显示。

在canvas大小已知的基础上,咱们就能够从视觉稿中量出绘制元素的宽度,left,top信息,直接缩放绘制到canvas上了。

const scale = window.innerWidth / 750 * window.devicePixelRatio; // 【重要】以750px(视觉稿给的宽度每每是750px)为基准计算canvas的宽度
const ratio = 1.7; // 截图的高宽比
const width = 750 * scale;
const height = width * ratio;
screenshotCanvas = document.createElement('canvas');
screenshotCanvas.width = width;
screenshotCanvas.height =  height;
const ctx = screenshotCanvas.getContext('2d');
ctx.drawImage(imgEls['container.png'], (750 - 643) / 2 * scale, 184 * scale, 634 * scale, 843 * scale ); //【重要】 1. 图片水平居中,top从视觉稿中量出来是184 2. 634 * 843是视觉稿中图片的尺寸
复制代码

3.3.3 圆形头像绘制

微信后台给到的用户头像都是方形的,若是须要绘制圆形头像,能够先设置头像圆形区域,裁剪后进行绘制,这个时候只有在圆形区域的内容才会被绘制进去,而后恢复ctx设置(ctx.save() / ctx.restore())继续绘制别的内容便可。

ctx.save();
const avatarR = 103 * scale / 2; // 头像半径
ctx.arc(540 * scale + avatarR, 244 * scale + avatarR, avatarR, 0, Math.PI * 2, false); // 设置特定区域
ctx.clip(); // 裁剪区域,仅绘制特定区域内的内容
ctx.drawImage(imgEls['avatar.png'], 540 * scale, 244 * scale, 103 * scale, 103 * scale );
ctx.restore();
复制代码

3.3.4 绘制文字 & 适配大小

文字大小咱们常常用rem来适配,可是canvas绘制只支持px单位,所以须要咱们本身计算,其实和上面的高清图同样,等比缩放便可:

const getFont = (size) => {
	return size * scale + 'px serif'; // 能够按需设置字体
};
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; // 【重要】 文本设置垂直居中,默认为向上对齐,可是不一样浏览器向上对齐的表现不同,所以使用垂直居中,下面的591.5, 380至关于文案的中心点位置
ctx.font = getFont(26); // 【重要】计算字号大小,视觉稿上字号大小为26,实际大小为26 * scale
复制代码

看上面的注释你们应该也看到了,关于字体的垂直位置设置有个坑,字体绘制的时候,默认是顶部对齐的,可是不一样浏览器顶部对齐的表现不一样,所以改用垂直居中。假设原来的top是400,字体高度为100,那设置ctx.textBaseline = 'middle'后,绘制字体的y位置就是400 + 100 / 2 = 450。 参考:Canvas文字绘制top位置不一致问题

5 End

截图分享需求,大都是绘制图片、分享文案和用户信息,不少时候并不须要出动html2canvas这种“重型武器”,了解如何作适配和查阅绘制相关的api,你也能够轻松实现截图需求。

前方还有不少问题等着咱们去解决,例如文字绘制怎么局部高亮、自动换行和自动省略等等,下一篇博客见,同时期待与你们共同成长。

附:demo源码

写完后发现系统相关推荐的一篇文章讲截图(主要用了html2canvas)讲得比较全面,这里引用一下,你们能够结合起来学习:

《高质量前端快照方案:来自页面的「自拍」》

相关文章
相关标签/搜索