繁体   English   中英

如何使用 JavaScript 在画布上手绘?

[英]How do I hand draw on canvas with JavaScript?

我如何在画布元素上自由绘制(使用我的鼠标/手指),就像你可以用铅笔在油漆上画一样?

关于这个问题

想在canvas上实现自由手绘的问题有很多:

所以我认为制作一个参考问题是个好主意,其中每个答案都是社区维基,并包含对一个 JavaScript 库/纯 JavaScript 如何在画布上绘画的解释。

答案的结构

答案应该是社区维基并使用以下模板:

## [Name of library](Link to project page)
### Simple example
    A basic, complete example. That means it has to contain HTML 
    and JavaScript. You can start with this:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Simple example</title>
        <script type='text/javascript' src='http://cdnjs.com/[your library]'></script>
        <style type='text/css'>
            #sheet {
                border:1px solid black;
            }
        </style>
        <script type='text/javascript'>
            window.onload=function(){
                // TODO: Adjust
            }
        </script>
      </head>
      <body>
        <canvas id="sheet" width="400" height="400"></canvas>
      </body>
    </html>

    If possible, this example should work with both, mouse and touch events.

[JSFiddle](Link to code on jsfiddle.net)

This solution works with:

<!-- Please test it the following way: Write "Hello World"
  Problems that you test this way are:
   * Does it work at all?
   * Are lines separated?
   * Does it get slow when you write too much?
-->

* Desktop computers:
  * [Browser + Version list]
* Touch devices:
  * [Browser + Version list] on [Device name]

### Import / Export
Some explanations how to import / export user drawn images.

### Line smoothing
Explanations about how to manipulate the line the user draws. 
This can include:
  * Bézier curves
  * Controlling thickness of lines

Fabric.js

<!DOCTYPE html>
<html>
  <head>
    <title>Simple example</title>
    <script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js'></script>
    <style type='text/css'>
        #sheet {
            border:1px solid black;
        }
    </style>
    <script type='text/javascript'>
        window.onload=function(){
            var canvas = new fabric.Canvas('sheet');
            canvas.isDrawingMode = true;
            canvas.freeDrawingBrush.width = 5;
            canvas.freeDrawingBrush.color = "#ff0000";
        }
    </script>
  </head>
  <body>
    <canvas id="sheet" width="400" height="400"></canvas>
  </body>
</html>

JSFiddle - 演示

  • 可以使用canvas.freeDrawingBrush.width控制线条的宽度。
  • 可以使用canvas.freeDrawingBrush.color控制线条的颜色。

此解决方案适用于:

  • 台式电脑:
    • Chrome 33
    • Firefox 28
  • 触控设备:
    • Nexus 4上的Chrome 34
    • Nexus 4上的Opera 20
    • Nexus 4上的Firefox 28

进出口

只能通过序列化完整的画布来实现,请参阅教程

线条平滑

是自动完成的,似乎无法停用它。

简单的JavaScript

简单的例子

<!DOCTYPE html>
<html>
  <head>
    <title>Simple example</title>
    <style type='text/css'>
        #sheet {
            border:1px solid black;
        }
    </style>
  </head>
  <body>
    <canvas id="sheet" width="400" height="400"></canvas>
    <script type='text/javascript'>
/*jslint browser:true */
"use strict";
var context = document.getElementById('sheet').getContext("2d");
var canvas = document.getElementById('sheet');
context = canvas.getContext("2d");
context.strokeStyle = "#ff0000";
context.lineJoin = "round";
context.lineWidth = 5;

var clickX = [];
var clickY = [];
var clickDrag = [];
var paint;

/**
 * Add information where the user clicked at.
 * @param {number} x
 * @param {number} y
 * @return {boolean} dragging
 */
function addClick(x, y, dragging) {
    clickX.push(x);
    clickY.push(y);
    clickDrag.push(dragging);
}

/**
 * Redraw the complete canvas.
 */
function redraw() {
    // Clears the canvas
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);

    for (var i = 0; i < clickX.length; i += 1) {
        if (!clickDrag[i] && i == 0) {
            context.beginPath();
            context.moveTo(clickX[i], clickY[i]);
            context.stroke();
        } else if (!clickDrag[i] && i > 0) {
            context.closePath();

            context.beginPath();
            context.moveTo(clickX[i], clickY[i]);
            context.stroke();
        } else {
            context.lineTo(clickX[i], clickY[i]);
            context.stroke();
        }
    }
}

/**
 * Draw the newly added point.
 * @return {void}
 */
