javascript - HTML 画布和动画
问题描述
我知道如何用 lineTo() 画一条线:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(300, 150);
ctx.stroke();
有没有办法将动画(例如使用anime.js)应用于生产线?
解决方案
一次一帧
要制作动画,您需要以高于人眼可以看到不同帧的速度(大约每秒 20 帧)绘制图形。
要在画布上执行此操作,您需要先清除显示,然后绘制场景。
例如,让一条线动画为围绕画布中心在两个圆圈中移动的点之间的线。变量time
用于确定画线的位置。如果我们添加到time
该线是向前动画,如果从时间减去该线在相反的方向移动。变量时间的变化越大,它移动线的速度就越快。
// time determines where the line is drawn
function drawLine(time) {
// get size of the canvas
const w = ctx.canvas.width;
const h = ctx.canvas.height;
// get the radius of circle that fits canvas
const radius = Math.min(w, h) / 2;
// get the center of the canvas
const cx = w / 2;
const cy = h / 2;
// start of line outer radius
const x1 = Math.cos(time) * radius * 0.9;
const y1 = Math.sin(time) * radius * 0.9;
// end of line, offset time
const x2 = Math.cos(time * 0.707) * radius * 0.4;
const y2 = Math.sin(time * 0.707) * radius * 0.4;
ctx.moveTo(x1 + cx, y1 + cy); // Add to path
ctx.lineTo(x2 + cx, y2 + cy);
}
渲染函数
现在您有了一个可以在不同时间绘制一条线的函数,您需要以看起来很生动的速度重复它。现代浏览器通过 DOM 提供了一个回调函数,旨在通过 DOM 呈现动画内容。它每秒调用 60 次(根据请求,每个请求),这是 DOM 呈现动画内容的最快速度。设置回调函数使用requestAnimationFrame
(见示例)
我们一般使用一个更新函数来处理清除和绘制框架的整个过程。下面是一个更新函数的例子,它可以为上面的 line 函数设置动画。
var time = 0; // position of line as abstracted time
const rate = 0.01; // Movement per frame in radians
requestAnimationFrame(updateFrame); // request the first frame
function updateFrame() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear the canvas
time += rate;
// set the line with color and end cap
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.strokeStyle = "black";
// create a path
ctx.beginPath();
drawLine(time);
// now draw it
ctx.stroke();
requestAnimationFrame(updateFrame);
}
运行示例
综上所述,我们得到以下结果。
我添加了一些东西让它更有趣。单击画布以添加行并重新启动时间变量。添加线条以了解在设备无法以愚弄眼睛的速度绘制线条之前可以绘制多少线条。对于将有很多线路的普通设备。(有一些方法可以大大提高该速率,但更先进一些)
添加大量线条还会显示一些有趣的锯齿效果,这些效果可以创建不存在的运动模式,但这是由于 DOM 渲染到画布的方式以及眼睛如何推断运动的微小缺陷造成的。
还检查画布与页面相比的大小,并在需要时调整画布大小。最好以这种方式调整大小而不是使用调整大小事件,因为调整大小事件与显示不同步。
// time determines where the line is drawn
function drawLine(time) {
// get size of the canvas
const w = ctx.canvas.width;
const h = ctx.canvas.height;
// get the radius of circle that fits canvas
const radius = Math.min(w, h) / 2;
// get the center of the canvas
const cx = w / 2;
const cy = h / 2;
// start of line outer radius
const x1 = Math.cos(time) * radius * 0.95;
const y1 = Math.sin(time*1.04) * radius * 0.95;
// end of line, offset time
const x2 = Math.cos(time * 0.9) * radius * 0.82;
const y2 = Math.sin(time * 0.902) * radius * 0.82;
// Offset inner circle a little for interesting FX
const ox = Math.cos(time * 0.7) * radius * 0.12;
const oy = Math.sin(time * 0.703) * radius * 0.12;
ctx.moveTo(x1 + cx, y1 + cy);
ctx.lineTo(x2 + cx + ox, y2 + cy + oy);
}
const ctx = canvas.getContext("2d");
canvas.addEventListener("click",() => {
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
timeOffsets.push(timeOffsets[timeOffsets.length -1] * 1.002);
time = 2;
});
const timeOffsets = [1];
var time = 0; // position of line as abstracted time
const rate = 0.01; // Movement per frame in radians
requestAnimationFrame(updateFrame); // request the first frame
function updateFrame() {
// check if the canvas size needs to change to fit the page
if (innerWidth !== ctx.canvas.width || innerHeight !== ctx.canvas.height) {
// changing the canvas resolution also clears the canvas so dont need to clear
ctx.canvas.width = innerWidth;
ctx.canvas.height = innerHeight;
timeOffsets.length = 1;
} else {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear the canvas
}
time += rate;
var separate = 0;
const angularSep = 8 / Math.min(ctx.canvas.width, ctx.canvas.height);
// set the line with color and end cap
ctx.lineWidth = 2;
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.beginPath();
for(const t of timeOffsets) {
drawLine(time * t + separate);
separate += angularSep;
}
ctx.stroke();
requestAnimationFrame(updateFrame);
}
body: {
padding: 0px;
margin: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
推荐阅读
- java - 连接字符串中每个单词的首字母,不带空格
- javascript - 为什么我的 jquery 计数器功能不起作用?
- db2 - 是否可以在存储过程中向用户授予角色?
- javascript - 如何根据Javascript和Jest单元测试中的可变条件将两个功能和两个单元测试合二为一
- applescript - 如何使用 AppleScript 登录远程 Mac?
- rust - missing generics for trait `actix_service::Service` --> src/middleware.rs:57:8
- arrays - 在 Swift 中创建 C 数组并将其指针传递给 C 函数(在 Swift 中)
- flutter - Flutter A RenderFlex 底部溢出 46 像素
- javascript - 如何在javascript中为对象动态添加属性?
- reactjs - 使用next js动态导入有条件导入模块,SSR不起作用