如何在画布元素上自由绘制(使用我的鼠标/手指),就像你可以用铅笔画画一样?
有很多问题需要在画布上实现自由手绘:
所以我认为提出一个参考问题是一个好主意,其中每个答案都是社区维基,并且包含对一个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
答案 0 :(得分:8)
<!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>
context.lineWidth
控制线条的宽度。strokeStyle
控制线条的颜色。此解决方案适用于:
不适用于
导入和导出图像可以通过导入/导出clickX
,clickY
和clickDrag
来完成。
最终可以将lineTo()
替换为bezierCurveTo()
答案 1 :(得分:8)
<!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>
canvas.freeDrawingBrush.width
控制线条的宽度。canvas.freeDrawingBrush.color
控制线条的颜色。此解决方案适用于:
只有通过序列化完整画布才能实现,请参阅Tutorial
自动完成,似乎无法停用它。
答案 2 :(得分:2)
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>
文档中有趣的部分是:
此解决方案适用于:
答案 3 :(得分:1)
<!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>
strokeWidth
控制线条的宽度。strokeColor
控制线条的颜色。此解决方案适用于:
可以通过调整path.simplify();
来完成线条平滑。
答案 4 :(得分:1)
在这里,尝试我的画布免费绘图和擦除。
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
}
&#13;
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
&#13;
<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>
&#13;
答案 5 :(得分:1)
上面的纯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>
对于导入,使用 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 filters 中的 blur()
;如果您导入,请不要忘记在导入图像后应用它
context.filter = "blur(1px)";
答案 6 :(得分:0)
(免责声明:我写了这个图书馆)
<!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>