在 HTML5 画布中绘制鼠标移动的半透明线

Posted

技术标签:

【中文标题】在 HTML5 画布中绘制鼠标移动的半透明线【英文标题】:Drawing semi-transparent lines on mouse movement in HTML5 canvas 【发布时间】:2013-12-26 19:16:48 【问题描述】:

我试图让用户通过在画布上绘制半透明线条的“绘画”工具在区域上绘画来指定区域。其目的是为将在画布下方绘制的图像指定一个“蒙版”。

这是我迄今为止尝试过的:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasPos = canvas.getBoundingClientRect();

var dragging = false;

drawImage();

$(canvas).mousedown(mouseDown);
$(canvas).mouseup(mouseUp);
$(canvas).mousemove(mouseMove);

function drawImage() 
    var img = new Image();
    img.src = 'http://img2.timeinc.net/health/img/web/2013/03/slides/cat-allergies-400x400.jpg';

    img.onload = function () 
        ctx.drawImage(img, 0, 0);
    ;


function mouseDown(e) 
    var pos = getCursorPosition(e);

    dragging = true;

    ctx.strokeStyle = 'rgba(0, 100, 0, 0.25)';
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.lineWidth = 15;
    ctx.beginPath();
    ctx.moveTo(pos.x, pos.y);


function mouseUp(e) 
    dragging = false;


function mouseMove(e) 
    var pos, i;

    if (!dragging) 
        return;
    

    pos = getCursorPosition(e);

    ctx.lineTo(pos.x, pos.y);
    ctx.stroke();


function getCursorPosition(e) 
    return 
        x: e.clientX - canvasPos.left,
        y: e.clientY - canvasPos.top
    ;

链接到上述代码的jsfiddle:http://jsfiddle.net/s34PL/2/

此示例代码的问题在于,随后绘制的像素使不透明度变得越来越不可见。我认为这是因为这条线是 15 像素宽(但我希望它那么宽)。

我该如何解决这个问题?

谢谢!

【问题讨论】:

Kangax wrote an awesome blog post on this topic 最近。 @Pointy,我在发布这个问题之前实际上已经阅读了它。确实是一篇很棒的 帖子,学到了很多东西——但我没有找到针对我的特定问题的解决方案? 【参考方案1】:

问题是你一遍又一遍地绘制整个路径:

function mouseMove(e) 
    ...
    ctx.stroke(); // Draws whole path which begins where mouseDown happened.

您只需绘制路径的新段 (http://jsfiddle.net/jF9a6/)。然后......你遇到了 15px 宽度的问题。

那么如何解决呢?我们必须像你一样立即画线,但要避免在现有线的顶部画线。这是代码:http://jsfiddle.net/yfDdC/

最大的变化是paths 数组。它包含是的,路径:-) 路径是存储在mouseDownmouseMove 函数中的点数组。在 mouseDown 函数中创建新路径:

paths.push([pos]); // Add new path, the first point is current pos.

在 mouseMove 中,您将当前鼠标位置添加到 paths 数组中的最后一个路径并刷新图像。

paths[paths.length-1].push(pos); // Append point tu current path.
refresh();

refresh() 函数清除整个画布,再次绘制猫并绘制每条路径。

function refresh() 
    // Clear canvas and draw the cat.
    ctx.clearRect(0, 0, ctx.width, ctx.height);
    if (globImg)
        ctx.drawImage(globImg, 0, 0);

    for (var i=0; i<paths.length; ++i) 
        var path = paths[i];

        if (path.length<1)
            continue; // Need at least two points to draw a line.

        ctx.beginPath();
        ctx.moveTo(path[0].x, path[0].y);
        ... 
        for (var j=1; j<path.length; ++j)
            ctx.lineTo(path[j].x, path[j].y);
        ctx.stroke();

    

【讨论】:

