javascript 在 <canvas> 元素上实现流畅的草图和绘图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10567287/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 10:16:59  来源:igfitidea点击:

Implementing smooth sketching and drawing on the <canvas> element

javascripthtmlcanvasdrawingpaint

提问by ryuutatsuo

I am trying to create a drawing area with canvas. I am having trouble with making the lines look smooth when drawing curves and I also have changing line thickness in my algorithm which looks bad as well because the size jumps to much as well and you can see where the size changed. I did find this link on stackoverflowbut this was for a native iPhone app and I can't figure it out.

我正在尝试使用画布创建绘图区域。我在绘制曲线时无法使线条看起来平滑,而且我的算法中的线条粗细也发生了变化,这看起来也很糟糕,因为尺寸也跳得很大,你可以看到尺寸变化的地方。我确实在 stackoverflow 上找到了这个链接,但这是针对本机 iPhone 应用程序的,我无法弄清楚。

Here is my current JS code. and Here is it running on jsFiddle

这是我当前的 JS 代码。这是它在 jsFiddle 上运行的

var xStart,
xEnd,
yStart,
yEnd,
paint,
ctx;
$(document).ready(function (){

   ctx = $('canvas')[0].getContext("2d");
   ctx.strokeStyle = '#000';
   ctx.lineJoin="round";
   ctx.lineCap="round";
   ctx.lineWidth = 1;


   $('canvas').bind('mousedown mousemove mouseup mouseleave touchstart touchmove touchend', function(e){
        var orig = e.originalEvent;

        if(e.type == 'mousedown'){
            e.preventDefault(); e.stopPropagation();

            xStart = e.clientX - $(this).offset().left;
            yStart = e.clientY - $(this).offset().top;
            xEnd = xStart;
            yEnd = yStart;

            paint = true;
            draw(e.type);

        }else if(e.type == 'mousemove'){
            if(paint==true){
                xEnd = e.clientX - $(this).offset().left;
                yEnd = e.clientY - $(this).offset().top;


               lineThickness = 1 + Math.sqrt((xStart - xEnd) *(xStart-xEnd) + (yStart - yEnd) * (yStart-yEnd))/5;

               if(lineThickness > 10){
                    lineThickness = 10;   
               }

                ctx.lineWidth = lineThickness;
                draw(e.type);
            }
        }else if(e.type == 'mouseup'){
            paint = false;
        }else if(e.type == 'mouseleave'){
            paint = false;
        }else if(e.type == 'touchstart'){
            if(orig.touches.length == 1){
                e.preventDefault(); e.stopPropagation();

                xStart = orig.changedTouches[0].pageX - $(this).offset().left;
                yStart = orig.changedTouches[0].pageY - $(this).offset().top;
                xEnd = xStart;
                yEnd = yStart; 

                paint = true;
                draw(e.type);
            }
        }else if(e.type == 'touchmove'){
            if(orig.touches.length == 1){
                if(paint==true){
                    xEnd = orig.changedTouches[0].pageX - $(this).offset().left;
                    yEnd = orig.changedTouches[0].pageY - $(this).offset().top;


                            lineThickness = 1 + Math.sqrt((xStart - xEnd) *(xStart-xEnd) + (yStart - yEnd) * (yStart-yEnd))/6;
                       if(lineThickness > 10){
                          lineThickness = 10;   
                       }


                      ctx.lineWidth = lineThickness;


                    draw(e.type);
                }
            }
        }else if(e.type == 'touchend'){
            paint = false;
        }

      });
    });


    function draw(event){

    if(event == 'mousedown'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }else if(event == 'mousemove'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }else if(event == 'touchstart'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }else if(event == 'touchmove'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }
    xStart = xEnd;
    yStart = yEnd;                  
}

Thank you all in advance.

谢谢大家。

This is what it looks like right now if you draw. current (jagged) implementation

如果你画画,这就是它现在的样子。 当前(锯齿状)实现

... and this is what I would love to achieve:

......这就是我想要实现的目标:

smooth brushstrokes

流畅的笔触

采纳答案by Alex

I made something like this a while ago and turned it into a jquery plugin. have a look over here, if it's what you're after I'll post a more detailed answer and dig out the simplified jquery version from my archives:

不久前我做了一个类似的东西,然后把它变成了一个 jquery 插件。看看这里,如果这是你想要的,我会发布更详细的答案并从我的档案中挖掘出简化的 jquery 版本:

