添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
酷酷的芹菜  ·  Get-AzContainerInstanc ...·  5 月前    · 
慷慨的松球  ·  1.jupyter ...·  1 年前    · 
大方的炒饭  ·  Javascript ...·  1 年前    · 

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

  • 熟悉canvas原生api(但不像接口文档);
  • 了解到html2canvas解决了哪些问题,而我们自己可以怎么解决;
  • 分析需求,按需使用或制造“轮子”的能力;
  • 相信H5截图分享的功能大家都做过,而且会反复做。凡是反复做的事情,我都会想: “下一次怎么做得更好,或者更快” ,毕竟我是一个“懒惰”而又“没有耐心”的人。懒惰驱动我提高生产力,没有耐心驱使我在同样一件事情上不断挖掘新的东西,否则容易在重复中失去耐心。

    说到截图,大名鼎鼎的 html2canvas - Screenshots with javascript 大家肯定耳熟能详,强大,易用。感谢开源社区的贡献,确实帮我们节省了很多力气。

    // 使用示例
    html2canvas(document.querySelector("#capture")).then(canvas => {
        document.body.appendChild(canvas)
    

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

  • 体积大(gzip 40k,未gzip 161k) - 通用的开源模块大而全也是常态;
  • 黑盒 - 和预期不符只能搜索求助,比较难定位;
  • 不好个性化处理 - 例如显示和截图不一致时,需要多维护一套截图用的DOM;
  • 不好优化绘制耗时 - 例如在重复绘图时,可以把一些固定不变的内容绘制到一个缓存canvas上面,后续把这个canvas绘制到目标canvas即可,这个缓存canvas的内容不用反复绘制;
  • 因此,简单分析截图分享的需求(如上图所示)后,我决定试试自己直接绘制,于是就开始了我的踩坑之旅。如果你也想为了一点点的体积优化而不使用html2canvas,那请做好心理准备解决下列问题

  • 适配(1/2/3倍图...)
  • 图片绘制(头像圆形裁剪...)
  • 文字绘制(定位,局部高亮,自动换行,自动省略...)
  • 本系列文章将逐步补充和解决我做各种各样的截图需求遇到的问题,也欢迎大家补充到留言里面,我会抽空解决。

    作为系列文章的第一篇,和大家介绍了一下出发点,后续不再赘述,下面进到正题。

    3 截图适配

    3.1 问题是什么

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

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

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

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

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

  • 750px视觉稿为基准,根据当前屏幕尺寸和屏幕显示精度计算视觉稿到屏幕的缩放关系:scale = window.innerWidth / 750 * window.devicePixelRatio;
  • 在视觉稿中量取绘制元素的大小,位置,绘制的时候根据上面的scale进行缩放即可;
  • 【方案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)讲得比较全面,这里引用一下,大家可以结合起来学习:

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

    分类:
    前端
    标签: