在 html canvas 中移动行而不撕裂其末端

Shift line without tearing its ends in html canvas

我正在尝试创建一个应用程序,其中线条跟随鼠标的 y 方向并且每隔 x 毫秒向左移动一次。

问题是每行的连接处都有伪影,如下图所示。

我该如何预防?

并可能平滑线条。问题的主要部分是如何防止撕裂,但如果您知道如何平滑这条线,请告诉我。

代码如下或交替 JsFiddle

let y = null

const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');

ctx.strokeStyle = 'red';
ctx.lineCap = 'round';
ctx.lineJoin = 'round'

ctx.lineWidth = 5;

const width = ctx.canvas.width
const height = ctx.canvas.height

let prevY = null;


const onMouseUpdate = (e) => {
    y = e.pageY;
}
const shift = 10

canvas.addEventListener('mousemove', onMouseUpdate, false);
canvas.addEventListener('mouseenter', onMouseUpdate, false);

function draw() {
    const rect = canvas.getBoundingClientRect()
    const yTarget = y - rect.top

    ctx.beginPath()
    ctx.moveTo(width - shift, prevY);
    ctx.lineTo(width, yTarget);
    ctx.stroke();
    prevY = yTarget

    const imageData = ctx.getImageData(shift, 0, width - shift, height);
    ctx.putImageData(imageData, 0, 0);
    ctx.clearRect(width - shift, 0, shift, height);
}
setInterval(draw, 300)

问题是因为您正在绘制到 canvas 的边缘。因此,价值 lineWidth/2 的圆形末端部分被绘制在 canvas 之外并被剪裁。您只需要画到 canvasWidth - lineWidth/2

let y = null;

// resolution of the canvas
const scale = 2;
const lineWidth = 6;
const shift = 7;

const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');

canvas.width = canvas.clientWidth * scale;
canvas.height = canvas.clientHeight * scale;

ctx.strokeStyle = 'red';
ctx.lineCap = 'round';
ctx.lineJoin = 'round'
ctx.lineWidth = lineWidth;

const width = ctx.canvas.width - lineWidth / 2;
const height = ctx.canvas.height
const rect = canvas.getBoundingClientRect();

let prevY = null;

const onMouseUpdate = (e) => {
  y = (e.clientY - rect.top) * scale;
}

canvas.addEventListener('mousemove', onMouseUpdate, false);
canvas.addEventListener('mouseenter', onMouseUpdate, false);

let xOff = 0

function draw() {
  const yTarget = y;

  ctx.beginPath()
  ctx.moveTo(width - shift, prevY);
  ctx.lineTo(width, yTarget);
  ctx.stroke();
  prevY = yTarget

  const imageData = ctx.getImageData(shift, 0, width + +20 + lineWidth / 2, height);
  ctx.clearRect(0, 0, rect.width, rect.height);
  ctx.putImageData(imageData, 0, 0);
}

let i = setInterval(draw, 200);
canvas {
  height: 90vh;
  border: 1px solid blue;
  width: 80vw;
}
<canvas id="canvas"> </canvas>

我提高了 canvas 分辨率。有关详细信息,请参阅 .

最好的选择是绘制一条路径:将所有点存储在一个数组中,然后在每个新帧清除整个上下文并再次跟踪整个路径。

这是一个示例代码,我从数组中删除了屏幕外的点。请注意,我还使用 requestAnimationFrame() 安排回调在下一个绘画帧触发,从而实现流畅的动画。

const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');

let y = canvas.height / 2;
let data = [];

ctx.strokeStyle = 'red';
ctx.lineCap = 'round';
ctx.lineJoin = 'round'
ctx.lineWidth = 5;

const onMouseUpdate = (e) => {
  const rect = canvas.getBoundingClientRect();
  const yTarget = y - rect.top;
  y = e.pageY;
}

canvas.addEventListener('mousemove', onMouseUpdate, false);
canvas.addEventListener('mouseenter', onMouseUpdate, false);

let lastTime = performance.now();
const speed = 33.3; // 10px per 300ms -> 33.3px per s
requestAnimationFrame(loop);

function loop(now) {
  // calculate x based on how much time elapsed since the last frame
  // we store the distance from the last point
  const x = ((now - lastTime) / 1000) * speed;
  lastTime = now;

  data.push({x, y});

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.beginPath()
  let currentX = canvas.width;
  // iterate from end to start
  for (let i = data.length - 1; i >= 0; i--) {
    const {x, y} = data[i];
    currentX -= data[i].x;
    if (currentX < 0) { // the first points are outside the screen
      data = data.slice(i); // update our data array
      break;
    }
    ctx.lineTo(currentX, y);
  }
  // paint all in one call
  ctx.stroke();
  requestAnimationFrame(loop);
}
<canvas id="canvas" height="400" width="500"> </canvas>