Как рисовать на холсте с помощью JavaScript?

Вопрос

Как рисовать бесплатно (используя мышь/пальцы) на элементе canvas, как вы можете сделать это в краске с помощью карандаша?

Об этом вопросе

Есть много вопросов, которые хотят добиться свободного рисования на холсте:

Итак, я подумал, что было бы неплохо сделать справочный вопрос, где каждый ответ будет вики-сообществом и содержит объяснение только одной библиотеки JavaScript/чистого JavaScript, как рисовать на холсте.

Структура ответов

Ответы должны быть wiki сообщества и использовать следующий шаблон:

## [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

Ответ 1

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
  • Сенсорные устройства:
    • Chrome 34 на Nexus 4
    • Opera 20 на Nexus 4
    • Firefox 28 на Nexus 4

Импорт/Экспорт

Возможно только путем сериализации полного холста, см. Учебник

Сглаживание линий

Выполняется автоматически, и, похоже, его невозможно отключить.

Ответ 2

Обычный 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
  • Сенсорные устройства:
    • Firefox 28 на Nexus 4

Он не работает с

  • Сенсорные устройства:
    • Chrome 34/Opera 20 на Nexus 4 (см. issue)

Импорт/Экспорт

Импорт и экспорт изображения может быть выполнен путем импорта/экспорта clickX, clickY и clickDrag.

Сглаживание линий

В конечном итоге можно будет сделать замену lineTo() с bezierCurveTo()

Ответ 3

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
  • Сенсорные устройства:
    • Chrome 34/Firefox 28/Opera 20 на Nexus 4

Импорт/Экспорт

?

Сглаживание линий

?

Ответ 4

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();.

Ответ 5

(Отказ от ответственности: я написал эту библиотеку)

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 - любой алгоритм, который может принимать данные линии в этом формате и сгладить его, должен работать
  • атрибуты линии - толщина, цвет, позиционирование, вращение и т.д. - могут быть установлены и анимированы.

Ответ 6

Здесь попробуйте мой бесплатный холст и стирание.

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>