前端图形——绘图、截图、合成动图

lanseguhui 2019-06-27

前言

本文主要是前端图形处理相关,主题围绕canvas进行展开。包括canvas动效、html2Canvas截图、gif.js合成动图。研究这些东西,有工作需要也有兴趣使然,研究的不深、不过还是有点成果的,欢迎有兴趣的小伙伴一起交流探讨。

主线

图形相关的主要包括两个方面:绘图、截图。

技术实现上截图又分拆出静态图、动图两大类。

总的来说也就是三个方面:动效、截图、动图。

关键点

  • 动效:主要是创意、实践、优化(封装等)
  • 截图:主要是canvas转图片、dom元素解析。此外还有衍生的模糊问题、跨域图片、样式兼容等难点
  • 动图:主要是截图、延时播放。技术实现上则主要是worker多线程的概念和在vue框架下的使用。

canvas动效

Canvas_API
常见的canvas优化——模糊问题、旋转效果

动效制作思路

关于前端动效这块,主要包括三部分:css3、svg、canvas。

其中css3最轻量、canvas功能最强大,svg对客户端性能的需求比canvas低,手机端常用svg代替canvas、或替canvas实现前置步骤(如html2Canvas1.0.0)。

鉴于我对canvas比较熟悉,接下来将针对canvas进行展开。

首先要了解相应API,知道能干什么。

比如canvas就要先通览它的api,才知道原来可以绘制图形、制作动效,还可以转成图片!

其次要有创意,知道要做成什么。

比如网上的树镜动效,效果很酷炫,实现也不难,但是没有这个创意也是做不出来的,很遗憾创意这块我也没什么好的思路。当然创意是建立在已有事物基础上的,兴许了解的多了,自然就有了吧……

然后要有好的逻辑思维能力,把创意变成可实现的方案。

最好能有良好的代码风格,实现可复用,这样开发10个动效的成本就不至于是开发1个动效的10倍。目前想到的一个思路是:创意用逻辑实现,具体绘图实现封装成公用方式,按需引用。

动效实现

这里强烈建议找一个网上的好的动效,照着实现一遍,然后再仔细品味改进。我是研究的树镜动效,也从中学到了很多东西,在此非常感谢该动效创作者分享出这么好的动效。

树镜动效在vue框架下整理后大概长这样子:
前端图形——绘图、截图、合成动图
前端图形——绘图、截图、合成动图
前端图形——绘图、截图、合成动图

关于这个动效所涉及到的知识点,我分了几个模块:

  • 树镜分支构造函数TreeBranch

这个对应于“创意变成可实现的方案”这条,复杂的动效拆分后,本质上就是一个树(分支设为2就是二叉树了,有没有很熟悉),通过改变树的树枝长度、分叉角度等实现如此酷炫的动效,真正需要画的只是点和线而已。

原理搞清楚之后,就可以用js计算出点和线的坐标、色值等参数,也就是这里TreeBranch的作用,之后直接从变量中取参绘图即可。

  • 绘图实现模块drawTree

这个方法接收树的各项参数branch,然后调用基础API画点画线,最终画出树某一状态下的图案。

  • 循环绘图实现动效loop

这个方法用来循环调用drawTree绘制图形,并修改相关参数修改树的状态,实现树镜动效。

  • 交互事件interEvent

这个方法是用来绑定用户事件的,这样用户可以通过鼠标或触屏的方式主动修改相关参数、控制树的状态。

html2Canvas截图

首次接触html2Canvas是2017年11月底,当时给的任务是要在微信中实现图片分享,因为种种原因,最后任务定为“实现前端截屏,并可添加自定义标签”。而很巧合的12月初作者来了次大改版,兼容了很多css3样式、并且对此前0.5.0版本的一些不足也进行了相关优化。针对html2Canvas的研究也在出了1.0.0-alpha.3之后的12月11号结束了。

鉴于对低版本手机的兼容,最终没有用吊炸天的1.0.0的版本,而是用了0.5.0的版本,也就需要研究0.5.0相对1.0.0有哪些不足,然后自行修复……

截图原理

鉴于要自行修复0.5.0的不足,所以就愉(悲)快(催)的去扒html2Canvas的源码了,自然也就了解了一点html2Canvas实现截图功能的原理。

对比两个版本,以及网上查阅资料,发现了两大亮点:
1、前端截图依赖于canvas转为图片资源的功能。
截图结束会返回一个canvas对象,这个canvas对象上绘制的正是我们的截图区域。然后再将canvas转成图片即可。
2、截图区域怎么绘制到canvas上呢——通过对dom对象解析,变成canvas能理解的语言,在canvas上绘制一遍即可。

理解到这两点,也就掌握了前端截图的精髓(是不是很厉害~)。然后按你需要去研究要优化的点即可。

优化点

截图模糊

这个问题是1.0.0版本之前讨论最多的一个了,可以说凡是涉及到canvas几乎必谈模糊问题。

这里就不讲演进过程了,直接说最终解决方案:获取dom的scale,用dom的scale去设置canvas的scale(缩放比例)。

原因可以去了解下手机端devicePixelRatio参数。

样式兼容

项目中很多地方用到flex布局,后面发现html2Canvas-0.5.0竟然不兼容!!!

如果了解了截图原理的第二点,相信你就清楚是怎么回事了。前端截图其实并不是真正的截图,而是理解了要截图区域的dom之后,仿照着在canvas画布上实现了一遍。这里有个很关键的就是要能解析dom结构,扒源码就会发现,并没有对flex样式的解析支持。
前端图形——绘图、截图、合成动图
前端图形——绘图、截图、合成动图

