HTML Canvas鼠标移动时重复绘制形状问题求助
Canvas 形状拖拽绘制时重复堆叠问题
我的HTML Canvas在鼠标移动绘制矩形、圆形、三角形时,会在原有形状内部重复绘制相同形状,导致轮廓堆叠变粗,只需要显示单个形状轮廓。尝试过多种方案未解决,相关代码及问题截图如下:

原代码
var colorLine = useRef('black') var thick = useRef(1) var selectedTool = useRef('brush') var pressedMouse = useRef(false); useEffect(() => { var square = document.getElementById("drawPlace"); square.height = window.innerHeight - 55; square.width = window.innerWidth - 65; var paper = square.getContext("2d"); var x; var y; var snapshot; var key = {C: 67}; const drawRect = (e, board) => { board.strokeRect(e.offsetX, e.offsetY, x-e.offsetX, y-e.offsetY) } const drawCircle = (e) => { paper.beginPath(); let radius = Math.sqrt(Math.pow((x - e.offsetX), 2) + Math.pow((y - e.offsetY), 2)); paper.arc(x, y, radius, 0, 2 * Math.PI); paper.stroke(); } const drawTriangle = (e) => { paper.beginPath(); paper.moveTo(x, y); paper.lineTo(e.offsetX, e.offsetY); paper.lineTo(x * 2 - e.offsetX, e.offsetY); paper.closePath(); paper.stroke(); } document.addEventListener("mousedown", startDrawing); document.addEventListener("mousemove", drawLine); document.addEventListener("mouseup", stopDrawing); document.addEventListener("keydown", clearCanvas); function startDrawing(eventvs01){ pressedMouse.current = true;; x = eventvs01.offsetX; y = eventvs01.offsetY; paper.beginPath(); //snapshot = paper.getImageData(0, 0, square.width, square.height); } function drawLine(eventvs02) { if (pressedMouse.current) { document.getElementById("drawPlace").style.cursor = `url(${dot})`; if(selectedTool.current === "brush") { var xM = eventvs02.offsetX; var yM = eventvs02.offsetY; drawing_line(colorLine.current, x, y, xM, yM, paper); x = xM; y = yM; }else if(selectedTool.current === "rectangle"){ drawRect(eventvs02, paper); }else if(selectedTool.current === "circle"){ drawCircle(eventvs02); }else { drawTriangle(eventvs02); } } } function stopDrawing(eventvs03) { pressedMouse.current = false; document.getElementById("drawPlace").style.cursor = "default"; } function clearCanvas(whenPressKey) { if (whenPressKey.keyCode === key.C) { paper.clearRect(0, 0, square.width, square.height); } } function drawing_line(color, x_start, y_start, x_end, y_end, board){ board.beginPath(); board.strokeStyle = color; board.lineWidth = thick.current; board.moveTo(x_start,y_start); board.lineTo(x_end,y_end); board.stroke(); board.closePath(); } let handleCol = e => { [...[...document.querySelectorAll('.writing-set')][0].children].map((item, index ) => { if(index !== 6){ item.addEventListener('click', (e) => { console.log(e.currentTarget.dataset.col) let color = e.currentTarget.dataset.col; colorLine.current=color; }) }else{ item.addEventListener('change', (e) => { thick.current = e.target.value; }) } }) } let handleShapes = e => { [...[...document.querySelectorAll('.writing-set')][2].children].map((item, index ) => { if(index !== 6){ item.addEventListener('click', (e) => { selectedTool.current = e.currentTarget.dataset.shape; }) } }) } handleShapes(); handleCol(); }, [colorLine])
解决方法
核心问题
鼠标移动时,每次mousemove事件都会在画布上新增一次形状绘制,之前的临时形状没有被清除,导致多层叠加。需要用画布快照实现"拖拽预览"效果:鼠标按下时保存当前画布状态,移动时先恢复快照再绘制临时形状,松开鼠标时再把最终形状绘制到画布上。
修改后的代码
var colorLine = useRef('black') var thick = useRef(1) var selectedTool = useRef('brush') var pressedMouse = useRef(false); useEffect(() => { var square = document.getElementById("drawPlace"); square.height = window.innerHeight - 55; square.width = window.innerWidth - 65; var paper = square.getContext("2d"); var x; var y; var snapshot; var key = {C: 67}; // 统一设置绘制样式 paper.strokeStyle = colorLine.current; paper.lineWidth = thick.current; const drawRect = (e, board) => { const width = x - e.offsetX; const height = y - e.offsetY; board.strokeRect(e.offsetX, e.offsetY, width, height); } const drawCircle = (e) => { paper.beginPath(); let radius = Math.sqrt(Math.pow((x - e.offsetX), 2) + Math.pow((y - e.offsetY), 2)); paper.arc(x, y, radius, 0, 2 * Math.PI); paper.stroke(); } const drawTriangle = (e) => { paper.beginPath(); paper.moveTo(x, y); paper.lineTo(e.offsetX, e.offsetY); paper.lineTo(x * 2 - e.offsetX, e.offsetY); paper.closePath(); paper.stroke(); } // 新增:最终绘制形状的方法,鼠标松开时调用 const drawFinalShape = (e) => { paper.strokeStyle = colorLine.current; paper.lineWidth = thick.current; switch(selectedTool.current) { case "rectangle": drawRect(e, paper); break; case "circle": drawCircle(e); break; case "triangle": drawTriangle(e); break; } } document.addEventListener("mousedown", startDrawing); document.addEventListener("mousemove", drawLine); document.addEventListener("mouseup", stopDrawing); document.addEventListener("keydown", clearCanvas); function startDrawing(eventvs01){ pressedMouse.current = true; x = eventvs01.offsetX; y = eventvs01.offsetY; // 启用快照:保存鼠标按下时的画布状态 snapshot = paper.getImageData(0, 0, square.width, square.height); // 更新绘制样式(防止颜色/粗细变化后不生效) paper.strokeStyle = colorLine.current; paper.lineWidth = thick.current; } function drawLine(eventvs02) { if (pressedMouse.current) { document.getElementById("drawPlace").style.cursor = `url(${dot})`; if(selectedTool.current === "brush") { var xM = eventvs02.offsetX; var yM = eventvs02.offsetY; drawing_line(colorLine.current, x, y, xM, yM, paper); x = xM; y = yM; } else { // 恢复快照,清除上一次的临时形状 paper.putImageData(snapshot, 0, 0); // 绘制当前临时形状 switch(selectedTool.current) { case "rectangle": drawRect(eventvs02, paper); break; case "circle": drawCircle(eventvs02); break; case "triangle": drawTriangle(eventvs02); break; } } } } function stopDrawing(eventvs03) { if (pressedMouse.current && selectedTool.current !== "brush") { // 恢复快照后绘制最终形状 paper.putImageData(snapshot, 0, 0); drawFinalShape(eventvs03); } pressedMouse.current = false; document.getElementById("drawPlace").style.cursor = "default"; } function clearCanvas(whenPressKey) { if (whenPressKey.keyCode === key.C) { paper.clearRect(0, 0, square.width, square.height); } } function drawing_line(color, x_start, y_start, x_end, y_end, board){ board.beginPath(); board.strokeStyle = color; board.lineWidth = thick.current; board.moveTo(x_start,y_start); board.lineTo(x_end,y_end); board.stroke(); board.closePath(); } let handleCol = e => { [...[...document.querySelectorAll('.writing-set')][0].children].map((item, index ) => { if(index !== 6){ item.addEventListener('click', (e) => { colorLine.current = e.currentTarget.dataset.col; }) }else{ item.addEventListener('change', (e) => { thick.current = e.target.value; }) } }) } let handleShapes = e => { [...[...document.querySelectorAll('.writing-set')][2].children].map((item, index ) => { if(index !== 6){ item.addEventListener('click', (e) => { selectedTool.current = e.currentTarget.dataset.shape; }) } }) } handleShapes(); handleCol(); }, [colorLine])
修改说明
- 启用快照机制:
startDrawing中恢复getImageData保存画布初始状态,drawLine中每次绘制前用putImageData恢复快照,清除之前的临时形状; - 分离临时绘制与最终绘制:鼠标松开时,先恢复快照再绘制最终形状,确保画布上只有一个最终形状;
- 统一绘制样式:在
startDrawing中更新画笔样式,避免颜色或粗细变化后预览形状样式不一致; - 优化逻辑分支:用
switch替代多if-else,让代码更清晰。
内容的提问来源于stack exchange,提问作者Chinedu Fabian
相关产品推荐
相关产品推荐

