跳到主要内容

移动

位移动画的关键不是怎么改坐标,而是怎么让速度与帧率无关。

用法​

要使用Konva为形状设置移动动画,我们可以使用Konva.Animation创建一个新的动画, 并在每个动画帧中修改形状的位置。

有关Konva.Animation的属性和方法的完整列表,请查看Konva.Animation文档。

<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva@10/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Animate Position Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});

var layer = new Konva.Layer();

var hexagon = new Konva.RegularPolygon({
x: stage.getWidth() / 2,
y: stage.getHeight() / 2,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});

layer.add(hexagon);
stage.add(layer);

var amplitude = 100;
var period = 2000;
// in ms
var centerX = stage.getWidth() / 2;

var anim = new Konva.Animation(function(frame) {
hexagon.setX(amplitude * Math.sin(frame.time * 2 * Math.PI / period) + centerX);
}, layer);

anim.start();
</script>

</body>
</html>

常见问题​

为什么动画在不同设备上快慢不同?​

因为用了固定增量。node.x(node.x() + 2) 意味着「每帧移动 2 像素」, 而帧率是设备决定的——高刷屏 120 帧,低端机可能只有 30 帧,速度差四倍。

正确写法是按时间算:

const SPEED = 120; // 每秒 120 像素
new Konva.Animation((frame) => {
node.x(node.x() + SPEED * frame.timeDiff / 1000);
}, layer);

长时间运行后位置有偏差?​

累加浮点数会积累误差。每帧加一个小数,跑上几分钟之后, 实际位置与理论位置会有可见的偏离。

对周期性运动(来回摆动、绕圈),更好的做法是从总时间直接算出位置, 而不是累加:

new Konva.Animation((frame) => {
node.x(centerX + Math.sin(frame.time / 1000) * 100);
}, layer);

这样每一帧的值都是独立算出来的,不会累积误差, 而且动画随时暂停恢复都能对得上。

边界反弹怎么判断?​

常见的写法是「超出边界就反转速度」,但要注意先把位置夹回边界内, 否则物体可能在边界外抖动:

if (x > maxX) { x = maxX; dir = -1; }

只反转方向而不修正位置,下一帧算出来可能仍在界外, 于是又反转一次,表现为在边界处高频颤动。

与其他方案的取舍​

有明确起止点的位移,用 Konva.Tween 更省事——缓动、暂停、完成回调都是现成的,不用自己管时间。

Animation 的适用场景是没有终点或终点在运行中变化的运动: 持续漂浮的粒子、跟随鼠标的物体、受物理规则驱动的位置。

还有一种情况必须用 Animation:需要同时驱动大量节点。 一百个节点各建一个 Tween,等于每帧一百次独立的属性写入; 用一个 Animation 在单个回调里算完所有节点,开销小得多。