谢谢!我理解关于避免一遍又一遍地绘制相同的“旧”路径的第一部分。但是在您的实际解决方案中(保存由 mousedown > mousemove 事件记录的所有路径),您会在每个 mousemove 事件中重新绘制所有路径,包括猫图像。这是解决这个问题的必要条件吗?最后一件事 - 是否可以避免使用新路径在旧路径上运行(这会使不透明度变得不那么透明)? 看看我发布的第一个 jsfiddle (jsfiddle.net/jF9a6)。在这里,我完全按照您的建议进行操作,并且出现了线宽问题。那是因为您一次绘制一个段的路径,因此段之间的重叠区域变得更暗。如果您一次绘制整个路径,则重叠区域仅按预期绘制一次。 如果您不想在穿过旧路径时降低不透明度,您可以稍微修改一下 refresh() 函数jsfiddle.net/Q6R3y 现在所有不同的路径都会一次绘制。 谢谢,很好的回答!在每次鼠标移动时(一次)绘制图像 + 所有路径 - 这对性能有很大影响吗? 这取决于您要达到的目标。测试一下,你会看到 :-) 它适用于小 kitteh 图像。【参考方案2】:

另一种方法是绘制实心路径并使整个画布透明。当然,您必须将图像移出画布并将其堆叠在下面。你可以在这里找到代码:http://jsfiddle.net/fP297/

<div style="position: relative; border: 1px solid black; width: 400px; height: 400px;">
    <img src='cat.jpg' style="position: absolute; z-order: 1;">
    <canvas id="canvas"   style="position: absolute; z-order: 2; opacity: 0.25;"></canvas>
</div>

通过绘制实线,您不必担心多次绘制一个区域,因此您不必担心擦除图像并重新绘制所有内容。

【讨论】:

【参考方案3】:

我同意 Strix。 在这里你会找到一个基于他的回答的例子。

    //    
    let mouseDownStartPosition: number | null = null;
    let mouseDownLastPaintedPosition: number | null = null;
    
    const drawRect = (canvas: htmlCanvasElement, pixelX0 : number, pixelX1: number) => 
        const context: CanvasRenderingContext2D = canvas.getContext('2d')!;

        context.globalAlpha = 0.3;
        context.fillStyle = "#bada55";
        context.fillRect(pixelX0, 0, pixelX1 - pixelX0, context.canvas.height);
        context.globalAlpha = 1.0;
    

    const onCanvasMouseDown = (e:  clientX: number; ) => 
        const canvas: HTMLCanvasElement = canvasRef.current!;
        let rect = canvas.getBoundingClientRect();
        mouseDownStartPosition = e.clientX - rect.left;
        mouseDownLastPaintedPosition = mouseDownStartPosition;
    

    const onCanvasMouseMove = (e:  clientX: number; ) => 
        if (mouseDownLastPaintedPosition == null) return;

        const canvas: HTMLCanvasElement = canvasRef.current!;
        let rect = canvas.getBoundingClientRect();
        const mouseCurrentPosition = e.clientX - rect.left;
        drawRect(canvas, Math.min(mouseDownLastPaintedPosition, mouseCurrentPosition), Math.max(mouseDownLastPaintedPosition, mouseCurrentPosition));
        mouseDownLastPaintedPosition = mouseCurrentPosition;
    

    const onCanvasMouseUp = () => 
        mouseDownStartPosition = null;
        mouseDownLastPaintedPosition = null;
    
                <MyCanvas
                    ref=canvasRef
                    onMouseDown=onCanvasMouseDown
                    onMouseMove=onCanvasMouseMove
                    onMouseUp=onCanvasMouseUp
                />

【讨论】:

以上是关于在 HTML5 画布中绘制鼠标移动的半透明线的主要内容,如果未能解决你的问题,请参考以下文章

Material Design 中呈现的 Android Lollipop 上的半透明渐变状态栏

Matlab 图形中的半透明标记

iPhone目标C中错误的半透明视图

unity部分模型半透明了怎么办

泰山OFFICE技术讲座:页面内容区的半透明范围

泰山OFFICE技术讲座:页面内容区的半透明范围