【问题标题】:How can I create an irregularly shaped HTML Canvas using FabricJS?如何使用 FabricJS 创建形状不规则的 HTML 画布?
【发布时间】:2020-05-17 15:13:07
【问题描述】:

我一直在尝试创建一个三角形的画布一天左右,但我没有运气。画布始终是方形/矩形。我正在使用 FabricJS,但如果可以的话,我也可以直接操作画布。

我尝试使用 .clipTo(ctx) 来剪辑画布,如下所述:Canvas in different shapes with fabricjs plugin

我也尝试过直接操作画布,如下所示:https://www.html5canvastutorials.com/tutorials/html5-canvas-custom-shapes/

我想要完成的是让用户将图像拖放到三角形画布上,这样三角形外的图像就不会“流血”。我用一个矩形很容易做到了这一点,但我不知道如何改变画布的形状。或者,如果有人有一个“技巧”解决方案,看起来画布是一个三角形,但引擎盖下仍然是一个正方形,那也可以。

【问题讨论】:

    标签: html html5-canvas fabricjs


    【解决方案1】:

    使用纯 API

    我不使用织物(特别是如果它只是用于简单的图像处理),因此您必须找到合适的 fabric functions 以匹配此答案。

    画布始终是 4 面的。 2D 和 3D 变换可以改变形状,但也会改变所包含像素的形状。

    您有 2 个简单的选择。还有其他方法可以做到这一点,但它们很复杂并且存在兼容性问题。

    仅视觉

    屏蔽

    要获得不规则形状画布的外观,您可以使用遮罩(第二张画布有遮罩)。将内容绘制到主画布上,然后用遮罩遮盖该画布。

    使用属性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.
    

    还是矩形!

    这并没有改变画布的形状。它仍然是一个矩形,只是有些像素是透明的。 DOM 仍会看到一个矩形,用户与画布的交互仍将使用整个矩形画布。

    CSS clip-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%);
    }
    

    剪裁路径到位后,画布将通过 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);
    }
    &lt;canvas id="canvas" width="300" height="300" title="You are over the clipped canvas"&gt;&lt;/canvas&gt;

    【讨论】:

    • 感谢您提供的所有示例!在我标记为答案之前,仍在使用 Fabric 中的一些。
    猜你喜欢
    • 1970-01-01
    • 2015-11-06
    • 2017-03-08
    • 1970-01-01
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    相关资源
    最近更新 更多