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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:08:17