我是HTML5中画布的新学习者,目前正在尝试为孩子们制作一个简单的绘画线项目。我将背景图像插入画布,并试图想出一个徒手画线方法,但最终没有结果。有人可以帮我修复下面的代码吗?谢谢大家以前。
<!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>
答案 0 :(得分:1)
绘图应用程序没有那么多。当按钮在鼠标位置向下绘制时,听鼠标。
如果你想拥有一个响应式画布并且还包含undos等等,那么你需要从更复杂的层面开始。
首先,您应该将图形与显示区分开。这是通过创建保存绘图的屏幕外画布来完成的。它的大小是恒定的,可以由用户平移和缩放(甚至旋转)。
如果要创建线条或方框,使用离屏画布来保存绘图还可以在绘图上绘制。
有助于创建画布的一些功能
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);
您可以使用
将该画布绘制到显示画布ctx.drawImage(drawing,0,0);
该片段使用阴影和边框在画布中心绘制绘图,看起来不错。
正确执行鼠标界面非常重要。从画布中聆听鼠标事件有一些问题。当用户拖离画布时,您不再获得任何鼠标事件。这意味着当鼠标离开画布时,您需要停止绘图,因为您不知道鼠标在返回时是否仍然处于停机状态。
要解决此问题,请聆听文档的鼠标事件。这将在按钮关闭时捕获鼠标,允许用户在您仍然获得事件时将鼠标移动到屏幕上的任何位置。如果鼠标在离开画布时上升,你仍然会得到那个事件。
注意堆栈溢出代码段窗口会阻止鼠标捕获ATM(最近的更改),因此上述鼠标行为仅限于包含该代码段的iFrame。
您也不应该从鼠标事件中进行任何渲染。某些渲染操作可能很慢,比鼠标的更新速度慢得多。如果从鼠标事件渲染,则会丢失鼠标事件。始终在鼠标事件侦听器中执行尽可能少的代码。
在代码段中,鼠标事件仅记录当前鼠标状态,如果鼠标已关闭并且绘制它将记录鼠标创建的路径。通过函数调用requestAnimationFrame
同步到显示刷新率的单独循环负责呈现内容。它以大约60fps的速度运行。为了在没有任何事情发生时停止绘图,会使用一个标志来指示显示需要更新updateDisplay
当有更改时,您将其设置为true updateDisplay=true;
,并在下次显示硬件准备好显示时一个框架,它将绘制所有更新的内容。
一条线只是一组连接点。在代码片段中,我创建了一个线对象。它包含构成线条的点以及线宽和颜色。
当鼠标停止时,我创建一个新的线对象并开始向其添加点。我标记显示需要更新,并在显示循环中通过其绘制方法在显示画布上绘制线条。
当鼠标向上移动时,我在绘图画布上绘制线条。这样做可以让你将一些智能应用到线上(片段很简单,对线条没有任何作用),例如使它沿着它的长度淡出。只有在鼠标启动时才会知道它的长度。
在代码片段中,当鼠标按下时我会丢弃该行,但如果您想要撤消,则会保存在数组中绘制的每一行。要撤消您,只需清除图形并重新绘制除撤消线之外的所有线条。
行对象和相关代码。
// 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);
&#13;
#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;
}
&#13;
<canvas id="can"></canvas>
<div class="colors" id="colorSel"></div>
&#13;
更新为了回应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("https://i.stack.imgur.com/C7qq2.png?s=328&g=1");
</script>
</body>
</html>
&#13;
答案 1 :(得分:0)
当你想到这个过程时,这个过程非常简单:
toDataURL()
功能。URL.createObjectURL(blob)
创建指向blob的链接。<a>
代码并将其href
设置为前面提到的链接。同时将其download
属性设置为您希望调用文件的任何内容(例如&#39; myImage.png&#39;)。这应该会为您留下<a>
标记,其内容类似于此<a href="blob:https://example.com/3d45a4f6-7302-485a-a255-d7f56ecf8074" download="myImage.png">Click to Save</a>
当然,这是我看到你完成这个应用程序的方式。
还有一件事,请阅读以下教程以了解更多信息:
美好的一天!希望这会有所帮助,抱歉这么懒,而且没有给出实际代码来支持这个答案。