Canvas绘图重放异常:如何实现带线条平滑的慢速重放?
解决Canvas绘图重放的平滑慢速问题
核心修改点
- 拆分绘制逻辑:将录制和重放的绘图操作分离,避免重放时重复往
coordinates数组添加数据 - 控制重放速率:用
setTimeout替代连续的requestAnimationFrame,通过调整时间间隔控制重放速度 - 重置重放状态:每次重放前清空画布、重置绘图状态变量,保证重放从初始状态开始
- 修复曲线绘制逻辑:重放时正确初始化贝塞尔曲线的控制点状态,确保线条平滑生成
修改后的完整代码
HTML代码
<!DOCTYPE HTML> <html> <head> <title>Canvas Draw</title> </head> <body> <div id="coords"> <div id="msg">Start drawing!</div> X: <span id="x"></span> Y: <span id="y"></span> </div> <button id="coordinatesBtn">Show coordinates of drawing</button> <button id="coordinatesDrawBtn">Use coordinates to draw</button> <div id="container"> <canvas id="surface"></canvas> </div> </body> </html>
CSS代码
* { -webkit-user-select:none; } body { margin:0; padding:0; width:100%; height:100%; overflow:hidden; } #msg { color:red; font-weight:bold; } #coords{ background:#ffc; border-bottom:1px solid #fc0; } #container { position:relative; } #surface { background:white; } .tp{ visibility:hidden; position:absolute; width:30px; height:30px; background:#999; -webkit-border-radius:15px; -webkit-box-shadow:inset white 0 0 15px 5px, white 0 0 2px 2px; }
JavaScript代码
const coordinates = []; let replayInterval = 30; // 重放间隔(单位:ms),值越大速度越慢 document.getElementById('coordinatesBtn').addEventListener('click', () => { console.log(coordinates) }); document.getElementById('coordinatesDrawBtn').addEventListener('click', () => { play() }); let step = 0; let replayTimer = null; function play() { // 终止正在进行的重放 if (replayTimer) { clearTimeout(replayTimer); replayTimer = null; } // 重置重放状态 step = 0; inProgress = false; cp1x = cp1y = cp2x = cp2y = null; skip1 = true; skip2 = false; // 清空画布 ctx.clearRect(0, 0, surface.width, surface.height); function steps() { if (step >= coordinates.length) { // 重放结束,重置状态 inProgress = false; ctx.save(); return; } // 调用重放专用绘制函数 replayDraw(coordinates[step].x, coordinates[step].y); step++; replayTimer = setTimeout(steps, replayInterval); } steps(); } var surface, ctx, target, inProgress, cp1x, cp1y, cp2x, cp2y, skip1, skip2; function $(e) { return document.getElementById(e); } // 录制时的绘图函数 function draw(e) { var x = e.offsetX; var y = e.offsetY; target.x.innerHTML = x; target.y.innerHTML = y; coordinates.push({x, y}); drawPath(x, y); } // 重放时的绘图函数 function replayDraw(x, y) { target.x.innerHTML = x; target.y.innerHTML = y; drawPath(x, y); } // 通用路径绘制逻辑 function drawPath(x, y) { ctx.shadowColor = "rgba(0,0,0,.5)"; ctx.shadowBlur = 2; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.lineWidth = 2; ctx.strokeStyle = 'red'; if (!inProgress) { ctx.beginPath(); ctx.moveTo(x, y); inProgress = true; skip1 = true; skip2 = false; } else { if (skip1) { cp1x = x; cp1y = y; skip1 = false; skip2 = true; } else if (skip2) { cp2x = x; cp2y = y; skip1 = false; skip2 = false; } else { ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); skip1 = true; skip2 = false; } } ctx.stroke(); } function captureTouch() { surface.addEventListener('selectstart', function(e) { e.preventDefault(); return false; }, false); surface.addEventListener('dragstart', function(e) { e.preventDefault(); return false; }, false); surface.addEventListener('mousedown', function() { $('msg').style.setProperty('display', 'none'); surface.addEventListener('mousemove', draw, false); }, false); surface.addEventListener('mouseup', function() { surface.removeEventListener('mousemove', draw, false); inProgress = false; ctx.save(); }, false); } function init() { surface = $('surface'); surface.width = document.body.clientWidth; surface.height = window.innerHeight; ctx = surface.getContext('2d'); target = { x: $('x'), y: $('y') }; captureTouch(); document.body.addEventListener('touchmove', function(e) { e.preventDefault(); }, false); } document.addEventListener('DOMContentLoaded', init, false);
说明
- 修改
replayInterval的值可以调整重放速度,比如设为50ms会比30ms更慢 - 拆分后的
drawPath函数复用了绘制逻辑,减少代码冗余 - 重放前的状态重置确保每次重放都是独立的全新过程
内容的提问来源于stack exchange,提问作者user21188932
相关产品推荐
相关产品推荐

