gpt4 book ai didi

javascript - 带有手绘线条的 HTML5 Canvas

转载 作者:行者123 更新时间:2023-12-03 03:56:14 26 4
gpt4 key购买 nike

关闭。这个问题需要details or clarity .它目前不接受答案。












想改进这个问题?通过 editing this post 添加详细信息并澄清问题.

4年前关闭。




Improve this question




我是 HTML5 中 Canvas 的新学习者,目前正在尝试为 children 制作一个简单的画线项目。我将背景图像插入 Canvas 中,并尝试提出一种手绘线绘制方法,但没有结果。有人可以帮我修复下面的代码吗?之前谢谢大家。

<!DOCTYPE HTML>
<html>
<script type="text/javascript">
var canvas, ctx, flag = false,
prevX = 0,
currX = 0,
prevY = 0,
currY = 0,
dot_flag = false;

var x = "black",
y = 2;

function init() {
canvas = document.getElementById('can');
ctx = canvas.getContext("2d");
w = canvas.width;
h = canvas.height;

canvas.addEventListener("mousemove", function (e) {
findxy('move', e)
}, false);
canvas.addEventListener("mousedown", function (e) {
findxy('down', e)
}, false);
canvas.addEventListener("mouseup", function (e) {
findxy('up', e)
}, false);
canvas.addEventListener("mouseout", function (e) {
findxy('out', e)
}, false);
}

function color(obj) {
switch (obj.id) {
case "green":
x = "green";
break;
case "blue":
x = "blue";
break;
case "red":
x = "red";
break;
case "yellow":
x = "yellow";
break;
case "orange":
x = "orange";
break;
case "black":
x = "black";
break;
case "white":
x = "white";
break;
}
if (x == "white") y = 14;
else y = 2;

}

function draw() {
ctx.beginPath();
ctx.moveTo(prevX, prevY);
ctx.lineTo(currX, currY);
ctx.strokeStyle = x;
ctx.lineWidth = y;
ctx.stroke();
ctx.closePath();
}

function erase() {
var m = confirm("Want to clear");
if (m) {
ctx.clearRect(0, 0, w, h);
document.getElementById("canvasimg").style.display = "none";
}
}

function save() {
document.getElementById("canvasimg").style.border = "2px solid";
var dataURL = canvas.toDataURL();
document.getElementById("canvasimg").src = dataURL;
document.getElementById("canvasimg").style.display = "inline";
}

function findxy(res, e) {
if (res == 'down') {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;

flag = true;
dot_flag = true;
if (dot_flag) {
ctx.beginPath();
ctx.fillStyle = x;
ctx.fillRect(currX, currY, 2, 2);
ctx.closePath();
dot_flag = false;
}
}
if (res == 'up' || res == "out") {
flag = false;
}
if (res == 'move') {
if (flag) {
prevX = currX;
prevY = currY;
currX = e.clientX - canvas.offsetLeft;
currY = e.clientY - canvas.offsetTop;
draw();
}
}
}
</script>
<body onload="init()" style="background-image: src=c:/WebProgram/Pictures/test1.png;">
<canvas id="can" width="520" height="700" style="position:absolute;top:10%;left:10%;border:2px solid;"></canvas>
<div style="position:absolute;top:12%;left:43%;">Choose Color</div>
<div style="position:absolute;top:15%;left:45%;width:10px;height:10px;background:green;" id="green" onclick="color(this)"></div>
<div style="position:absolute;top:15%;left:46%;width:10px;height:10px;background:blue;" id="blue" onclick="color(this)"></div>
<div style="position:absolute;top:15%;left:47%;width:10px;height:10px;background:red;" id="red" onclick="color(this)"></div>
<div style="position:absolute;top:17%;left:45%;width:10px;height:10px;background:yellow;" id="yellow" onclick="color(this)"></div>
<div style="position:absolute;top:17%;left:46%;width:10px;height:10px;background:orange;" id="orange" onclick="color(this)"></div>
<div style="position:absolute;top:17%;left:47%;width:10px;height:10px;background:black;" id="black" onclick="color(this)"></div>
<div style="position:absolute;top:20%;left:43%;">Eraser</div>
<div style="position:absolute;top:22%;left:45%;width:15px;height:15px;background:white;border:2px solid;" id="white" onclick="color(this)"></div>
<img id="canvasimg" style="position:absolute;top:10%;left:52%;" style="display:none;">
<input type="button" value="save" id="btn" size="30" onclick="save()" style="position:absolute;top:5%;left:10%;">
<input type="button" value="clear" id="clr" size="23" onclick="erase()" style="position:absolute;top:5%;left:15%;">
</body>
</html>