http://jsfiddle.net/95tft/

http://jsfiddle.net/95tft/

EDIT

编辑

OK, sorry I couldn't do this yesterday:

好吧,抱歉我昨天没能做到:

Originally the code above was forked from Mr Doob's 'harmony' sketcher over here: http://mrdoob.com/projects/harmony/#ribbon

最初上面的代码是从 Doob 先生的“和谐”草图在这里分叉出来的:http: //mrdoob.com/projects/harmony/#ribbon

(which I think is the best solution). But I kinda broke it down and remade it for my own purposes on another project. I've hacked my own plugin a bit to make it a bit easier still over here:

(我认为这是最好的解决方案)。但我有点把它分解并在另一个项目中为我自己的目的重新制作。我已经破解了我自己的插件,让它在这里更容易一些:

http://jsfiddle.net/dh3bj/

http://jsfiddle.net/dh3bj/

The only thing you might want to change is to change it to work on mousedown/mouseup which should be easy also have a look at the settings at the bottom of the plugin, you should be able to get the effect you want by playing with the brush size, colour, alpha (rgba) etc.

您可能想要更改的唯一一件事是将其更改为在 mousedown/mouseup 上工作,这应该很容易还可以查看插件底部的设置,您应该能够通过使用画笔大小、颜色、alpha (rgba) 等。

Hope that helps

希望有帮助

回答by Simon Sarris

Have a look at this code:

看看这个代码:

http://jsfiddle.net/aMmVQ/

http://jsfiddle.net/aMmVQ/

What I'm doing is starting a new list of points on mouseDown, then for each mousemove I add a point to the list. Once I get enough points (6 or so) I start drawing quadratic curves, with the control point of the curve being the average of the current point and the next point.

我正在做的是在 mouseDown 上开始一个新的点列表,然后为每个 mousemove 添加一个点到列表中。一旦我得到足够的点(6 个左右),我就开始绘制二次曲线,曲线的控制点是当前点和下一个点的平均值。

drawPointsis the bit that works this magic:

drawPoints是这个魔法的位:

function drawPoints(ctx, points) {
    // draw a basic circle instead
    if (points.length < 6) {
        var b = points[0];
        ctx.beginPath(), ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0), ctx.closePath(), ctx.fill();
        return
    }
    ctx.beginPath(), ctx.moveTo(points[0].x, points[0].y);
    // draw a bunch of quadratics, using the average of two points as the control point
    for (i = 1; i < points.length - 2; i++) {
        var c = (points[i].x + points[i + 1].x) / 2,
            d = (points[i].y + points[i + 1].y) / 2;
        ctx.quadraticCurveTo(points[i].x, points[i].y, c, d)
    }
    ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y), ctx.stroke()
}

回答by Jong-Chan Choi

Why don't you use croquis.js?

你为什么不使用croquis.js

It has neat brush implementation like photoshop:)

它有像Photoshop一样整洁的画笔实现:)

And here is Demowhich is using croquis.js.

这里是演示其使用croquis.js。

brush-preview

刷预览

回答by antyrat

Seems that you need to use some brushesin your canvas. It's hard to say what kind of brush exactly you need but there is many JS libraries that has already implement brush technology.

似乎您需要在画布中使用一些画笔。很难说你到底需要什么样的画笔,但是已经有很多JS库已经实现了画笔技术。

For example did you look at this libraries?

例如,你看过这个图书馆吗?

Laso in web you can find many brushes implemented in Mr. Doob Harmonyproject. For example stringyor Harmony-Brushesproject on github.

Laso in web 你可以找到很多在 Mr. Doob Harmony项目中实现的画笔。例如github 上的stringyHarmony-Brushes项目。

回答by george calvert

Suggest the rendering be done with a chain of bezier curves which surround the curve which is thus filled. (ie end with ctx.fill) Still lots of work to do but hope this helps.

建议使用一串贝塞尔曲线来完成渲染,这些曲线围绕着被填充的曲线。(即以 ctx.fill 结尾)还有很多工作要做,但希望这会有所帮助。

Adapted a nice demo app for bezier curves

为贝塞尔曲线改编了一个不错的演示应用程序

added it to a fork of your fiddle http://jsfiddle.net/d3zFU/1/

将它添加到你的小提琴的叉子 http://jsfiddle.net/d3zFU/1/

