《你并不老是须要html2canvas》将会是系列文章,逐步讲解如何使用canvas原生api来实现咱们的截图需求,文章将围绕不使用html2canvas须要解决的一系列问题来展开,指望能给你们带来以下收获:javascript
相信H5截图分享的功能你们都作过,并且会反复作。凡是反复作的事情,我都会想:“下一次怎么作得更好,或者更快”,毕竟我是一个“懒惰”而又“没有耐心”的人。懒惰驱动我提升生产力,没有耐心驱使我在一样一件事情上不断挖掘新的东西,不然容易在重复中失去耐心。html
说到截图,大名鼎鼎的html2canvas - Screenshots with javascript你们确定耳熟能详,强大,易用。感谢开源社区的贡献,确实帮咱们节省了不少力气。前端
// 使用示例
html2canvas(document.querySelector("#capture")).then(canvas => {
document.body.appendChild(canvas)
});
复制代码
但在使用过程当中我也遇到了一些问题:html5
所以,简单分析截图分享的需求(如上图所示)后,我决定试试本身直接绘制,因而就开始了个人踩坑之旅。若是你也想为了一点点的体积优化而不使用html2canvas,那请作好心理准备解决下列问题:java
本系列文章将逐步补充和解决我作各类各样的截图需求遇到的问题,也欢迎你们补充到留言里面,我会抽空解决。git
做为系列文章的第一篇,和你们介绍了一下出发点,后续再也不赘述,下面进到正题。github
截图适配,主要指布局适配、图片分辨率和字体大小适配。众所周知,不一样的设备显示尺寸、显示精度可能不同,因此一样的代码在不一样的设备上运行会显示不彻底同样的图像,会致使以下截屏问题:chrome
375px
宽的屏幕居中显示175px
宽的图片时,图片left = 100px
,而600px
宽的屏幕居中显示同样的图片(图片尺寸为175 * 600/375 = 280px
)时,图片left = (600 - 280) / 2 = 160px
;解决问题前,咱们先明确咱们的截图分享需求:生成一张“看起来”同样的图片。canvas
为了看起来同样,咱们首先会肯定一个截图的尺寸,以750px
视觉稿为基准,假设截图是750 * 1280
。视觉稿给的750px
是以iPhone6为基准的两倍图,因此在iPhone6上完美显示,可是iPhoneX应该使用三倍图,所以750 * 1280
在iPhoneX上显示会以为有些模糊(特别是文字,认真看能看到锯齿),所以咱们有两个解决方法:后端
方案 | 优势 | 缺点 |
---|---|---|
1. 全部设备都按照三倍图的尺寸绘制(甚至更大尺寸) | 生成的图片彻底一致 | canvas绘制图片越大,耗时越长 |
2. 不一样设备安装本身的显示精度进行绘制 | 在当前设备显示清晰 | 分享出去的图片在更高显示精度的设备上会感受到模糊 |
两种方案能够按需挑选,假设须要频繁绘制,优先选择方案2,假设只须要绘制1次,而且对绘制内容有较高的视觉要求,优先选择方案1。方案2能够理解为方案1的一种特例,下面重点说方案1的实现,你们能够推导出方案1怎么实现:
750px
视觉稿为基准,根据当前屏幕尺寸和屏幕显示精度计算视觉稿到屏幕的缩放关系:scale = window.innerWidth / 750 * window.devicePixelRatio
;scale
进行缩放便可;window.innerWidth = 375
, window.devicePixelRatio = 3
;理解了适配的原理以后,具体的编码工做就水到渠成了,下面主要是补充操做细节。
绘制图片的第一步是加载图片,只有在图片加载完成后,才能把图片内容绘制到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:*
。
高清图这个概念和两倍图、三倍图的概念同样,不理解的同窗能够去搜索一下。举个例子,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是视觉稿中图片的尺寸
复制代码
微信后台给到的用户头像都是方形的,若是须要绘制圆形头像,能够先设置头像圆形区域,裁剪后进行绘制,这个时候只有在圆形区域的内容才会被绘制进去,而后恢复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();
复制代码
文字大小咱们常常用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位置不一致问题
截图分享需求,大都是绘制图片、分享文案和用户信息,不少时候并不须要出动html2canvas这种“重型武器”,了解如何作适配和查阅绘制相关的api,你也能够轻松实现截图需求。
前方还有不少问题等着咱们去解决,例如文字绘制怎么局部高亮、自动换行和自动省略等等,下一篇博客见,同时期待与你们共同成长。
附:demo源码
写完后发现系统相关推荐的一篇文章讲截图(主要用了html2canvas)讲得比较全面,这里引用一下,你们能够结合起来学习: