HTML Canvas绘制动画线条模糊问题求助
HTML Canvas 连线动画线条模糊问题修复
我正在用HTML Canvas实现元素间的连线动画,但线条始终过度模糊。已尝试给描边坐标加0.5、使用ctx.scale(dpi, dpi)、不设置Canvas CSS尺寸等方法,但问题仍未解决。
核心问题分析与修复步骤
1. 动画帧叠加导致模糊
当前代码每次绘制新的动画进度时,没有清除之前的画布内容,导致同一线条被多次绘制叠加,视觉上变粗模糊。需要在每次绘制前清空画布。
2. 未适配高DPI屏幕
普通屏幕的设备像素比是1,但Retina屏幕是2或更高,Canvas的实际像素尺寸如果不匹配,会被拉伸导致模糊。需要根据设备像素比调整Canvas的宽高和上下文缩放。
3. 亚像素渲染导致模糊
绘制线条时如果坐标是小数,Canvas会进行抗锯齿处理,导致线条模糊。需要确保坐标落在像素边界上(或偏移0.5,根据线条宽度调整)。
4. 动画回调重复创建
当前animateLine每次调用requestAnimationFrame时都会创建新的函数实例,可能导致绘制逻辑重复,需要优化回调方式。
修改后的完整代码
JavaScript 部分
const canvas = document.getElementById('line-canvas'); const ctx = canvas.getContext('2d'); const list = document.querySelector('.ressource-search__parent-terms'); const items = Array.from(list.querySelectorAll('.ressource-search__parent-term')); // 适配高DPI屏幕 const dpr = window.devicePixelRatio || 1; function resizeCanvas() { const rect = list.getBoundingClientRect(); // 设置Canvas实际像素尺寸 canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; // 设置Canvas显示尺寸 canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`; // 缩放上下文 ctx.scale(dpr, dpr); // 更新元素坐标 itemCoordinates = items.map(getCanvasCoordinates); } const animationDuration = 500; function getCanvasCoordinates(element) { const rect = element.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect(); // 坐标取整,避免亚像素渲染 return { x: Math.round(rect.left + rect.width / 2 - canvasRect.left), y: Math.round(rect.top + rect.height / 2 - canvasRect.top) }; } let itemCoordinates = []; function drawLine(x1, y1, x2, y2, progress) { ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x1 + (x2 - x1) * progress, y1 + (y2 - y1) * progress); ctx.lineWidth = 1; ctx.strokeStyle = "#707070"; ctx.stroke(); } function animateLine(index) { const startTime = performance.now(); function animate(timestamp) { const elapsed = timestamp - startTime; const progress = Math.min(1, elapsed / animationDuration); // 每次绘制前清空画布 ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr); // 绘制已完成的线条 for (let i = 0; i < index; i++) { drawLine(itemCoordinates[i].x, itemCoordinates[i].y, itemCoordinates[i+1].x, itemCoordinates[i+1].y, 1); } // 绘制当前动画中的线条 drawLine(itemCoordinates[index].x, itemCoordinates[index].y, itemCoordinates[index+1].x, itemCoordinates[index+1].y, progress); if (progress < 1) { requestAnimationFrame(animate); } else { if (index + 2 < items.length) { animateLine(index + 1); } } } requestAnimationFrame(animate); } function startAnimation() { if (items.length > 1) { // 先清空画布 ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr); animateLine(0); } } // 初始化Canvas尺寸 resizeCanvas(); window.addEventListener('resize', () => { resizeCanvas(); startAnimation(); }); setTimeout(() => { startAnimation(); }, 50);
HTML 部分
<div class="ressource-search"> <canvas id="line-canvas" style="position: absolute; top: 0; left: 0;"></canvas> <ul class="ressource-search__parent-terms"> <li class="ressource-search__parent-term">test</li> <li class="ressource-search__parent-term">test</li> <li class="ressource-search__parent-term">test</li> </ul> </div>
关键修改说明
- 清屏处理:每次动画帧绘制前调用
ctx.clearRect,清除画布上的旧内容,避免线条叠加。 - 高DPI适配:通过
devicePixelRatio调整Canvas的实际像素尺寸和显示尺寸,并缩放绘图上下文,确保在高分辨率屏幕上清晰显示。 - 坐标取整:在
getCanvasCoordinates中对坐标进行取整,避免亚像素渲染导致的模糊。 - 动画逻辑优化:重构
animateLine,使用单个回调函数进行动画循环,避免重复创建函数实例,同时保留已完成的线条绘制。
内容的提问来源于stack exchange,提问作者Kilian
相关产品推荐
相关产品推荐

