Canvas效果片段
总结在使用canvas时遇到的一些通用效果实现与相关示例。
透底(剪裁)效果
使用clip API
示例中的蓝色小方块即为剪裁的区域,无透明效果的原始图案。
主要使用了如下三个元素:
-
save()
&restore()
save()
与restore()
是为了保留裁剪前的上下文状态,不然在需要交互(如移动透底框体)场景下,透底区域的图像就不会随着外部canvas内容的变化而改变了。 -
Path2D
region为一个
Path2D
对象,用于声明路径,可以直接在canvas2d上下文中使用。首先在region上添加目标区域矩形,其次添加整体区域矩形,则两者相交叉的部分即为目标的透底(剪裁)区域。之后在上下文上使用clip API应用该region区域即可对目标区域进行透底(裁剪)。
-
clip()
clip有两种填充模式:
nonzero
模式和evenodd
模式,默认为nonzero
。详情可以看张鑫旭的这篇文章了解。本例中使用的是
evenodd
模式(奇偶判断规则)。若想剪裁相关区域可以直接通过例子中targetArea
所代表的目标区域来进行剪裁
相关QA:https://stackoverflow.com/questions/7821384/html-canvas-clip-area-context-restore
笔画效果
在canvas上想实现一个可以自由绘制的画笔效果,需要两个步骤
- 监听鼠标事件得到坐标数据
- 使用处理后的坐标进行绘制
在第2步中有两种方法进行绘制:一种是在监听到鼠标事件并处理坐标后立即绘制,第二种是通过一个标志位(开关)在requestAnimationFrame所执行的函数中进行绘制,区别在于体验时的流畅度、执行的频率以及性能。
鼠标事件
设置开始与结束绘制的标志位
el.onmousedown = function(e) {
isDrawing = true;
points.push({ x: e.clientX, y: e.clientY });
};
el.onmouseup = function() {
isDrawing = false;
points.length = 0;
};
在移动时通过标志点进行绘制,示例中为了使路径光滑使用了二次贝塞尔曲线函数
el.onmousemove = function(e) {
if (!isDrawing) return;
points.push({ x: e.clientX, y: e.clientY });
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
var p1 = points[0];
var p2 = points[1];
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
for (var i = 1, len = points.length; i < len; i++) {
var midPoint = midPointBtw(p1, p2);
ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
p1 = points[i];
p2 = points[i+1];
}
ctx.lineTo(p1.x, p1.y);
ctx.stroke();
};
鼠标事件 + requestAnimationFrame
同样根据判断鼠标事件中设置标志位绘制,不过是在requestAnimationFrame的动画关键帧函数中执行
路径坐标绘制
同样可以使用Path2D来绘制一个给定一系列坐标的轨迹线,需要先将这些坐标点转换成SVG路径,直接传入Path2D构造函数即可。笔者这里用了svg-points
这个库来将坐标点转换成SVG路径。
let points = contour.map(p => {
let [x, y] = p;
return { x, y };
});
points[0].moveTo = true;
let svgPath = toPath(points);
ctx.beginPath();
var p = new Path2D(svgPath);
ctx.stroke(p);
ctx.closePath();
图形或图像的透明效果
RGBA
在绘制时给填充或笔触颜色设置alpha值,就可以实现透明图形或图像的绘制。
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgba(255,0,255,0.2)';
ctx.fillRect(0, 0, 75, 75);
globalAlpha
在canvas context上设定globalAlpha属性可以用来设置之后图形或图像绘制在canvas上的alpha值。
ctx.globalAlpha = 0.5;
ctx.fillStyle = '#FD0';
ctx.fillRect(0, 0, 75, 75); // 该矩形为半透明的
层叠图形的加深效果
若设置globalAlpha后绘制层叠图形,那么层叠的部分会出现透明度加深的效果
全局统一透明度
若想保持图形的透明度统一,可以采用加一层offscreen canvas(通过document.createElement('canvas')
创建)的方式。
offscreen层为不透明的,将其绘制到onscreen层,设置onscreen层的globalAlpha属性即可。
擦除操作
设置globalCompositeOperation
属性进行绘制即可,会将绘制图形的形状未覆盖的部分保留。
ctx.globalCompositeOperation = "destination-out";
在下面这个例子中,绘制的路径形状会被去除,显示为背景的白色。
图片绘制与导出
图片绘制
使用drawImage API
进行绘制。
需要注意的是,表示图像源的第一个参数可以是多种类型:CSSImageValue, HTMLImageElement, SVGImageElement, HTMLVideoElement, HTMLCanvasElement, ImageBitmap, OffscreenCanvas。
即在常见的场景中,可以将图像绘制到画布,也可以将另一个画布的内容绘制到该画布上。
通过调整表示尺寸的source与destination参数可以在绘制时进行一定比例的缩放。
跨域问题
若跨域使用图片资源,并在画布上进行如下三种操作时:
- 在canvas上下文中调用getImageData()
- 在<canvas>元素上调用toBlob()
- 在canvas对象上调用toDataURL()
会提示Tainted canvases may not be exported
的错误,此时需要给Image对象设置一个crossOrigin
属性来解决。
如下是个简单封装的加载image方法:
export const loadImage = imgPath => {
return new Promise((resolve, reject) => {
let img = new Image();
img.setAttribute("crossOrigin", "anonymous"); // to solve "Tainted canvases may not be exported" error
img.onload = () => {
resolve(img);
};
img.onerror = e => {
reject(new Error(e));
};
img.src = imgPath;
});
};
图片导出成base64
let img = await loadImage(imagePath)
let { width, height } = img;
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
let imageData = canvas.toDataURL("image/png");
console.log('imageData: ', imageData)
// imageData: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAg4AAAIOCAYAAADQu4U5AAAgAElEQVR4Xu3dd5zdVZ3/8de5M5M6k4QmLqKsIhYgE4orsjbcddcVewklE5pYsLA2VBQRfooFFxXsNEWSCWBYy+qK7loQFREJGxKwF1BBBYEkM5M6c8/v8Z077EYI8J3J/Z45c+/r+6ee7/mc8zwn4Z3v/ZaAhwIKKKCAAgooUFIglGxnMwUUUEABBRRQAIO...
原始图像与mask遮罩合成
将mask层的alpha通道值赋予原始图像的alpha通道进行结果图的合成
// resultMaskData: { width, height, values }
...
let resultMaskLayer = document.createElement("canvas");
let resultMaskCtx = resultMaskLayer.getContext("2d");
resultMaskLayer.width = img.width;
resultMaskLayer.height = img.height;
resultMaskCtx.drawImage(img, 0, 0);
if (resultMaskData) {
let { width: maskWidth, height: maskHeight, values } = resultMaskData;
let maskData = resultMaskCtx.getImageData(0, 0, maskWidth, maskHeight);
let size = maskWidth * maskHeight;
for (let i = 0; i < size; i++) {
if (values[i] !== 255) {
maskData.data[(i + 1) * 4 - 1] = values[i];
}
}
resultMaskCtx.putImageData(maskData, 0, 0);
}
- 原文作者:yrq110
- 原文链接:http://yrq110.me/post/front-end/canvas-effect-snippets/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。