最佳答案

创建基于 Canvas 的绘图应用程序。

绘图应用程序没有那么多。听鼠标,当按钮按下时在鼠标位置绘制。

如果您想要一个响应式 Canvas 并且还包括撤消等,那么您需要从稍微复杂一点的级别开始。

绘图和展示。

首先,您应该将绘图与显示分开。这是通过创建一个包含绘图的屏幕外 Canvas 来完成的。它的大小是恒定的,并且可以由用户平移和缩放(甚至旋转)。

如果您正在创建线条或框,则使用屏幕外 Canvas 来保存绘图还可以让您在绘图上进行绘制。

一些有助于创建 Canvas 的功能

function createCanvas(width, height) {
const c = document.createElement("canvas");
c.width = width;
c.height = height;
c.ctx = c.getContext("2d");
return c;
}
const drawing = createCanvas(512,512);

您可以将该 Canvas 绘制到显示 Canvas 上
ctx.drawImage(drawing,0,0);

该片段在 Canvas 的中心绘制了带有阴影和边框的图形,看起来不错。

鼠标处理程序

正确使用鼠标界面很重要。从 Canvas 监听鼠标事件有一些问题。当用户拖出 Canvas 时,您不再获得任何鼠标事件。这意味着当鼠标离开 Canvas 时,您需要停止绘图,因为您不知道鼠标返回时是否仍然向下。

要解决这个问题,您需要监听文档的鼠标事件。这将在按下按钮时捕获鼠标,允许用户在您仍然获得事件的同时将鼠标移动到屏幕上的任何位置。如果鼠标离开 Canvas 时上升,您仍然会收到该事件。

NOTE the stack overflow snippet window prevents mouse capture ATM (a recent change) so the above mouse behaviour is restricted to the iFrame containing the snippet.



您也不应该从鼠标事件中进行任何渲染。一些渲染操作可能很慢,比鼠标的更新速度慢得多。如果您从鼠标事件渲染,您将丢失鼠标事件。始终在鼠标事件监听器中执行尽可能少的代码。

在片段中,鼠标事件仅记录当前鼠标状态,如果鼠标按下并绘制,它将记录鼠标创建的路径。通过函数调用 requestAnimationFrame 同步到显示刷新率的单独循环负责渲染内容。它以大约 60fps 的速度运行。为了在没有发生任何事情时停止绘制,使用一个标志来指示显示需要更新 updateDisplay当有更改时,您将其设置为 true updateDisplay=true;并且下次显示硬件准备好显示帧时,它将绘制所有更新的内容。

画一条线

一条线只是一组连接点。在片段中,我创建了一个线对象。它包含构成线条的点以及线条的宽度和颜色。

当鼠标按下时,我创建一个新的线对象并开始向它添加点。我标记显示需要更新,并在显示循环中通过其绘制方法在显示 Canvas 上绘制线条。

当鼠标向上移动时,我在绘图 Canvas 上画线。这样做可以让您对线条应用一些智能(片段很简单,对线条没有任何作用),例如使其沿其长度淡出。只有当鼠标抬起时,您才会知道它的长度。

