如何使用 FabricJS 创建不规则形状的 HTML Canvas?
How can I create an irregularly shaped HTML Canvas using FabricJS?
我已经尝试创建一个三角形 canvas 一天左右了,但我没有成功。 canvas 始终是 square/rectangle。我正在使用 FabricJS,但如果可以的话,我也可以直接操作 canvas。
我尝试使用 .clipTo(ctx) 来剪辑 canvas,如下所述:Canvas in different shapes with fabricjs plugin
我也曾尝试直接操纵 canvas,正如我在此处看到的那样:https://www.html5canvastutorials.com/tutorials/html5-canvas-custom-shapes/
我想要完成的是让用户将图像拖放到 canvas 的三角形上,这样三角形之外的图像就不会 "bleed" 了。我用一个矩形轻松完成了这个,但我不知道如何更改 canvas 形状。或者,如果有人有一个 "trick" 解决方案,看起来 canvas 是一个三角形,但在引擎盖下仍然是一个正方形,那也可以。
使用纯API的
我不使用织物(特别是如果它只是用于简单的图像处理)所以你必须找到合适的 fabric functions 来匹配这个答案。
canvas 始终是 4 面的。 2D 和 3D 变换可以改变形状,但也会改变所包含像素的形状。
您有 2 个简单的选项。还有其他方法可以做到这一点,但它们很复杂并且存在兼容性问题。
仅视觉
屏蔽
要获得不规则形状的外观 canvas,您可以使用蒙版(第二个 canvas 有蒙版)。将内容绘制到主canvas,然后用掩码canvas遮住
使用 属性 CanvasRenderingContext2D.globalCompositeOperation
定义蒙版的应用方式。
例如
function createTriangleMask(w, h) {
const mask = document.createElement("canvas");
mask.width = w;
mask.height = h;
mask.ctx = mask.getContext("2d");
mask.ctx.beginPath();
mask.ctx.lineTo(w / 2, 0);
mask.ctx.lineTo(w , h);
mask.ctx.lineTo(0 , h);
mask.ctx.fill();
return mask;
}
const mask = createTriangleMask(ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(myImg, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(mask, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "source-over";
使用 2D clip
或者您可以使用 2D API CanvasRenderingContext2D.clip
创建剪辑区域并在剪辑处于活动状态时绘制内容。不要忘记在剪辑完成后弹出 2D 状态,
function triangleClip(ctx, w, h) {
ctx.save();
ctx.beginPath();
mask.ctx.lineTo(w / 2, 0);
mask.ctx.lineTo(w , h);
mask.ctx.lineTo(0 , h);
ctx.clip();
}
triangleClip(ctx, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(myImg, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore(); // Turn off clip. Must do before calling triangle clip again.
还是矩形!
这并没有改变 canvas 的形状。它仍然是一个矩形,只是一些像素是透明的。 DOM 仍然看到一个矩形,用户与 canvas 的交互仍将使用整个矩形 canvas。
CSSclip-path
您可以使用样式 属性 clip-path
来定义元素的形状。这将剪辑元素视觉内容和元素交互区域。有效地将任何适用的元素转变为不规则形状的元素。
使用 JS 声明
canvas.style.clipPath = "polygon(50% 0, 100% 100%, 0% 100%)"
使用 JS
function clipElement(el, shape) {
var rule = "polygon(", i = 0, comma = "";
while (i < shape.length) {
rule += comma + shape[i++] + "% " + shape[i++] + "%";
comma = ",";
}
el.style.clipPath = rule + ")";
}
clipElement(canvas, [50, 0, 100, 100, 0, 100]);
使用 CSS 规则
canvas {
clip-path: polygon(50% 0, 100% 100%, 0% 100%);
}
在剪切路径到位后,canvas 将通过 UI
遵循其形状
canvas.style.cursor = "pointer"; // Pointer change only inside clipped area
canvas.title = "foo"; // appears only when over clipped area
canvas.addEventListener("mouseover", () => console.log("foo")); // fires when crossing
// clip boundary
演示
通过 JS 在 canvas 元素上创建动画剪辑,内容渲染一次。
有限制
注意 CSS 定义的背景颜色(黄色)和阴影也被剪裁。许多其他视觉属性也将被剪裁。
注意 如果没有干预用户迭代,JS 动画不会更新 UI 事件。
动画也可以通过CSS实现。
我不知道与面料的兼容性,请检查他们的 documentation。
var clearConsole = 0;
const s = 2 ** 0.5 * 0.25, clipPath = [0.5, 0, 0.5 + s, 0.5 + s, 0.5 - s, 0.5 + s], img = new Image;
img.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
img.addEventListener("load",() => canvas.getContext("2d").drawImage(img, 0, 0, 300, 300), {once: true});
requestAnimationFrame(animateLoop);
function clipRotate(el, ang, scale, path) {
const dx = Math.cos(ang) * scale;
const dy = Math.sin(ang) * scale;
var clip = "polygon(", i = 0, comma = "";
while (i < path.length) {
const x = path[i++] - 0.5;
const y = path[i++] - 0.5;
clip += comma;
clip += ((x * dx - y * dy + 0.5) * 100) + "% ";
clip += ((x * dy + y * dx + 0.5) * 100) + "%";
comma = ",";
}
el.style.clipPath = clip + ")";
}
function animateLoop(time) {
clipRotate(canvas, time / 1000 * Math.PI, 0.9, clipPath);
requestAnimationFrame(animateLoop);
if (clearConsole) {
clearConsole --;
!clearConsole && console.clear();
}
}
canvas.addEventListener("pointerenter", () => (clearConsole = 30, console.log("Pointer over")));
body {
background-color: #49C;
}
canvas {
cursor: pointer;
background-color: yellow;
box-shadow: 12px 12px 4px rgba(0,0,0,0.8);
}
<canvas id="canvas" width="300" height="300" title="You are over the clipped canvas"></canvas>
我已经尝试创建一个三角形 canvas 一天左右了,但我没有成功。 canvas 始终是 square/rectangle。我正在使用 FabricJS,但如果可以的话,我也可以直接操作 canvas。
我尝试使用 .clipTo(ctx) 来剪辑 canvas,如下所述:Canvas in different shapes with fabricjs plugin
我也曾尝试直接操纵 canvas,正如我在此处看到的那样:https://www.html5canvastutorials.com/tutorials/html5-canvas-custom-shapes/
我想要完成的是让用户将图像拖放到 canvas 的三角形上,这样三角形之外的图像就不会 "bleed" 了。我用一个矩形轻松完成了这个,但我不知道如何更改 canvas 形状。或者,如果有人有一个 "trick" 解决方案,看起来 canvas 是一个三角形,但在引擎盖下仍然是一个正方形,那也可以。
使用纯API的
我不使用织物(特别是如果它只是用于简单的图像处理)所以你必须找到合适的 fabric functions 来匹配这个答案。
canvas 始终是 4 面的。 2D 和 3D 变换可以改变形状,但也会改变所包含像素的形状。
您有 2 个简单的选项。还有其他方法可以做到这一点,但它们很复杂并且存在兼容性问题。
仅视觉
屏蔽
要获得不规则形状的外观 canvas,您可以使用蒙版(第二个 canvas 有蒙版)。将内容绘制到主canvas,然后用掩码canvas遮住
使用 属性 CanvasRenderingContext2D.globalCompositeOperation
定义蒙版的应用方式。
例如
function createTriangleMask(w, h) {
const mask = document.createElement("canvas");
mask.width = w;
mask.height = h;
mask.ctx = mask.getContext("2d");
mask.ctx.beginPath();
mask.ctx.lineTo(w / 2, 0);
mask.ctx.lineTo(w , h);
mask.ctx.lineTo(0 , h);
mask.ctx.fill();
return mask;
}
const mask = createTriangleMask(ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(myImg, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(mask, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.globalCompositeOperation = "source-over";
使用 2D clip
或者您可以使用 2D API CanvasRenderingContext2D.clip
创建剪辑区域并在剪辑处于活动状态时绘制内容。不要忘记在剪辑完成后弹出 2D 状态,
function triangleClip(ctx, w, h) {
ctx.save();
ctx.beginPath();
mask.ctx.lineTo(w / 2, 0);
mask.ctx.lineTo(w , h);
mask.ctx.lineTo(0 , h);
ctx.clip();
}
triangleClip(ctx, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(myImg, 0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore(); // Turn off clip. Must do before calling triangle clip again.
还是矩形!
这并没有改变 canvas 的形状。它仍然是一个矩形,只是一些像素是透明的。 DOM 仍然看到一个矩形,用户与 canvas 的交互仍将使用整个矩形 canvas。
CSSclip-path
您可以使用样式 属性 clip-path
来定义元素的形状。这将剪辑元素视觉内容和元素交互区域。有效地将任何适用的元素转变为不规则形状的元素。
使用 JS 声明
canvas.style.clipPath = "polygon(50% 0, 100% 100%, 0% 100%)"
使用 JS
function clipElement(el, shape) {
var rule = "polygon(", i = 0, comma = "";
while (i < shape.length) {
rule += comma + shape[i++] + "% " + shape[i++] + "%";
comma = ",";
}
el.style.clipPath = rule + ")";
}
clipElement(canvas, [50, 0, 100, 100, 0, 100]);
使用 CSS 规则
canvas {
clip-path: polygon(50% 0, 100% 100%, 0% 100%);
}
在剪切路径到位后,canvas 将通过 UI
遵循其形状canvas.style.cursor = "pointer"; // Pointer change only inside clipped area
canvas.title = "foo"; // appears only when over clipped area
canvas.addEventListener("mouseover", () => console.log("foo")); // fires when crossing
// clip boundary
演示
通过 JS 在 canvas 元素上创建动画剪辑,内容渲染一次。
有限制
注意 CSS 定义的背景颜色(黄色)和阴影也被剪裁。许多其他视觉属性也将被剪裁。
注意 如果没有干预用户迭代,JS 动画不会更新 UI 事件。
动画也可以通过CSS实现。
我不知道与面料的兼容性,请检查他们的 documentation。
var clearConsole = 0;
const s = 2 ** 0.5 * 0.25, clipPath = [0.5, 0, 0.5 + s, 0.5 + s, 0.5 - s, 0.5 + s], img = new Image;
img.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
img.addEventListener("load",() => canvas.getContext("2d").drawImage(img, 0, 0, 300, 300), {once: true});
requestAnimationFrame(animateLoop);
function clipRotate(el, ang, scale, path) {
const dx = Math.cos(ang) * scale;
const dy = Math.sin(ang) * scale;
var clip = "polygon(", i = 0, comma = "";
while (i < path.length) {
const x = path[i++] - 0.5;
const y = path[i++] - 0.5;
clip += comma;
clip += ((x * dx - y * dy + 0.5) * 100) + "% ";
clip += ((x * dy + y * dx + 0.5) * 100) + "%";
comma = ",";
}
el.style.clipPath = clip + ")";
}
function animateLoop(time) {
clipRotate(canvas, time / 1000 * Math.PI, 0.9, clipPath);
requestAnimationFrame(animateLoop);
if (clearConsole) {
clearConsole --;
!clearConsole && console.clear();
}
}
canvas.addEventListener("pointerenter", () => (clearConsole = 30, console.log("Pointer over")));
body {
background-color: #49C;
}
canvas {
cursor: pointer;
background-color: yellow;
box-shadow: 12px 12px 4px rgba(0,0,0,0.8);
}
<canvas id="canvas" width="300" height="300" title="You are over the clipped canvas"></canvas>