在 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>
我正在尝试创建一个应用程序,其中线条跟随鼠标的 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>