在片段中,当鼠标启动时,我会丢弃该行,但如果您想要撤消操作,您可以将绘制的每一行保存在一个数组中。要撤消您只需清除绘图并重新绘制除撤消线之外的所有线。

线对象和相关代码。
// a point object creates point from x,y coords or object that has x,y
const point = (x, y = x.y + ((x = x.x) * 0)) => ({ x, y });

// function to add a point to the line
function addPoint(x, y) { this.points.push(point(x, y)); }

// draw a line on context ctx and adds offset.x, offset.y
function drawLine(ctx, offset) {
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
var i = 0;
while (i < this.points.length) {
const p = this.points[i++];
ctx.lineTo(p.x + offset.x, p.y + offset.y);
}
ctx.stroke();
}
// creates a new line object
function createLine(color, width) {
return {
points: [], // the points making up the line
color, // colour of the line
width, // width of the line
add: addPoint, // function to add a point
draw: drawLine, // function to draw the whole line
};
}

示例片段

这段代码比我想要的要长一点。我已经为相关代码添加了评论,但如果您有任何问题,请在评论中提问,我会用更多信息更新答案。

// size of drawing and its starting background colour
const drawingInfo = {
width: 384 ,
height: 160,
bgColor: "white",
}
const brushSizes = [1, 2, 3, 4, 5, 6, 7, 8];
const colors = "red,orange,yellow,green,cyan,blue,purple,white,gray,black".split(",");
var currentColor = "blue";
var currentWidth = 2;
var currentSelectBrush;
var currentSelectColor;
const colorSel = document.getElementById("colorSel");
colors.forEach((color, i) => {
var swatch = document.createElement("span");
swatch.className = "swatch";
swatch.style.backgroundColor = color;
if (currentColor === color) {
swatch.className = "swatch highlight";
currentSelectColor = swatch;
} else {
swatch.className = "swatch";
}
swatch.addEventListener("click", (e) => {
currentSelectColor.className = "swatch";
currentColor = e.target.style.backgroundColor;
currentSelectColor = e.target;
currentSelectColor.className = "swatch highlight";
});
colorSel.appendChild(swatch);
})
brushSizes.forEach((brushSize, i) => {
var brush = document.createElement("canvas");
brush.width = 16;
brush.height = 16;
brush.ctx = brush.getContext("2d");
brush.ctx.beginPath();
brush.ctx.arc(8, 8, brushSize / 2, 0, Math.PI * 2);
brush.ctx.fill();
brush.brushSize = brushSize;
if (currentWidth === brushSize) {
brush.className = "swatch highlight";
currentSelectBrush = brush;
} else {
brush.className = "swatch";
}

brush.addEventListener("click", (e) => {
currentSelectBrush.className = "swatch";
currentSelectBrush = e.target;
currentSelectBrush.className = "swatch highlight";
currentWidth = e.target.brushSize;

});
colorSel.appendChild(brush);
})


const canvas = document.getElementById("can");
const mouse = createMouse().start(canvas, true);
const ctx = canvas.getContext("2d");
var updateDisplay = true; // when true the display needs updating
var ch, cw, w, h; // global canvas size vars


var currentLine;

var displayOffset = {
x: 0,
y: 0
};

// a point object creates point from x,y coords or object that has x,y
const point = (x, y = x.y + ((x = x.x) * 0)) => ({
x,
y
});
// function to add a point to the line
function addPoint(x, y) {
this.points.push(point(x, y));
}

function drawLine(ctx, offset) { // draws a line
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
var i = 0;
while (i < this.points.length) {
const p = this.points[i++];
ctx.lineTo(p.x + offset.x, p.y + offset.y);
}
ctx.stroke();
}

function createLine(color, width) {
return {
points: [],
color,
width,
add: addPoint,
draw: drawLine,
};
}


