You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在旋转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";
}

关键修改说明

  1. 跟踪实时旋转角度:通过requestAnimationFrame和CSS动画时长同步计算当前Canvas的旋转角度,确保逻辑和视觉旋转一致
  2. 模拟真实绘图逻辑:鼠标按下时记录其相对Canvas中心的距离(半径)和初始角度,旋转过程中根据实时角度计算Canvas坐标系中的对应点,连续绘制形成圆形轨迹
  3. 优化交互体验:添加lineCap: "round"让线条更平滑,调整CSS的pointer-events确保鼠标事件正常响应
  4. 保留原有功能:保留颜色选择、橡皮擦、保存和清空功能,不改变原有交互逻辑

内容的提问来源于stack exchange,提问作者Sachin Giri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:05:25