Code is

代码是

/*
 * Canvas curves example
 *
 * By Craig Buckler,        http://twitter.com/craigbuckler
 * of OptimalWorks.net        http://optimalworks.net/
 * for SitePoint.com        http://sitepoint.com/
 *
 * Refer to:
 * http://blogs.sitepoint.com/html5-canvas-draw-quadratic-curves/
 * http://blogs.sitepoint.com/html5-canvas-draw-bezier-curves/
 *
 * This code can be used without restriction.
 */

(function() {

(功能() {

var canvas, ctx, code, point, style, drag = null, dPoint;

// define initial points
function Init(quadratic) {

    point = {
        p1: { x:100, y:250 },
        p2: { x:400, y:250 }
    };

    if (quadratic) {
        point.cp1 = { x: 250, y: 100 };
    }
    else {
        point.cp1 = { x: 150, y: 100 };
        point.cp2 = { x: 350, y: 100 };
    }

    // default styles
    style = {
        curve:    { width: 6, color: "#333" },
        cpline:    { width: 1, color: "#C00" },
        point: { radius: 10, width: 2, color: "#900", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI }
    }

    // line style defaults
    ctx.lineCap = "round";
    ctx.lineJoin = "round";

    // event handlers
    canvas.onmousedown = DragStart;
    canvas.onmousemove = Dragging;
    canvas.onmouseup = canvas.onmouseout = DragEnd;

    DrawCanvas();
}


// draw canvas
function DrawCanvas() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // control lines
    ctx.lineWidth = style.cpline.width;
    ctx.strokeStyle = style.cpline.color;
    ctx.fillStyle = style.cpline.color;
    ctx.beginPath();
    ctx.moveTo(point.p1.x, point.p1.y);
    ctx.lineTo(point.cp1.x, point.cp1.y);
    if (point.cp2) {
        ctx.moveTo(point.p2.x, point.p2.y);
        ctx.lineTo(point.cp2.x, point.cp2.y);
    }
    else {
        ctx.lineTo(point.p2.x, point.p2.y);
    }
    ctx.stroke();

    // curve
ctx.lineWidth = 1 ; //style.curve.width;
    ctx.strokeStyle = style.curve.color;
    ctx.beginPath();
    ctx.moveTo(point.p1.x, point.p1.y);
    if (point.cp2) {
        ctx.bezierCurveTo(point.cp1.x, point.cp1.y, point.cp2.x, point.cp2.y, point.p2.x, point.p2.y);
        ctx.bezierCurveTo(point.cp2.x, point.cp2.y+12, point.cp1.x, point.cp1.y+12, point.p1.x, point.p1.y);

    }
    else {
        ctx.quadraticCurveTo(point.cp1.x, point.cp1.y, point.p2.x, point.p2.y);
    }
//ctx.stroke();
ctx.fill();

    // control points
    for (var p in point) {
        ctx.lineWidth = style.point.width;
        ctx.strokeStyle = style.point.color;
        ctx.fillStyle = style.point.fill;
        ctx.beginPath();
        ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true);
        ctx.fill();
        ctx.stroke();
    }

    ShowCode();
}


// show canvas code
function ShowCode() {
    if (code) {
        code.firstChild.nodeValue =
            "canvas = document.getElementById(\"canvas\");\n"+
            "ctx = canvas.getContext(\"2d\")\n"+
            "ctx.lineWidth = " + style.curve.width +
            ";\nctx.strokeStyle = \"" + style.curve.color +
            "\";\nctx.beginPath();\n" +
            "ctx.moveTo(" + point.p1.x + ", " + point.p1.y +");\n" +
            (point.cp2 ?
                "ctx.bezierCurveTo("+point.cp1.x+", "+point.cp1.y+", "+point.cp2.x+", "+point.cp2.y+", "+point.p2.x+", "+point.p2.y+");" :
                "ctx.quadraticCurveTo("+point.cp1.x+", "+point.cp1.y+", "+point.p2.x+", "+point.p2.y+");"
            ) +
            "\nctx.stroke();"
        ;
    }
}


// start dragging
function DragStart(e) {
    e = MousePos(e);
    var dx, dy;
    for (var p in point) {
        dx = point[p].x - e.x;
        dy = point[p].y - e.y;
        if ((dx * dx) + (dy * dy) < style.point.radius * style.point.radius) {
            drag = p;
            dPoint = e;
            canvas.style.cursor = "move";
            return;
        }
    }
}


// dragging
function Dragging(e) {
    if (drag) {
        e = MousePos(e);
        point[drag].x += e.x - dPoint.x;
        point[drag].y += e.y - dPoint.y;
        dPoint = e;
        DrawCanvas();
    }
}


// end dragging
function DragEnd(e) {
    drag = null;
    canvas.style.cursor = "default";
    DrawCanvas();
}


// event parser
function MousePos(event) {
    event = (event ? event : window.event);
    return {
        x: event.pageX - canvas.offsetLeft,
        y: event.pageY - canvas.offsetTop
    }
}


// start
canvas = document.getElementById("canvas");
code = document.getElementById("code");
if (canvas.getContext) {
    ctx = canvas.getContext("2d");
    Init(canvas.className == "quadratic");
}

})();

})();