// creates a canvas
function createCanvas(width, height) {
const c = document.createElement("canvas");
c.width = width;
c.height = height;
c.ctx = c.getContext("2d");
return c;
}
// resize main display canvas and set global size vars
function resizeCanvas() {
ch = ((h = canvas.height = innerHeight - 32) / 2) | 0;
cw = ((w = canvas.width = innerWidth) / 2) | 0;
updateDisplay = true;
}

function createMouse() {
function preventDefault(e) { e.preventDefault() }
const mouse = {
x: 0,
y: 0,
buttonRaw: 0,
prevButton: 0
};
const bm = [1, 2, 4, 6, 5, 3]; // bit masks for mouse buttons
const mouseEvents = "mousemove,mousedown,mouseup".split(",");
const m = mouse;
// one mouse handler
function mouseMove(e) {
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;

if (e.type === "mousedown") {
m.buttonRaw |= bm[e.which - 1];
} else if (e.type === "mouseup") {
m.buttonRaw &= bm[e.which + 2];
}
// check if there should be a display update
if (m.buttonRaw || m.buttonRaw !== m.prevButton) {
updateDisplay = true;
}
// if the mouse is down and the prev mouse is up then start a new line
if (m.buttonRaw !== 0 && m.prevButton === 0) { // starting new line
currentLine = createLine(currentColor, currentWidth);
currentLine.add(m); // add current mouse position
} else if (m.buttonRaw !== 0 && m.prevButton !== 0) { // while mouse is down
currentLine.add(m); // add current mouse position
}
m.prevButton = m.buttonRaw; // remember the previous mouse state
e.preventDefault();
}
// starts the mouse
m.start = function(element, blockContextMenu) {
m.element = element;

mouseEvents.forEach(n => document.addEventListener(n, mouseMove));
if (blockContextMenu === true) {
document.addEventListener("contextmenu", preventDefault)
}
return m
}
return m;
}
var cursor = "crosshair";
function update(timer) { // Main update loop
cursor = "crosshair";
globalTime = timer;
// if the window size has changed resize the canvas
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas()
}
if (updateDisplay) {
updateDisplay = false;
display(); // call demo code
}

ctx.canvas.style.cursor = cursor;
requestAnimationFrame(update);
}
// create a drawing canvas.
const drawing = createCanvas(drawingInfo.width, drawingInfo.height);
// fill with white
drawing.ctx.fillStyle = drawingInfo.bgColor;
drawing.ctx.fillRect(0, 0, drawing.width, drawing.height);

// function to display drawing
function display() {
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "rgba(0,0,0,0.25)";
const imgX = cw - (drawing.width / 2) | 0;
const imgY = ch - (drawing.height / 2) | 0;
// add a shadow to make it look nice
ctx.fillRect(imgX + 5, imgY + 5, drawing.width, drawing.height);

// add outline
ctx.strokeStyle = "black";
ctx.lineWidth = "2";
ctx.strokeRect(imgX, imgY, drawing.width, drawing.height);
// draw the image
ctx.drawImage(drawing, imgX, imgY);
if (mouse.buttonRaw !== 0) {
if (currentLine !== undefined) {
currentLine.draw(ctx, displayOffset); // draw line on display canvas
cursor = "none";
updateDisplay = true; // keep updating
}
} else if (mouse.buttonRaw === 0) {
if (currentLine !== undefined) {
currentLine.draw(drawing.ctx, {x: -imgX, y: -imgY }); // draw line on drawing
currentLine = undefined;
updateDisplay = true;
// next line is a quick fix to stop a slight flicker due to the current frame not showing the line
ctx.drawImage(drawing, imgX, imgY);

}
}
}

requestAnimationFrame(update);
#can {
position: absolute;
top: 32px;
left: 0px;
background-color: #AAA;
}

.colors {
border: 1px solid black;
display: inline-flex;
}

.swatch {
min-width: 16px;
min-height: 16px;
max-width: 16px;
border: 1px solid black;
display: inline-block;
margin: 2px;
cursor: pointer;

}