虽然找到了样式不兼容的原因,然而并不能针对性的解决——毕竟在旧版本上开发解析样式的任务还是太大了点、也不实用,只能期望什么时候能用新版本。

最终我们还是老老实实的把需要截图的dom中的flex布局都替换掉了(万恶的兼容)。

跨域图片

当时截图元素包括微信头像和微信生成的带参二维码,结果图片出不来,好气哦!

因为已经悟到了截图原理的第一条——最终图片是通过canvas转过来的,而且canvas上是能画出来的,转成图片后才加载不出来微信头像和带参二维码,所以就针对性的研究canvas转图片的环节。这里要了解两点:
1、跨域图片会污染canvas
2、被污染的canvas不能转成图片(麻烦的同源策略)

而html2Canvas就提供了这两个参数的配置:useCORS、allowTaint,默认不加载跨域图片、不允许污染。
如果要转成图片allowTaint就必须为false,如果要加载跨域图片useCORS就必须为true。

所以要么我们能把图片变成不跨域的,要不然就要解决跨域图片污染canvas的问题。

索性跨域图片的问题是仅此与截图模糊的一个问题,自然也有很多成熟方案。通过修改html2Canvas的源码,可以解决跨域图片污染canvas的问题。

前端图形——绘图、截图、合成动图

通过这个方案,我们解决了微信头像的问题,然而带参二维码还是不行。这里还要了解一点:加载跨域资源是需要后端支持的。而显然带参二维码的配置是不允许这样使用的。 那就只能把图片变成不跨域的了……
很幸运的,前端生成二维码的能力还是有的,所以前端不再请求二维码的url,而是直接在前端生成二维码。

经过一系列努力,跨域图片的问题总算也是圆满解决了。

gif.js生成动图

研究这个的动力来源于网上看到的一个例子canvas+gif.js打造自己的数字雨头像。而我的初步目标是实现在vue框架下实现gif.js的使用,从而方便之后研究调试(不用一直刷新浏览器),成果可以看我的github,路由是‘#/test/testCtxGif’。

踩过的坑

要有url才能使用,不能用本地文件路径

研究的最初自然是扒网上的代码放到本地访问,但是竟然不能生成动图!自此开始研究gif.js。
gif.js是一个可直接在浏览器上运行的JS GIF编码器。使用类型化数组和web workers来渲染背景中的每一帧,速度非常快。

对比网上可正常使用的实例和本地无法正常使用的复制版,结合报错信息,尝试把实例挂到一个服务器上运行——IIS服务器,见证奇迹的时刻来了,方案可行!!!

移植代码到vue框架下

验证方案可行,只是需要挂在服务器上之后,就着手搬到vue框架下了。然而过程并不轻松。

  • Web Worker——前端多线程

首先是文件不能正常加载。gif.js倒是好说,直接import就可以用了,GIF对象会挂载到window上;但是gif.worker.js却是怎么都不能正常加载,所以也就总是不能正常执行。
然后开始探究gif.worker.js究竟是何方神圣,能不能改造一下。
再然后就认识了webworkers这尊大佛,简直就是前端的一大利器啊!

Web Workers API 的 Worker 接口代表一个可以轻松创建的后台任务,并可以将消息发送回其创建者。创建一个工作程序只要简单的调用Worker() 构造函数,并指定一个要在工作线程中运行的脚本。

前端也可以多线程了?这简直是神迹了!

  • gif.worker.js文件加载

而gif.worker.js显然就是运用了Worker这个,Worker的创建是要传入要执行脚本的url,然后由worker去加载调用。那问题很明显了,就是worker的加载机制,不能正确加载到vue框架下的gif.worker.js文件——相对路径、hash路由,问题多多。

尝试了修改路径、改路由配置等方法失败后,灵光一闪——这不是webpack给的配置吗?加载失败我应该去找webpack啊!

然后就查到了worker-loader,配置好webpack的配置并适当修改gif.js的源码之后,文件终于加载成功了。vue框架下跑成功了!


但是也发现了个严重的问题:用worker-loader方式加载会严重影响worker的性能,对比不用vue框架,速度反而慢下来了。这可就大条了,搬到vue框架下是要提升开发效率的,可不能越搞越慢啊~

具体原因没有深究,但通过实现效果来看,应该是webpack的这种引用方式破坏了worker的多线程的性能,竟然会阻塞用户操作。于是乎开始抛开webpack去加载gif.worker.js。

事实证明,有梦想总是好的。把gif.js和gif.worker.js移到static文件夹下之后,终于成功了,速度贼快、性能贼好。

使用gif.js实现合成动图

可行性验证通过、开发环境配置完成之后,终于可以实践一下了,成品如下图。
前端图形——绘图、截图、合成动图

制作canvas动效-用作截动图

仿照网上的例子,我也实现了一个简单的canvas动效作为素材。也就是动图中的效果,具体实现可以看github上的代码。

gif.js实现动图合成

gif.js支持我们加载canvas对象、image对象或直接拷贝从ctx中拷贝像素点,设置帧延迟、采样间隔、循环方式等。

而原理则是gif相关的:截取多张图片、按照设置的时间间隔依次展示,从而实现动图的效果。

具体的参数设置比如:画图尺寸(主要是缩放效果)、canvas对象截图间隔等等,还有待进一步探究。

结语

从接触canvas到现在也有将近一年,也许是从canvas开始入门前端的缘故,总是会对图形相关的比较有兴趣。现在也算是对这系列的研究有个小结,希望之后有机会接触更酷炫的效果或者游戏相关的,当然前端、算法这些也希望都有突破。嗯,总要有点梦想的,说不定就实现了呢~
前端图形——绘图、截图、合成动图

相关推荐