canvas 简单直线轨迹运动与线性插值计算
一、canvas 直线轨迹运行
添加 canvas 语法提示
通过/** @type {HTMLCanvasElement} */代码块 来添加canvas的语法提示
<body>
<canvas id="canvas"></canvas>
</body>
<script>
/** @type {HTMLCanvasElement} */
</script>
获取 canvas,模拟demo数据
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById("canvas");
// 建议设置宽高的方式
canvas.width = 600;
canvas.height = 600;
const coords = [
[100, 100],
[200, 200],
[400, 200],
[500, 300],
[500, 500],
];
const img = new Image();
img.src = "img/arrow2.png";
let t = 0; // 图片移动步进
let index = 0; // 图片轨迹分段索引
const imgW = 242 / 10;
const imgH = 166 / 10;
// 图片运动轨迹分段
const animationCoords = [
[coords[0], coords[1]],
[coords[1], coords[2]],
[coords[2], coords[3]],
[coords[3], coords[4]],
];
// 获取canvas 的上下文
const ctx = canvas.getContext("2d");
根据模拟点位数据 coords,画轨迹
// 轨迹
function drawLine() {
ctx.beginPath();
ctx.moveTo(coords[0][0], coords[0][1]);
ctx.lineTo(coords[1][0], coords[1][1]);
ctx.lineTo(coords[2][0], coords[2][1]);
ctx.lineTo(coords[3][0], coords[3][1]);
ctx.lineTo(coords[4][0], coords[4][1]);
ctx.strokeStyle = "gray";
ctx.stroke();
}
drawLine();
运动中计算图片当前所在位置坐标与角度
/**
* @desc 根据t切割line坐标,计算当前点的角度值,用于动画
* @parmas start 轨迹线段开始坐标
* @parmas end 轨迹线段结束坐标
*/
function computed(start, end, t) {
const dx = end[0] - start[0];
const dy = end[1] - start[1];
const angle = Math.atan2(dy, dx);
if (t > 1) return {};
const x = start[0] + (end[0] - start[0]) * t;
const y = start[1] + (end[1] - start[1]) * t;
return {
x,
y,
angle,
};
}
绘制图片
function drawImage() {
if (t >= 1) {
t = 0; // 重置
index++; // 进行下一分段的轨迹运动
if (index === animationCoords.length) {
index = 0; // 反复运动
}
}
t += 0.01; // 控制动画速度
const start = animationCoords[index][0];
const end = animationCoords[index][1];
const { x, y, angle } = computed(start, end, t);
if (x === undefined) return;
ctx.beginPath();
ctx.translate(x, y); // 改变画布原点
ctx.rotate(angle); // 旋转画布
ctx.drawImage(img, -imgW / 2, -imgH / 2, imgW, imgH);
// setTransform设置默认矩阵 恢复画布原点 避免后续画图坐标偏离。也可以通过save和restore的方式来恢复画布原点,save可以保存变换矩阵,在translate之前调用save
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
编写动画函数
function animation() {
// 清空画布 动画每一帧之前都需要清空画布,除非动画下一帧是全屏覆盖
ctx.clearRect(0, 0, 600, 600);
requestAnimationFrame(animation); // 浏览器下一次渲染时执行动画函数
drawImage();
drawLine();
}
animation()
二、canvas 中的线性插值
已知 A,B 两点坐标,计算已知 x 或 y 的 C 点坐标,且 C 点在 AB 连线上
线性插值计算方法
/**
* @desc 线性插值
* @params coord1
* @params coord2
*/
function TDLI(coord1, coord2) {
const scale = (coord1[0] - coord2[0]) / (coord1[1] - coord2[1]);
return {
getY: (x) => (x - coord1[0]) / scale + coord1[1],
getX: (y) => (y - coord1[1]) * scale + coord1[0],
};
}
demo
const A = [90, 100];
const B = [205, 110];
const tdli = TDLI(A, B);
// 计算AB连线上 x坐标为 320的点
const Cx = 320;
const Cy = tdli.getY(Cx);
ctx.beginPath();
ctx.moveTo(A[0], A[1]);
ctx.lineTo(B[0], B[1]);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(B[0], B[1]);
ctx.lineTo(x, y);
ctx.setLineDash([4, 16]);
ctx.strokeStyle = "red";
ctx.stroke();