修改html2canvas使其支持mask-image

背景

有个需求需要实现剪切蒙版的效果,然后发现css里面有个mask-image属性可以比较容易的实现,最后通过html2canvas生成一个封面图,但是发现html2canvas并不支持这个属性,然后看一下html2canvas的提交记录,已经2年没有更新了。所以,自己动手,丰衣足食,只能自己修改了。

源码解析

如果全部代码都看完的话花费的时间肯定是很多,先根据需求来,首先需要的div背景图片的mask-image,只需要了解背景图片是如何实现的就行。
html2canvas的大致流程如下:

在这里插入图片描述

具体的绘制方法在renderStackContent方法里面

renderStatckContent
这是对html层叠上下文的一个实现,刚好一一对应

在这里插入图片描述
所以很容易找到背景的绘制方法,没错,就是renderNodeBackgroundAndBorders(),其原理大致如下:

在这里插入图片描述

通过clip方法,先裁剪出可绘制区域,这样所有的绘制内容就只会在区域内生效,然后再绘制背景色和背景图片,这就是绘制背景图片的原理,了解到这也就可以开始实现需求的功能了。

准备工作

调试

观察package.json的script,其调试主要是start和watch命令
在这里插入图片描述

  • watch会监听代码的修改并且编译
  • start会启动可以访问项目目录的服务,调试页面主要在examples里面,里面是demo页面

所以只要同时启动这两个脚本就可以一边修改代码一边调试效果了

mask属性支持代码

原来的html2canvas的css对象里面是没有mask相关的属性的,要先实现这个,发现它的属性和background的十分一致,那直接抄就好,省了好多功夫。

scr/css/property-descriptors下直接复制background的文件改名

在这里插入图片描述

scr/css/render下直接复制background的文件改名

在这里插入图片描述

实现思路

思路1(失败思路)

这是我一开始的思路,因为遇到了没法解决的问题所以无法实现,但是还是记录一下。流程思路如下:

在这里插入图片描述

剪切蒙版的原理主要是利用下层图层的透明度进行蒙版,所以只要把图片的透明度替换成蒙版的透明度就好了。
主要利用canvas的getImageDataputImageData

context.getImageData(x, y, width, height)用于在画布上复制指定矩形的像素数据,
其参数为:

属性 含义
x 要复制的矩形区域的左上角的x坐标
y 要复制的矩形区域的左上角的y坐标
width 要复制的矩形区域的宽度
height 要复制的矩形区域的高度

其返回值为返回的是一个ImageData对象,该对象包含了三个只读属性:

属性 含义
ImageData.width ImageData的宽度
ImageData.height ImageData的高度
ImageData.data 类型为Uint8ClampedArray的一维数组,每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255

context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)用于图像数据(从指定的 ImageData 对象)放回画布上,其参数为:

属性 含义
imgData 要放回画布的 ImageData 对象
x ImageData 对象左上角的 x 坐标
y ImageData 对象左上角的 y 坐标
dirtyX 可选。水平值(x),在画布上放置图像的位置
dirtyY 可选。垂直值(y),在画布上放置图像的位置
dirtyWidth 可选。在画布上绘制图像所使用的宽度
dirtyHeight 可选。在画布上绘制图像所使用的高度

最后按照流程图来实现功能,最终代码如下:

在这里插入图片描述

实现思路是先保存原先已经绘制的画布像素数据,然后绘制图片并保存图片的像素数据,最后是遮罩数据,最后通过透明度来合并三份数据,再重新绘制到画布上。实现过程中,也遇到了一些问题,刚开始忽略了半透明的存在,因为遮罩是自己ps里面画了个圆,本来想着就是图形就是透明度100%,空白地方是0%,其实圆的最外面有很多非100%透明度的像素,所以刚开始实现的时候圆十分不圆滑,显示非常明显的锯齿。最后找了一个合并两个颜色的算法去合并不是介于0-100之间透明度的颜色,解决了这个问题。

到这里已经十分兴奋了,因为确实可以支持mask-image了,但是,最后发现了一个问题没法解决,这个方法也被放弃了。

ok,这个问题就是图片旁边会出现边框,并且在不同的缩放下不一样。html2canvas的绘制前都会先画出要绘制的图形的边框路径,接着使用clip()方法去裁剪出绘制的区域,clip()方法剪切了某个区域,则所有之后的绘图都会被限制在被剪切的区域内(不能访问画布上的其他区域)。所以整个流程下来,也没有发现什么不对的地方,因为每次绘制的宽高和起点坐标都是一样的,所以瞬间懵掉了。一开始猜测是clearRect()清除不干净,一直搜索为啥clearRect()清除不干净,也没找到所以然。经过了好一番折腾,在stackoverflow找到了一个答案:
在这里插入图片描述

所以即使使用了clip(),也是有可能绘制到剪切的路径之外的,特别是缩放的时候。由于clearRect()的时候是按照clip的路径去执行的,所以调用clearRect()到时候内容一旦绘制到路径之外,肯定没有清除掉,最终产生了内容残留最后看起来像是一个边框。坑爹啊T_T。

思路2

第二种想法是,因为div本身就是一个元素,background-color,background-image,mask-image等属性都在一个canvas合成后,再一次性绘制到画布上就好。

在这里插入图片描述

修改renderNodeBackgroundAndBorders依次绘制内容

async renderNodeBackgroundAndBorders(paint: ElementPaint): Promise<void> {
   
        this.applyEffects(paint.getEffects(EffectTarget.BACKGROUND_BORDERS));
        const
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值