回答by tfmontague

For those interested in a click version of the code provided by @Alex, I've rewritten his script here:

对于那些对@Alex 提供的代码的点击版本感兴趣的人,我在这里重写了他的脚本:

http://jsbin.com/aqoqad/3/

http://jsbin.com/aqoqad/3/

回答by Cristian

Important!

重要的!

I collected some needed parts and attached here all of that!

我收集了一些需要的零件并附在此处!

<canvas id="paint_board" width="500" height="800" style="border: 1px solid;"></canvas>

<script>
var el = document.getElementById('paint_board');
// rect gets the cavas left top value in browser
var rect = el.getBoundingClientRect();
var ctx = el.getContext('2d');
    ctx.lineJoin = ctx.lineCap = 'round';
    ctx.lineWidth = 1;
var isDrawing, pen_type=1;
var screenWidth=500, screenHeight=500;  //set canvas width and height
var strokes=20;                         //how many strokes to draw
var color = [0, 0, 0]; // color val RGB 0-255, 0-255, 0-255
var painters = [], unpainters = [], timers = [];
var brushPressure=1; // brush Opacity
var easing = 0.7; // kind of "how loopy" higher= bigger loops
var refreshRate = 30; // set this higher if performace is an issue directly affects easing
var mouseX = screenWidth / 2, mouseY = screenHeight / 2;
var testinterval;

pen_init();
function pen_init(){
    for(var i = 0; i < strokes; i++) {
        var ease = Math.random() * 0.05 + easing;
        painters.push({
            dx : screenWidth / 2,
            dy : screenHeight / 2,
            ax : 0,
            ay : 0,
            div : 0.1,
            ease : ease
        });
    }
    testinterval = setInterval(update, refreshRate);
    function update() {
        var i;
        ctx.strokeStyle = "rgba(" + color[0] + ", " + color[1] + ", " + color[2] + ", " + brushPressure + ")";
        for( i = 0; i < painters.length; i++) {
            ctx.beginPath();
            var dx = painters[i].dx;
            var dy = painters[i].dy;
            ctx.moveTo(dx, dy);
            var dx1 = painters[i].ax = (painters[i].ax + (painters[i].dx - mouseX) * painters[i].div) * painters[i].ease;
            painters[i].dx -= dx1;
            var dx2 = painters[i].dx;
            var dy1 = painters[i].ay = (painters[i].ay + (painters[i].dy - mouseY) * painters[i].div) * painters[i].ease;
            painters[i].dy -= dy1;
            var dy2 = painters[i].dy;
            ctx.lineTo(dx2, dy2);
            ctx.stroke();
        }
    }
}
el.onmousedown = function(e) {
    isDrawing = true;
    mouseX = e.clientX+window.scrollX-rect.left;
    mouseY = e.clientY+window.scrollY-rect.top;
    var i = 0, paintersLen = painters.length;
    for(i; i < paintersLen; i++) {
                painters[i].dx = mouseX;
                painters[i].dy = mouseY;
    }
};
el.onmousemove = function(e) {
    if (!isDrawing) return;
    mouseX = e.clientX+window.scrollX-rect.left;
    mouseY = e.clientY+window.scrollY-rect.top;
};
el.onmouseup = function() {
    isDrawing = false;
};
</script>

Just copy all and paste it to your code. And don't forget to click the voting!

只需复制所有内容并将其粘贴到您的代码中即可。并且不要忘记点击投票!