.highlight {
border: 1px solid red;
}
<canvas id="can"></canvas>
<div class="colors" id="colorSel"></div>


更新 为了回应 OP 的评论,我添加了一个 HTML 版本,您应该可以将它(包括 <!DOCTYPE HTML></HTML> 在内的所有内容)复制并粘贴到 html 文档(例如,drawing.html)中,然后在支持 ES6 的浏览器。例如 Chrome、Firefox、Edge。

复制以下片段的内容。

<!DOCTYPE HTML>
<html>
<head>
<style>
#can {
position: absolute;
top: 32px;
left: 0px;
background-color: #AAA;
}

.colors {
border: 1px solid black;
display: inline-flex;
}

.swatch {
min-width: 16px;
min-height: 16px;
max-width: 16px;
border: 1px solid black;
display: inline-block;
margin: 2px;
cursor: pointer;

}

.highlight {
border: 1px solid red;
}
</style>
</head>

<body>
<canvas id="can"></canvas>
<div class="colors" id="colorSel"></div>

<script>
// size of drawing and its starting background colour
const drawingInfo = {
width: 384 ,
height: 160,
bgColor: "white",
}
const brushSizes = [1, 2, 3, 4, 5, 6, 7, 8];
const colors = "red,orange,yellow,green,cyan,blue,purple,white,gray,black".split(",");
var currentColor = "blue";
var currentWidth = 2;
var currentSelectBrush;
var currentSelectColor;
const colorSel = document.getElementById("colorSel");
colors.forEach((color, i) => {
var swatch = document.createElement("span");
swatch.className = "swatch";
swatch.style.backgroundColor = color;
if (currentColor === color) {
swatch.className = "swatch highlight";
currentSelectColor = swatch;
} else {
swatch.className = "swatch";
}
swatch.addEventListener("click", (e) => {
currentSelectColor.className = "swatch";
currentColor = e.target.style.backgroundColor;
currentSelectColor = e.target;
currentSelectColor.className = "swatch highlight";
});
colorSel.appendChild(swatch);
})
brushSizes.forEach((brushSize, i) => {
var brush = document.createElement("canvas");
brush.width = 16;
brush.height = 16;
brush.ctx = brush.getContext("2d");
brush.ctx.beginPath();
brush.ctx.arc(8, 8, brushSize / 2, 0, Math.PI * 2);
brush.ctx.fill();
brush.brushSize = brushSize;
if (currentWidth === brushSize) {
brush.className = "swatch highlight";
currentSelectBrush = brush;
} else {
brush.className = "swatch";
}

brush.addEventListener("click", (e) => {
currentSelectBrush.className = "swatch";
currentSelectBrush = e.target;
currentSelectBrush.className = "swatch highlight";
currentWidth = e.target.brushSize;

});
colorSel.appendChild(brush);
})


const canvas = document.getElementById("can");
const mouse = createMouse().start(canvas, true);
const ctx = canvas.getContext("2d");
var updateDisplay = true; // when true the display needs updating
var ch, cw, w, h; // global canvas size vars


var currentLine;

var displayOffset = {
x: 0,
y: 0
};

// a point object creates point from x,y coords or object that has x,y
const point = (x, y = x.y + ((x = x.x) * 0)) => ({
x,
y
});
// function to add a point to the line
function addPoint(x, y) {
this.points.push(point(x, y));
}

function drawLine(ctx, offset) { // draws a line
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
var i = 0;
while (i < this.points.length) {
const p = this.points[i++];
ctx.lineTo(p.x + offset.x, p.y + offset.y);
}
ctx.stroke();
}

function createLine(color, width) {
return {
points: [],
color,
width,
add: addPoint,
draw: drawLine,
};
}


// creates a canvas
function createCanvas(width, height) {
const c = document.createElement("canvas");
c.width = width;
c.height = height;
c.ctx = c.getContext("2d");
return c;
}
// resize main display canvas and set global size vars
function resizeCanvas() {
ch = ((h = canvas.height = innerHeight - 32) / 2) | 0;
cw = ((w = canvas.width = innerWidth) / 2) | 0;
updateDisplay = true;
}

