在旋转HTML Canvas上实现类现实画圆效果的技术求助
在旋转Canvas上实现真实画笔绘图效果(固定笔绘出圆形)
你当前的代码用CSS让Canvas旋转,但绘图逻辑直接映射鼠标屏幕坐标,和真实场景中“笔抵着旋转物画出圆形”的效果不符。真实场景里笔固定,旋转物转动,笔会在旋转物表面留下圆形轨迹,以下是修正后的实现方案:
完整代码
HTML
<body onload="init()"> <canvas class="rotating" id="can" width="400" height="400" style="position:absolute;top:10%;left:10%;border:2px solid;border-radius:50%;"></canvas> <div style="position:absolute;top:12%;left:43%;">选择颜色</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%;">橡皮擦</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%;display:none;"> <input type="button" value="保存" id="btn" size="30" onclick="save()" style="position:absolute;top:55%;left:10%;"> <input type="button" value="清空" id="clr" size="23" onclick="erase()" style="position:absolute;top:55%;left:15%;"> </body>
CSS
@-webkit-keyframes rotating /* Safari 和 Chrome */ { from { -webkit-transform: rotate(0deg); -o-transform: rotate(0deg); transform: rotate(0deg); } to { -webkit-transform: rotate(360deg); -o-transform: rotate(360deg); transform: rotate(360deg); } } @keyframes rotating { from { -ms-transform: rotate(0deg); -moz-transform: rotate(0deg); -webkit-transform: rotate(0deg); -o-transform: rotate(0deg); transform: rotate(0deg); } to { -ms-transform: rotate(360deg); -moz-transform: rotate(360deg); -webkit-transform: rotate(360deg); -o-transform: rotate(360deg); transform: rotate(360deg); } } .rotating { animation: rotating 1.5s linear infinite; pointer-events: none; } #can { pointer-events: auto; }
JavaScript
var canvas, ctx, isDrawing = false; var currentColor = "black", lineWidth = 2; var canvasCenterX, canvasCenterY; var rotationAngle = 0; var animationDuration = 1500; // 和CSS动画时长一致(毫秒) var lastDrawAngle = 0; var drawRadius = 0; var startAngleOffset = 0; function init() { canvas = document.getElementById('can'); ctx = canvas.getContext("2d"); canvasCenterX = canvas.width / 2; canvasCenterY = canvas.height / 2; canvas.addEventListener("mousedown", startDrawing); canvas.addEventListener("mouseup", stopDrawing); canvas.addEventListener("mouseout", stopDrawing); requestAnimationFrame(updateRotation); } function updateRotation(timestamp) { rotationAngle = (timestamp % animationDuration) / animationDuration * Math.PI * 2; if (isDrawing) { drawRotatingLine(); } requestAnimationFrame(updateRotation); } function startDrawing(e) { isDrawing = true; const mouseX = e.clientX - canvas.offsetLeft; const mouseY = e.clientY - canvas.offsetTop; const dx = mouseX - canvasCenterX; const dy = mouseY - canvasCenterY; drawRadius = Math.sqrt(dx*dx + dy*dy); startAngleOffset = Math.atan2(dy, dx); lastDrawAngle = rotationAngle; } function stopDrawing() { isDrawing = false; } function drawRotatingLine() { const currentAngle = rotationAngle + startAngleOffset; const x = canvasCenterX + drawRadius * Math.cos(currentAngle); const y = canvasCenterY + drawRadius * Math.sin(currentAngle); const lastX = canvasCenterX + drawRadius * Math.cos(lastDrawAngle + startAngleOffset); const lastY = canvasCenterY + drawRadius * Math.sin(lastDrawAngle + startAngleOffset); ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(x, y); ctx.strokeStyle = currentColor; ctx.lineWidth = lineWidth; ctx.lineCap = "round"; ctx.stroke(); ctx.closePath(); lastDrawAngle = rotationAngle; } function color(obj) { switch (obj.id) { case "green": currentColor = "green"; break; case "blue": currentColor = "blue"; break; case "red": currentColor = "red"; break; case "yellow": currentColor = "yellow"; break; case "orange": currentColor = "orange"; break; case "black": currentColor = "black"; break; case "white": currentColor = "white"; break; } lineWidth = currentColor === "white" ? 14 : 2; } function erase() { if (confirm("确定要清空画布吗?")) { ctx.clearRect(0, 0, canvas.width, canvas.height); document.getElementById("canvasimg").style.display = "none"; } } function save() { document.getElementById("canvasimg").style.border = "2px solid"; const dataURL = canvas.toDataURL(); document.getElementById("canvasimg").src = dataURL; document.getElementById("canvasimg").style.display = "inline"; }
关键修改说明
- 跟踪实时旋转角度:通过
requestAnimationFrame和CSS动画时长同步计算当前Canvas的旋转角度,确保逻辑和视觉旋转一致 - 模拟真实绘图逻辑:鼠标按下时记录其相对Canvas中心的距离(半径)和初始角度,旋转过程中根据实时角度计算Canvas坐标系中的对应点,连续绘制形成圆形轨迹
- 优化交互体验:添加
lineCap: "round"让线条更平滑,调整CSS的pointer-events确保鼠标事件正常响应 - 保留原有功能:保留颜色选择、橡皮擦、保存和清空功能,不改变原有交互逻辑
内容的提问来源于stack exchange,提问作者Sachin Giri
相关产品推荐
相关产品推荐