function drawNew() {
    var i = clickX.length - 1
    if (!clickDrag[i]) {
        if (clickX.length == 0) {
            context.beginPath();
            context.moveTo(clickX[i], clickY[i]);
            context.stroke();
        } else {
            context.closePath();

            context.beginPath();
            context.moveTo(clickX[i], clickY[i]);
            context.stroke();
        }
    } else {
        context.lineTo(clickX[i], clickY[i]);
        context.stroke();
    }
}

function mouseDownEventHandler(e) {
    paint = true;
    var x = e.pageX - canvas.offsetLeft;
    var y = e.pageY - canvas.offsetTop;
    if (paint) {
        addClick(x, y, false);
        drawNew();
    }
}

function touchstartEventHandler(e) {
    paint = true;
    if (paint) {
        addClick(e.touches[0].pageX - canvas.offsetLeft, e.touches[0].pageY - canvas.offsetTop, false);
        drawNew();
    }
}

function mouseUpEventHandler(e) {
    context.closePath();
    paint = false;
}

function mouseMoveEventHandler(e) {
    var x = e.pageX - canvas.offsetLeft;
    var y = e.pageY - canvas.offsetTop;
    if (paint) {
        addClick(x, y, true);
        drawNew();
    }
}

function touchMoveEventHandler(e) {
    if (paint) {
        addClick(e.touches[0].pageX - canvas.offsetLeft, e.touches[0].pageY - canvas.offsetTop, true);
        drawNew();
    }
}

function setUpHandler(isMouseandNotTouch, detectEvent) {
    removeRaceHandlers();
    if (isMouseandNotTouch) {
        canvas.addEventListener('mouseup', mouseUpEventHandler);
        canvas.addEventListener('mousemove', mouseMoveEventHandler);
        canvas.addEventListener('mousedown', mouseDownEventHandler);
        mouseDownEventHandler(detectEvent);
    } else {
        canvas.addEventListener('touchstart', touchstartEventHandler);
        canvas.addEventListener('touchmove', touchMoveEventHandler);
        canvas.addEventListener('touchend', mouseUpEventHandler);
        touchstartEventHandler(detectEvent);
    }
}

function mouseWins(e) {
    setUpHandler(true, e);
}

function touchWins(e) {
    setUpHandler(false, e);
}

function removeRaceHandlers() {
    canvas.removeEventListener('mousedown', mouseWins);
    canvas.removeEventListener('touchstart', touchWins);
}

canvas.addEventListener('mousedown', mouseWins);
canvas.addEventListener('touchstart', touchWins);
    </script>
  </body>
</html>

的jsfiddle

  • 可以使用context.lineWidth控制线条的宽度。
  • 可以使用strokeStyle控制线条的颜色。

此解决方案适用于:

  • 台式电脑:
    • Chrome 33
    • Firefox 28
  • 触控设备:
    • Nexus 4上的Firefox 28

它不起作用

  • 触控设备:
    • Nexus 4上的Chrome 34 / Opera 20(见问题

进出口

导入和导出图像可以通过导入/导出clickXclickYclickDrag

线条平滑

最终可以通过用bezierCurveTo()替换lineTo()来完成

EaselJs

简单的例子

A basic, complete example. That means it has to contain HTML 
and JavaScript. You can start with this:

<!DOCTYPE html>
<html>
<head>
    <title>EaselJS example</title>

    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/EaselJS/0.7.1/easeljs.min.js"></script>

    <script>
        var canvas, stage;
        var drawingCanvas;
        var oldPt;
        var oldMidPt;
        var color;
        var stroke;
        var index;

        function init() {
            if (window.top != window) {
                document.getElementById("header").style.display = "none";
            }
            canvas = document.getElementById("sheet");
            index = 0;

            //check to see if we are running in a browser with touch support
            stage = new createjs.Stage(canvas);
            stage.autoClear = false;
            stage.enableDOMEvents(true);

            createjs.Touch.enable(stage);
            createjs.Ticker.setFPS(24);

            drawingCanvas = new createjs.Shape();

            stage.addEventListener("stagemousedown", handleMouseDown);
            stage.addEventListener("stagemouseup", handleMouseUp);

            stage.addChild(drawingCanvas);
            stage.update();
        }

        function stop() {}

        function handleMouseDown(event) {
            color = "#ff0000";
            stroke = 5;
            oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
            oldMidPt = oldPt;
            stage.addEventListener("stagemousemove" , handleMouseMove);
        }

        function handleMouseMove(event) {
            var midPt = new createjs.Point(oldPt.x + stage.mouseX>>1, oldPt.y+stage.mouseY>>1);

            drawingCanvas.graphics.clear().setStrokeStyle(stroke, 'round', 'round').beginStroke(color).moveTo(midPt.x, midPt.y).curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y);

            oldPt.x = stage.mouseX;
            oldPt.y = stage.mouseY;

            oldMidPt.x = midPt.x;
            oldMidPt.y = midPt.y;

            stage.update();
        }

        function handleMouseUp(event) {
            stage.removeEventListener("stagemousemove" , handleMouseMove);
        }

    </script>
