1. Canvas坐标系统基础
1.1 坐标系统概念
Canvas使用二维笛卡尔坐标系统,原点(0,0)位于左上角:
// 获取canvas和上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Canvas坐标系统特点:
// - 原点(0,0)在左上角
// - X轴向右为正方向
// - Y轴向下为正方向
// - 单位为像素
// 绘制坐标系参考线
function drawCoordinateSystem() {
const width = canvas.width;
const height = canvas.height;
ctx.strokeStyle = 'lightgray';
ctx.lineWidth = 1;
// 绘制网格
for (let x = 0; x <= width; x += 50) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
for (let y = 0; y <= height; y += 50) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
// 绘制坐标轴
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
// X轴
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(width, 0);
ctx.stroke();
// Y轴
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, height);
ctx.stroke();
// 标记原点
ctx.fillStyle = 'red';
ctx.fillRect(-2, -2, 4, 4);
// 添加坐标标签
ctx.fillStyle = 'black';
ctx.font = '12px Arial';
ctx.fillText('(0,0)', 5, 15);
ctx.fillText(`(${width},0)`, width - 50, 15);
ctx.fillText(`(0,${height})`, 5, height - 5);
}
drawCoordinateSystem();
1.2 坐标转换工具
// 坐标转换工具类
class CoordinateUtils {
// 屏幕坐标转Canvas坐标
static screenToCanvas(canvas, screenX, screenY) {
const rect = canvas.getBoundingClientRect();
return {
x: screenX - rect.left,
y: screenY - rect.top
};
}
// Canvas坐标转屏幕坐标
static canvasToScreen(canvas, canvasX, canvasY) {
const rect = canvas.getBoundingClientRect();
return {
x: canvasX + rect.left,
y: canvasY + rect.top
};
}
// 数学坐标系转Canvas坐标系
static mathToCanvas(mathX, mathY, centerX, centerY, scale = 1) {
return {
x: centerX + mathX * scale,
y: centerY - mathY * scale // 注意Y轴翻转
};
}
// Canvas坐标系转数学坐标系
static canvasToMath(canvasX, canvasY, centerX, centerY, scale = 1) {
return {
x: (canvasX - centerX) / scale,
y: (centerY - canvasY) / scale // 注意Y轴翻转
};
}
// 极坐标转直角坐标
static polarToCartesian(r, theta) {
return {
x: r * Math.cos(theta),
y: r * Math.sin(theta)
};
}
// 直角坐标转极坐标
static cartesianToPolar(x, y) {
return {
r: Math.sqrt(x * x + y * y),
theta: Math.atan2(y, x)
};
}
}
// 使用示例
canvas.addEventListener('mousemove', (e) => {
const canvasCoord = CoordinateUtils.screenToCanvas(canvas, e.clientX, e.clientY);
const mathCoord = CoordinateUtils.canvasToMath(
canvasCoord.x, canvasCoord.y,
canvas.width / 2, canvas.height / 2, 1
);
console.log(`Canvas: (${canvasCoord.x.toFixed(1)}, ${canvasCoord.y.toFixed(1)})`);
console.log(`Math: (${mathCoord.x.toFixed(1)}, ${mathCoord.y.toFixed(1)})`);
});
2. 基础变换操作
2.1 平移变换 (translate)
// 平移变换基础
function translateDemo() {
// 保存当前状态
ctx.save();
// 绘制原始矩形
ctx.fillStyle = 'red';
ctx.fillRect(50, 50, 100, 50);
// 平移坐标系
ctx.translate(150, 100);
// 在新坐标系中绘制(相对于新原点)
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 50); // 实际位置是 (200, 150)
// 再次平移
ctx.translate(50, 50);
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, 100, 50); // 实际位置是 (250, 200)
// 恢复状态
ctx.restore();
// 验证坐标系已恢复
ctx.fillStyle = 'orange';
ctx.fillRect(50, 200, 100, 50);
}
translateDemo();
2.2 旋转变换 (rotate)
// 旋转变换基础
function rotateDemo() {
const centerX = 400;
const centerY = 150;
// 绘制旋转中心点
ctx.fillStyle = 'red';
ctx.fillRect(centerX - 2, centerY - 2, 4, 4);
// 绘制多个旋转的矩形
for (let i = 0; i < 8; i++) {
ctx.save();
// 移动到旋转中心
ctx.translate(centerX, centerY);
// 旋转
ctx.rotate((i * Math.PI) / 4);
// 绘制矩形(相对于旋转中心)
ctx.fillStyle = `hsl(${i * 45}, 70%, 50%)`;
ctx.fillRect(-50, -10, 100, 20);
ctx.restore();
}
}
rotateDemo();
2.3 缩放变换 (scale)
// 缩放变换基础
function scaleDemo() {
const baseX = 600;
const baseY = 50;
// 原始大小
ctx.fillStyle = 'red';
ctx.fillRect(baseX, baseY, 50, 50);
// 不同缩放比例
const scales = [0.5, 1.5, 2.0, 2.5];
scales.forEach((scale, index) => {
ctx.save();
const x = baseX + (index + 1) * 80;
// 移动到缩放中心
ctx.translate(x + 25, baseY + 25);
// 缩放
ctx.scale(scale, scale);
// 绘制矩形(以中心为原点)
ctx.fillStyle = `hsl(${index * 60}, 70%, 50%)`;
ctx.fillRect(-25, -25, 50, 50);
ctx.restore();
// 添加标签
ctx.fillStyle = 'black';
ctx.font = '12px Arial';
ctx.fillText(`${scale}x`, x + 10, baseY + 70);
});
}
scaleDemo();
2.4 组合变换
// 组合变换示例
function combinedTransformDemo() {
const centerX = 400;
const centerY = 350;
// 绘制参考点
ctx.fillStyle = 'red';
ctx.fillRect(centerX - 2, centerY - 2, 4, 4);
// 创建一个复杂的变换序列
ctx.save();
// 1. 平移到中心点
ctx.translate(centerX, centerY);
// 2. 旋转45度
ctx.rotate(Math.PI / 4);
// 3. 缩放1.5倍
ctx.scale(1.5, 1.5);
// 4. 再次平移
ctx.translate(30, 0);
// 绘制图形
ctx.fillStyle = 'blue';
ctx.fillRect(-25, -25, 50, 50);
// 在变换后的坐标系中继续绘制
ctx.fillStyle = 'green';
ctx.beginPath();
ctx.arc(0, 0, 15, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
combinedTransformDemo();
3. 变换矩阵
3.1 变换矩阵基础
// 变换矩阵类
class TransformMatrix {
constructor(a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) {
// 矩阵格式:
// [a c e]
// [b d f]
// [0 0 1]
this.a = a; // 水平缩放
this.b = b; // 水平倾斜
this.c = c; // 垂直倾斜
this.d = d; // 垂直缩放
this.e = e; // 水平平移
this.f = f; // 垂直平移
}
// 创建单位矩阵
static identity() {
return new TransformMatrix();
}
// 创建平移矩阵
static translate(tx, ty) {
return new TransformMatrix(1, 0, 0, 1, tx, ty);
}
// 创建旋转矩阵
static rotate(angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return new TransformMatrix(cos, sin, -sin, cos, 0, 0);
}
// 创建缩放矩阵
static scale(sx, sy = sx) {
return new TransformMatrix(sx, 0, 0, sy, 0, 0);
}
// 创建倾斜矩阵
static skew(skewX, skewY) {
return new TransformMatrix(
1, Math.tan(skewY),
Math.tan(skewX), 1,
0, 0
);
}
// 矩阵乘法
multiply(other) {
const a = this.a * other.a + this.c * other.b;
const b = this.b * other.a + this.d * other.b;
const c = this.a * other.c + this.c * other.d;
const d = this.b * other.c + this.d * other.d;
const e = this.a * other.e + this.c * other.f + this.e;
const f = this.b * other.e + this.d * other.f + this.f;
return new TransformMatrix(a, b, c, d, e, f);
}
// 变换点
transformPoint(x, y) {
return {
x: this.a * x + this.c * y + this.e,
y: this.b * x + this.d * y + this.f
};
}
// 获取逆矩阵
inverse() {
const det = this.a * this.d - this.b * this.c;
if (Math.abs(det) < 1e-10) {
throw new Error('Matrix is not invertible');
}
return new TransformMatrix(
this.d / det,
-this.b / det,
-this.c / det,
this.a / det,
(this.c * this.f - this.d * this.e) / det,
(this.b * this.e - this.a * this.f) / det
);
}
// 应用到Canvas上下文
applyToContext(ctx) {
ctx.setTransform(this.a, this.b, this.c, this.d, this.e, this.f);
}
// 转换为CSS transform字符串
toCSSTransform() {
return `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`;
}
// 克隆矩阵
clone() {
return new TransformMatrix(this.a, this.b, this.c, this.d, this.e, this.f);
}
}
// 使用变换矩阵
function matrixDemo() {
// 创建复合变换矩阵
const transform = TransformMatrix.identity()
.multiply(TransformMatrix.translate(200, 200))
.multiply(TransformMatrix.rotate(Math.PI / 6))
.multiply(TransformMatrix.scale(1.5, 1.0))
.multiply(TransformMatrix.translate(-25, -25));
// 应用变换
transform.applyToContext(ctx);
// 绘制图形
ctx.fillStyle = 'purple';
ctx.fillRect(0, 0, 50, 50);
// 重置变换
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 验证点变换
const originalPoint = { x: 25, y: 25 };
const transformedPoint = transform.transformPoint(originalPoint.x, originalPoint.y);
console.log('原始点:', originalPoint);
console.log('变换后点:', transformedPoint);
// 绘制变换后的点
ctx.fillStyle = 'red';
ctx.fillRect(transformedPoint.x - 2, transformedPoint.y - 2, 4, 4);
}
matrixDemo();
3.2 自定义变换
// 自定义变换效果
class CustomTransforms {
// 透视变换
static perspective(ctx, focalLength, vanishingPointX, vanishingPointY) {
return {
transformPoint(x, y, z = 0) {
const scale = focalLength / (focalLength + z);
return {
x: vanishingPointX + (x - vanishingPointX) * scale,
y: vanishingPointY + (y - vanishingPointY) * scale
};
}
};
}
// 弹性变换
static elastic(amplitude, frequency, phase = 0) {
return {
transformPoint(x, y) {
const offset = Math.sin(x * frequency + phase) * amplitude;
return { x, y: y + offset };
}
};
}
// 波浪变换
static wave(amplitude, wavelength, direction = 'horizontal') {
return {
transformPoint(x, y) {
if (direction === 'horizontal') {
const offset = Math.sin((x / wavelength) * Math.PI * 2) * amplitude;
return { x, y: y + offset };
} else {
const offset = Math.sin((y / wavelength) * Math.PI * 2) * amplitude;
return { x: x + offset, y };
}
}
};
}
// 漩涡变换
static vortex(centerX, centerY, strength) {
return {
transformPoint(x, y) {
const dx = x - centerX;
const dy = y - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx) + (strength / (distance + 1));
return {
x: centerX + distance * Math.cos(angle),
y: centerY + distance * Math.sin(angle)
};
}
};
}
}
// 应用自定义变换
function customTransformDemo() {
// 创建网格进行变换演示
function drawTransformedGrid(transform, startX, startY, gridSize = 20, gridCount = 10) {
ctx.strokeStyle = 'blue';
ctx.lineWidth = 1;
// 绘制水平线
for (let i = 0; i <= gridCount; i++) {
ctx.beginPath();
for (let j = 0; j <= gridCount; j++) {
const x = startX + j * gridSize;
const y = startY + i * gridSize;
const transformed = transform.transformPoint(x, y);
if (j === 0) {
ctx.moveTo(transformed.x, transformed.y);
} else {
ctx.lineTo(transformed.x, transformed.y);
}
}
ctx.stroke();
}
// 绘制垂直线
for (let j = 0; j <= gridCount; j++) {
ctx.beginPath();
for (let i = 0; i <= gridCount; i++) {
const x = startX + j * gridSize;
const y = startY + i * gridSize;
const transformed = transform.transformPoint(x, y);
if (i === 0) {
ctx.moveTo(transformed.x, transformed.y);
} else {
ctx.lineTo(transformed.x, transformed.y);
}
}
ctx.stroke();
}
}
// 波浪变换
const waveTransform = CustomTransforms.wave(20, 100, 'horizontal');
drawTransformedGrid(waveTransform, 50, 400, 15, 8);
// 漩涡变换
const vortexTransform = CustomTransforms.vortex(400, 500, 0.5);
drawTransformedGrid(vortexTransform, 300, 400, 15, 8);
// 弹性变换
const elasticTransform = CustomTransforms.elastic(15, 0.1, 0);
drawTransformedGrid(elasticTransform, 550, 400, 15, 8);
}
customTransformDemo();
4. 坐标系统管理
4.1 状态栈管理
// 高级状态管理类
class CanvasStateManager {
constructor(ctx) {
this.ctx = ctx;
this.stateStack = [];
this.namedStates = new Map();
}
// 保存当前状态
save(name = null) {
const state = this.getCurrentState();
this.stateStack.push(state);
if (name) {
this.namedStates.set(name, state);
}
this.ctx.save();
return this;
}
// 恢复状态
restore() {
if (this.stateStack.length > 0) {
this.stateStack.pop();
}
this.ctx.restore();
return this;
}
// 恢复到命名状态
restoreToNamed(name) {
if (this.namedStates.has(name)) {
const state = this.namedStates.get(name);
this.applyState(state);
}
return this;
}
// 获取当前状态
getCurrentState() {
const transform = this.ctx.getTransform();
return {
transform: {
a: transform.a,
b: transform.b,
c: transform.c,
d: transform.d,
e: transform.e,
f: transform.f
},
fillStyle: this.ctx.fillStyle,
strokeStyle: this.ctx.strokeStyle,
lineWidth: this.ctx.lineWidth,
lineCap: this.ctx.lineCap,
lineJoin: this.ctx.lineJoin,
globalAlpha: this.ctx.globalAlpha,
globalCompositeOperation: this.ctx.globalCompositeOperation
};
}
// 应用状态
applyState(state) {
this.ctx.setTransform(
state.transform.a,
state.transform.b,
state.transform.c,
state.transform.d,
state.transform.e,
state.transform.f
);
this.ctx.fillStyle = state.fillStyle;
this.ctx.strokeStyle = state.strokeStyle;
this.ctx.lineWidth = state.lineWidth;
this.ctx.lineCap = state.lineCap;
this.ctx.lineJoin = state.lineJoin;
this.ctx.globalAlpha = state.globalAlpha;
this.ctx.globalCompositeOperation = state.globalCompositeOperation;
}
// 重置到初始状态
reset() {
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.fillStyle = 'black';
this.ctx.strokeStyle = 'black';
this.ctx.lineWidth = 1;
this.ctx.lineCap = 'butt';
this.ctx.lineJoin = 'miter';
this.ctx.globalAlpha = 1;
this.ctx.globalCompositeOperation = 'source-over';
return this;
}
// 链式变换方法
translate(x, y) {
this.ctx.translate(x, y);
return this;
}
rotate(angle) {
this.ctx.rotate(angle);
return this;
}
scale(x, y = x) {
this.ctx.scale(x, y);
return this;
}
// 执行带状态保护的操作
withState(callback, name = null) {
this.save(name);
try {
callback(this.ctx);
} finally {
this.restore();
}
return this;
}
}
// 使用状态管理器
const stateManager = new CanvasStateManager(ctx);
// 链式操作示例
stateManager
.save('initial')
.translate(100, 100)
.rotate(Math.PI / 4)
.scale(1.5)
.withState((ctx) => {
ctx.fillStyle = 'red';
ctx.fillRect(-25, -25, 50, 50);
})
.translate(100, 0)
.withState((ctx) => {
ctx.fillStyle = 'blue';
ctx.fillRect(-25, -25, 50, 50);
})
.restoreToNamed('initial');
4.2 视口和相机系统
// 2D相机系统
class Camera2D {
constructor(x = 0, y = 0, zoom = 1, rotation = 0) {
this.x = x;
this.y = y;
this.zoom = zoom;
this.rotation = rotation;
this.bounds = null;
}
// 设置边界限制
setBounds(minX, minY, maxX, maxY) {
this.bounds = { minX, minY, maxX, maxY };
}
// 移动相机
move(dx, dy) {
this.x += dx;
this.y += dy;
this.constrainToBounds();
}
// 设置相机位置
setPosition(x, y) {
this.x = x;
this.y = y;
this.constrainToBounds();
}
// 缩放
setZoom(zoom) {
this.zoom = Math.max(0.1, Math.min(10, zoom));
}
// 旋转
setRotation(rotation) {
this.rotation = rotation;
}
// 约束到边界
constrainToBounds() {
if (this.bounds) {
this.x = Math.max(this.bounds.minX, Math.min(this.bounds.maxX, this.x));
this.y = Math.max(this.bounds.minY, Math.min(this.bounds.maxY, this.y));
}
}
// 应用相机变换
applyTransform(ctx, canvasWidth, canvasHeight) {
// 移动到画布中心
ctx.translate(canvasWidth / 2, canvasHeight / 2);
// 应用缩放
ctx.scale(this.zoom, this.zoom);
// 应用旋转
ctx.rotate(this.rotation);
// 应用相机位置(注意符号)
ctx.translate(-this.x, -this.y);
}
// 屏幕坐标转世界坐标
screenToWorld(screenX, screenY, canvasWidth, canvasHeight) {
// 转换到以画布中心为原点的坐标
const centerX = screenX - canvasWidth / 2;
const centerY = screenY - canvasHeight / 2;
// 应用逆变换
const cos = Math.cos(-this.rotation);
const sin = Math.sin(-this.rotation);
const rotatedX = centerX * cos - centerY * sin;
const rotatedY = centerX * sin + centerY * cos;
return {
x: this.x + rotatedX / this.zoom,
y: this.y + rotatedY / this.zoom
};
}
// 世界坐标转屏幕坐标
worldToScreen(worldX, worldY, canvasWidth, canvasHeight) {
// 相对于相机的位置
const relativeX = (worldX - this.x) * this.zoom;
const relativeY = (worldY - this.y) * this.zoom;
// 应用旋转
const cos = Math.cos(this.rotation);
const sin = Math.sin(this.rotation);
const rotatedX = relativeX * cos - relativeY * sin;
const rotatedY = relativeX * sin + relativeY * cos;
return {
x: rotatedX + canvasWidth / 2,
y: rotatedY + canvasHeight / 2
};
}
// 跟随目标
followTarget(targetX, targetY, lerp = 0.1) {
this.x += (targetX - this.x) * lerp;
this.y += (targetY - this.y) * lerp;
this.constrainToBounds();
}
// 获取可视区域
getViewBounds(canvasWidth, canvasHeight) {
const halfWidth = (canvasWidth / 2) / this.zoom;
const halfHeight = (canvasHeight / 2) / this.zoom;
return {
left: this.x - halfWidth,
right: this.x + halfWidth,
top: this.y - halfHeight,
bottom: this.y + halfHeight
};
}
}
// 相机使用示例
const camera = new Camera2D(0, 0, 1, 0);
camera.setBounds(-500, -500, 500, 500);
// 世界对象
const worldObjects = [
{ x: 0, y: 0, width: 50, height: 50, color: 'red' },
{ x: 100, y: 100, width: 30, height: 30, color: 'blue' },
{ x: -150, y: 50, width: 40, height: 40, color: 'green' },
{ x: 200, y: -100, width: 60, height: 20, color: 'yellow' }
];
// 渲染函数
function renderWithCamera() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 保存状态
ctx.save();
// 应用相机变换
camera.applyTransform(ctx, canvas.width, canvas.height);
// 绘制世界坐标系
ctx.strokeStyle = 'lightgray';
ctx.lineWidth = 1 / camera.zoom; // 保持线条粗细
// 绘制网格
const bounds = camera.getViewBounds(canvas.width, canvas.height);
const gridSize = 50;
for (let x = Math.floor(bounds.left / gridSize) * gridSize; x <= bounds.right; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, bounds.top);
ctx.lineTo(x, bounds.bottom);
ctx.stroke();
}
for (let y = Math.floor(bounds.top / gridSize) * gridSize; y <= bounds.bottom; y += gridSize) {
ctx.beginPath();
ctx.moveTo(bounds.left, y);
ctx.lineTo(bounds.right, y);
ctx.stroke();
}
// 绘制坐标轴
ctx.strokeStyle = 'black';
ctx.lineWidth = 2 / camera.zoom;
ctx.beginPath();
ctx.moveTo(bounds.left, 0);
ctx.lineTo(bounds.right, 0);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, bounds.top);
ctx.lineTo(0, bounds.bottom);
ctx.stroke();
// 绘制世界对象
worldObjects.forEach(obj => {
ctx.fillStyle = obj.color;
ctx.fillRect(obj.x - obj.width/2, obj.y - obj.height/2, obj.width, obj.height);
});
// 恢复状态
ctx.restore();
// 绘制UI(不受相机影响)
ctx.fillStyle = 'black';
ctx.font = '14px Arial';
ctx.fillText(`Camera: (${camera.x.toFixed(1)}, ${camera.y.toFixed(1)})`, 10, 20);
ctx.fillText(`Zoom: ${camera.zoom.toFixed(2)}x`, 10, 40);
ctx.fillText(`Rotation: ${(camera.rotation * 180 / Math.PI).toFixed(1)}°`, 10, 60);
}
// 相机控制
let keys = {};
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
camera.setZoom(camera.zoom * zoomFactor);
});
// 相机更新循环
function updateCamera() {
const speed = 5 / camera.zoom;
if (keys['ArrowLeft'] || keys['a']) camera.move(-speed, 0);
if (keys['ArrowRight'] || keys['d']) camera.move(speed, 0);
if (keys['ArrowUp'] || keys['w']) camera.move(0, -speed);
if (keys['ArrowDown'] || keys['s']) camera.move(0, speed);
if (keys['q']) camera.setRotation(camera.rotation - 0.02);
if (keys['e']) camera.setRotation(camera.rotation + 0.02);
renderWithCamera();
requestAnimationFrame(updateCamera);
}
// 开始相机循环
updateCamera();
5. 高级变换技巧
5.1 动画变换
// 动画变换类
class AnimatedTransform {
constructor() {
this.animations = [];
this.isRunning = false;
}
// 添加动画
addAnimation(config) {
const animation = {
id: Math.random().toString(36).substr(2, 9),
startTime: performance.now(),
duration: config.duration || 1000,
easing: config.easing || this.easeInOutQuad,
from: config.from,
to: config.to,
onUpdate: config.onUpdate,
onComplete: config.onComplete
};
this.animations.push(animation);
if (!this.isRunning) {
this.start();
}
return animation.id;
}
// 移除动画
removeAnimation(id) {
this.animations = this.animations.filter(anim => anim.id !== id);
}
// 开始动画循环
start() {
this.isRunning = true;
this.update();
}
// 停止动画循环
stop() {
this.isRunning = false;
}
// 更新动画
update() {
if (!this.isRunning) return;
const currentTime = performance.now();
this.animations = this.animations.filter(animation => {
const elapsed = currentTime - animation.startTime;
const progress = Math.min(elapsed / animation.duration, 1);
const easedProgress = animation.easing(progress);
// 计算当前值
const currentValue = this.interpolate(animation.from, animation.to, easedProgress);
// 调用更新回调
if (animation.onUpdate) {
animation.onUpdate(currentValue, progress);
}
// 检查是否完成
if (progress >= 1) {
if (animation.onComplete) {
animation.onComplete(animation.to);
}
return false; // 移除动画
}
return true; // 保留动画
});
if (this.animations.length > 0) {
requestAnimationFrame(() => this.update());
} else {
this.isRunning = false;
}
}
// 插值函数
interpolate(from, to, progress) {
if (typeof from === 'number') {
return from + (to - from) * progress;
}
if (typeof from === 'object') {
const result = {};
for (const key in from) {
if (from.hasOwnProperty(key)) {
result[key] = this.interpolate(from[key], to[key], progress);
}
}
return result;
}
return progress < 0.5 ? from : to;
}
// 缓动函数
easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}
easeInOutElastic(t) {
if (t === 0 || t === 1) return t;
const p = 0.3;
const s = p / 4;
if (t < 0.5) {
t = 2 * t - 1;
return -0.5 * Math.pow(2, 10 * t) * Math.sin((t - s) * (2 * Math.PI) / p);
} else {
t = 2 * t - 1;
return 0.5 * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
}
}
}
// 使用动画变换
const animator = new AnimatedTransform();
// 创建一个旋转的矩形
let rectTransform = {
x: 400,
y: 300,
rotation: 0,
scale: 1
};
// 添加旋转动画
animator.addAnimation({
duration: 3000,
from: { rotation: 0 },
to: { rotation: Math.PI * 2 },
easing: animator.easeInOutQuad,
onUpdate: (value) => {
rectTransform.rotation = value.rotation;
},
onComplete: () => {
// 循环动画
animator.addAnimation({
duration: 3000,
from: { rotation: 0 },
to: { rotation: Math.PI * 2 },
easing: animator.easeInOutQuad,
onUpdate: (value) => {
rectTransform.rotation = value.rotation;
}
});
}
});
// 添加缩放动画
animator.addAnimation({
duration: 2000,
from: { scale: 1 },
to: { scale: 1.5 },
easing: animator.easeInOutElastic,
onUpdate: (value) => {
rectTransform.scale = value.scale;
},
onComplete: () => {
// 反向缩放
animator.addAnimation({
duration: 2000,
from: { scale: 1.5 },
to: { scale: 1 },
easing: animator.easeInOutElastic,
onUpdate: (value) => {
rectTransform.scale = value.scale;
}
});
}
});
// 渲染动画
function renderAnimation() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
// 应用变换
ctx.translate(rectTransform.x, rectTransform.y);
ctx.rotate(rectTransform.rotation);
ctx.scale(rectTransform.scale, rectTransform.scale);
// 绘制矩形
ctx.fillStyle = 'blue';
ctx.fillRect(-25, -25, 50, 50);
ctx.restore();
requestAnimationFrame(renderAnimation);
}
renderAnimation();
5.2 3D效果模拟
// 伪3D变换类
class Pseudo3D {
constructor(focalLength = 300) {
this.focalLength = focalLength;
}
// 3D点投影到2D
project(x, y, z) {
const scale = this.focalLength / (this.focalLength + z);
return {
x: x * scale,
y: y * scale,
scale: scale
};
}
// 绘制3D立方体
drawCube(ctx, centerX, centerY, size, rotationX, rotationY, rotationZ) {
// 立方体顶点(相对于中心)
const vertices = [
[-size, -size, -size], [size, -size, -size],
[size, size, -size], [-size, size, -size],
[-size, -size, size], [size, -size, size],
[size, size, size], [-size, size, size]
];
// 应用旋转
const rotatedVertices = vertices.map(vertex => {
return this.rotatePoint(vertex, rotationX, rotationY, rotationZ);
});
// 投影到2D
const projectedVertices = rotatedVertices.map(vertex => {
const projected = this.project(vertex[0], vertex[1], vertex[2]);
return {
x: centerX + projected.x,
y: centerY + projected.y,
z: vertex[2],
scale: projected.scale
};
});
// 定义面(顶点索引)
const faces = [
[0, 1, 2, 3], // 前面
[4, 7, 6, 5], // 后面
[0, 4, 5, 1], // 底面
[2, 6, 7, 3], // 顶面
[0, 3, 7, 4], // 左面
[1, 5, 6, 2] // 右面
];
// 计算面的平均Z值用于排序
const facesWithZ = faces.map((face, index) => {
const avgZ = face.reduce((sum, vertexIndex) => {
return sum + projectedVertices[vertexIndex].z;
}, 0) / face.length;
return { face, avgZ, index };
});
// 按Z值排序(远的先绘制)
facesWithZ.sort((a, b) => a.avgZ - b.avgZ);
// 绘制面
facesWithZ.forEach(({ face, index }) => {
ctx.beginPath();
face.forEach((vertexIndex, i) => {
const vertex = projectedVertices[vertexIndex];
if (i === 0) {
ctx.moveTo(vertex.x, vertex.y);
} else {
ctx.lineTo(vertex.x, vertex.y);
}
});
ctx.closePath();
// 根据面的索引设置颜色
const colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple'];
const brightness = Math.max(0.3, (facesWithZ[index].avgZ + 100) / 200);
ctx.fillStyle = this.adjustBrightness(colors[index], brightness);
ctx.fill();
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.stroke();
});
}
// 旋转点
rotatePoint([x, y, z], rotX, rotY, rotZ) {
// 绕X轴旋转
let cosX = Math.cos(rotX), sinX = Math.sin(rotX);
let y1 = y * cosX - z * sinX;
let z1 = y * sinX + z * cosX;
// 绕Y轴旋转
let cosY = Math.cos(rotY), sinY = Math.sin(rotY);
let x2 = x * cosY + z1 * sinY;
let z2 = -x * sinY + z1 * cosY;
// 绕Z轴旋转
let cosZ = Math.cos(rotZ), sinZ = Math.sin(rotZ);
let x3 = x2 * cosZ - y1 * sinZ;
let y3 = x2 * sinZ + y1 * cosZ;
return [x3, y3, z2];
}
// 调整颜色亮度
adjustBrightness(color, brightness) {
const colors = {
red: [255, 0, 0],
green: [0, 255, 0],
blue: [0, 0, 255],
yellow: [255, 255, 0],
orange: [255, 165, 0],
purple: [128, 0, 128]
};
const rgb = colors[color] || [128, 128, 128];
const adjustedRgb = rgb.map(c => Math.floor(c * brightness));
return `rgb(${adjustedRgb[0]}, ${adjustedRgb[1]}, ${adjustedRgb[2]})`;
}
}
// 3D演示
const pseudo3D = new Pseudo3D(400);
let cubeRotation = { x: 0, y: 0, z: 0 };
function render3D() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新旋转
cubeRotation.x += 0.01;
cubeRotation.y += 0.015;
cubeRotation.z += 0.005;
// 绘制多个立方体
const positions = [
{ x: 200, y: 200, size: 50 },
{ x: 400, y: 200, size: 30 },
{ x: 600, y: 200, size: 40 }
];
positions.forEach((pos, index) => {
const rotX = cubeRotation.x + index * 0.5;
const rotY = cubeRotation.y + index * 0.3;
const rotZ = cubeRotation.z + index * 0.7;
pseudo3D.drawCube(ctx, pos.x, pos.y, pos.size, rotX, rotY, rotZ);
});
requestAnimationFrame(render3D);
}
render3D();
6. 小结
本章全面介绍了Canvas的变换与坐标系统:
- 坐标系统基础:Canvas坐标系统、坐标转换工具
- 基础变换操作:平移、旋转、缩放、组合变换
- 变换矩阵:矩阵运算、自定义变换效果
- 坐标系统管理:状态栈管理、视口和相机系统
- 高级变换技巧:动画变换、3D效果模拟
下一章我们将学习动画基础与帧循环,包括动画原理、帧率控制和动画优化技术。
7. 练习题
- 创建一个支持缩放、平移、旋转的图像查看器
- 实现一个2D游戏相机系统,支持跟随目标和边界限制
- 制作一个3D立方体旋转动画
- 创建一个变换动画编辑器,支持关键帧动画
- 实现一个支持多层变换的图形编辑器