function createMouse() {
function preventDefault(e) { e.preventDefault() }
const mouse = {
x: 0,
y: 0,
buttonRaw: 0,
prevButton: 0
};
const bm = [1, 2, 4, 6, 5, 3]; // bit masks for mouse buttons
const mouseEvents = "mousemove,mousedown,mouseup".split(",");
const m = mouse;
// one mouse handler
function mouseMove(e) {
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;

if (e.type === "mousedown") {
m.buttonRaw |= bm[e.which - 1];
} else if (e.type === "mouseup") {
m.buttonRaw &= bm[e.which + 2];
}
// check if there should be a display update
if (m.buttonRaw || m.buttonRaw !== m.prevButton) {
updateDisplay = true;
}
// if the mouse is down and the prev mouse is up then start a new line
if (m.buttonRaw !== 0 && m.prevButton === 0) { // starting new line
currentLine = createLine(currentColor, currentWidth);
currentLine.add(m); // add current mouse position
} else if (m.buttonRaw !== 0 && m.prevButton !== 0) { // while mouse is down
currentLine.add(m); // add current mouse position
}
m.prevButton = m.buttonRaw; // remember the previous mouse state
e.preventDefault();
}
// starts the mouse
m.start = function(element, blockContextMenu) {
m.element = element;

mouseEvents.forEach(n => document.addEventListener(n, mouseMove));
if (blockContextMenu === true) {
document.addEventListener("contextmenu", preventDefault)
}
return m
}
return m;
}
var cursor = "crosshair";
function update(timer) { // Main update loop
cursor = "crosshair";
globalTime = timer;
// if the window size has changed resize the canvas
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas()
}
if (updateDisplay) {
updateDisplay = false;
display(); // call demo code
}

ctx.canvas.style.cursor = cursor;
requestAnimationFrame(update);
}
// create a drawing canvas.
const drawing = createCanvas(drawingInfo.width, drawingInfo.height);
// fill with white
drawing.ctx.fillStyle = drawingInfo.bgColor;
drawing.ctx.fillRect(0, 0, drawing.width, drawing.height);

// function to display drawing
function display() {
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "rgba(0,0,0,0.25)";
const imgX = cw - (drawing.width / 2) | 0;
const imgY = ch - (drawing.height / 2) | 0;
// add a shadow to make it look nice
ctx.fillRect(imgX + 5, imgY + 5, drawing.width, drawing.height);

// add outline
ctx.strokeStyle = "black";
ctx.lineWidth = "2";
ctx.strokeRect(imgX, imgY, drawing.width, drawing.height);
// draw the image
ctx.drawImage(drawing, imgX, imgY);
if (mouse.buttonRaw !== 0) {
if (currentLine !== undefined) {
currentLine.draw(ctx, displayOffset); // draw line on display canvas
cursor = "none";
updateDisplay = true; // keep updating
}
} else if (mouse.buttonRaw === 0) {
if (currentLine !== undefined) {
currentLine.draw(drawing.ctx, {x: -imgX, y: -imgY }); // draw line on drawing
currentLine = undefined;
updateDisplay = true;
// next line is a quick fix to stop a slight flicker due to the current frame not showing the line
ctx.drawImage(drawing, imgX, imgY);

}
}
}

requestAnimationFrame(update);

/* load and add image to the drawing. It may take time to load. */
function loadImage(url){
const image = new Image();
image.src = url;
image.onload = function(){
if(drawing && drawing.ctx){
drawing.width = image.width;
drawing.height = image.height;
drawing.ctx.drawImage(image,0,0);
};
}

}
loadImage("/image/C7qq2.png?s=328&g=1");


</script>

</body>
</html>

关于javascript - 带有手绘线条的 HTML5 Canvas ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44934221/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com