</head>
<body onload="init();">
    <canvas id="sheet" width="400" height="400"></canvas>
</body>
</html>

演示

文档中有趣的部分是:

此解决方案适用于:

  • 台式电脑:
    • Chrome 33
    • Firefox 28
  • 触控设备:
    • Nexus 4上的Chrome 34 / Firefox 28 / Opera 20

进出口

线条平滑

普通 JS - ES6

简单的例子

上面的纯 Javascript 示例有一些严重的问题:它没有反映评论反对意见, paint状态是多余的,事件没有正确解钩,没有使用redraw()函数,它可以简化很多,但它不起作用使用现代语法。 修复方法在这里:

 var canvas = document.getElementById('sheet'), g = canvas.getContext("2d"); g.strokeStyle = "hsl(208, 100%, 43%)"; g.lineJoin = "round"; g.lineWidth = 1; g.filter = "blur(1px)"; const relPos = pt => [pt.pageX - canvas.offsetLeft, pt.pageY - canvas.offsetTop], drawStart = pt => { with(g) { beginPath(); moveTo.apply(g, pt); stroke(); }}, drawMove = pt => { with(g) { lineTo.apply(g, pt); stroke(); }}, pointerDown = e => drawStart(relPos(e.touches ? e.touches[0] : e)), pointerMove = e => drawMove(relPos(e.touches ? e.touches[0] : e)), draw = (method, move, stop) => e => { if(method=="add") pointerDown(e); canvas[method+"EventListener"](move, pointerMove); canvas[method+"EventListener"](stop, g.closePath); }; canvas.addEventListener("mousedown", draw("add","mousemove","mouseup")); canvas.addEventListener("touchstart", draw("add","touchmove","touchend")); canvas.addEventListener("mouseup", draw("remove","mousemove","mouseup")); canvas.addEventListener("touchend", draw("remove","touchmove","touchend"));
 <canvas id="sheet" width="400" height="400" style="border: 1px solid black"></canvas>

  • 支持 它今天应该在任何地方都有效。 我可以通过指针事件进一步简化,但 Safari 到 2021 年缺乏对它的支持。

进出口

对于导入,使用g.drawImage()

g.drawImage(img, 0, 0);

对于导出,请参见canvas.toBlob()

function save(blob) {
  var fd = new FormData();
  fd.append("myFile", blob);
  // handle formData to your desire here
}
canvas.toBlob(save,'image/jpeg');

线条平滑

对于抗锯齿,请参阅SVG 过滤器中的blur() 如果您导入,请不要忘记在导入图像后应用它

context.filter = "blur(1px)";

Paper.js

简单的例子

<!DOCTYPE html>
<html>
<head>
    <title>Paper.js example</title>
    <script type='text/javascript' src='http://paperjs.org/assets/js/paper.js'></script>
    <style type='text/css'>
        #sheet {
            border:1px solid black;
        }
    </style>
</head>
<body>
    <script type="text/paperscript" canvas="sheet">
        var path;

        function onMouseDown(event) {
            // If we produced a path before, deselect it:
            if (path) {
                path.selected = false;
            }

            // Create a new path and set its stroke color to black:
            path = new Path({
                segments: [event.point],
                strokeColor: 'black',
                strokeWidth: 3
            });
        }

        // While the user drags the mouse, points are added to the path
        // at the position of the mouse:
        function onMouseDrag(event) {
            path.add(event.point);
        }

        // When the mouse is released, we simplify the path:
        function onMouseUp(event) {
            path.simplify();
        }
    </script>

    <canvas id="sheet" width="400" height="400"></canvas>
</body>
</html>

的jsfiddle

  • 可以使用strokeWidth控制线条的宽度。
  • 可以使用strokeColor控制线条的颜色。

此解决方案适用于:

  • 台式电脑:
    • Chrome 33

进出口

线条平滑

线路平滑可以通过调整path.simplify();

在这里,尝试我的画布免费绘图和擦除。

https://jsfiddle.net/richardcwc/d2gxjdva/

 //Canvas var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); //Variables var canvasx = $(canvas).offset().left; var canvasy = $(canvas).offset().top; var last_mousex = last_mousey = 0; var mousex = mousey = 0; var mousedown = false; var tooltype = 'draw'; //Mousedown $(canvas).on('mousedown', function(e) { last_mousex = mousex = parseInt(e.clientX-canvasx); last_mousey = mousey = parseInt(e.clientY-canvasy); mousedown = true; }); //Mouseup $(canvas).on('mouseup', function(e) { mousedown = false; }); //Mousemove $(canvas).on('mousemove', function(e) { mousex = parseInt(e.clientX-canvasx); mousey = parseInt(e.clientY-canvasy); if(mousedown) { ctx.beginPath(); if(tooltype=='draw') { ctx.globalCompositeOperation = 'source-over'; ctx.strokeStyle = 'black'; ctx.lineWidth = 3; } else { ctx.globalCompositeOperation = 'destination-out'; ctx.lineWidth = 10; } ctx.moveTo(last_mousex,last_mousey); ctx.lineTo(mousex,mousey); ctx.lineJoin = ctx.lineCap = 'round'; ctx.stroke(); } last_mousex = mousex; last_mousey = mousey; //Output $('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown); }); //Use draw|erase use_tool = function(tool) { tooltype = tool; //update } 
 canvas { cursor: crosshair; border: 1px solid #000000; } 
 <canvas id="canvas" width="800" height="500"></canvas> <input type="button" value="draw" onclick="use_tool('draw');" /> <input type="button" value="erase" onclick="use_tool('erase');" /> <div id="output"></div> 

(免责声明:我写了这个图书馆)

Scrawl.js

简单的例子

<!DOCTYPE html>
<html>
    <head>
        <title>Simple example</title>
        <style type='text/css'>
            #sheet {border:1px solid black;}
        </style>
    </head>
    <body>
        <canvas id="sheet" width="400" height="400"></canvas>
        <script src="http://scrawl.rikweb.org.uk/js/scrawlCore-min.js"></script>
        <script>
            var mycode = function(){
                //define variables
                var myPad = scrawl.pad.sheet, 
                    myCanvas = scrawl.canvas.sheet,
                    sX, sY, here,
                    drawing = false, 
                    currentSprite = false,
                    startDrawing,
                    endDrawing;

                //event listeners
                startDrawing = function(e){
                    drawing = true;
                    currentSprite = scrawl.newShape({
                        start:          here,
                        lineCap:        'round',
                        lineJoin:       'round',
                        method:         'draw',
                        lineWidth:      4,
                        strokeStyle:    'red',
                        data:           'l0,0 ',
                    });
                    sX = here.x;
                    sY = here.y;
                    if(e){
                        e.stopPropagation();
                        e.preventDefault();
                    }
                };
                myCanvas.addEventListener('mousedown', startDrawing, false);

                endDrawing = function(e){
                    if(currentSprite){
                        currentSprite = false;
                    }
                    drawing = false;
                    if(e){
                        e.stopPropagation();
                        e.preventDefault();
                    }
                };
                myCanvas.addEventListener('mouseup', endDrawing, false);

                //animation object
                scrawl.newAnimation({
                    fn: function(){
                        //get current mouse position
                        here = myPad.getMouse();
                        if(here.active){
                            if(drawing){
                                if(here.x !== sX || here.y !== sY){
                                    //extend the line
                                    currentSprite.set({
                                        data: currentSprite.data+' '+(here.x - sX)+','+(here.y - sY),
                                        });
                                    sX = here.x;
                                    sY = here.y;
                                }
                            }
                        }
                        else{
                            //stop drawing if mouse leaves canvas area
                            if(currentSprite){
                                endDrawing();
                            }
                        }
                        //update display
                        scrawl.render();
                    },
                });
            };

            //Scrawl is modular - load additional modules
            scrawl.loadModules({
                path: 'js/',
                modules: ['animation', 'shape'],            
                callback: function(){
                    window.addEventListener('load', function(){
                        scrawl.init();      //start Scrawl
                        mycode();           //run code
                    }, false);
                },
            });
        </script>
    </body>
</html>

的jsfiddle

此解决方案适用于:

  • IE,Chrome,Firefox,Opera(桌面版)的最新版本
  • (未在移动/触控设备上测试)

添加触摸支持

  • (尝试添加像Hammer.js这样的专用触摸库?)

进出口

线条平滑和其他精灵操作

  • 行数据在内部保存为SVGTiny Path.d值 - 任何可以采用该格式的行数据并使其平滑的算法应该可行
  • 线属性 - 厚度,颜色,定位,旋转等 - 可以设置和动画。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM