メインコンテンツへ移動

曲線軌道アニメーションの原理

無料2017-01-07#JS#CSS曲线动画#CSS轨迹动画#CSS沿曲线运动#JS曲线动画#JS抛物线动画

アニメーションライブラリは通常、さまざまなイージング(easing)効果を提供していますが、曲線運動を実現するにはちょっとしたコツが必要です。

1. アニメーション関数

アニメーションとは、変位を時間の関数として表したものです:s = f(t)

独立変数はt、従属変数はsであり、物体の変位が時間とともに変化することでアニメーションに見えます。例えば:

// 已知
var property = 'marginLeft';
var s0 = 100;   // 起点
var s1 = 200;   // 终点
var duration = 1000;

// 由题意得
var S = s1 - s0;    // 总位移
var T = duration;   // 总时间

// 求任意时刻t对应的位移
var t0 = +new Date();
var tick, interval = 1000 / 60;
setTimeout(tick = function() {
    var t = +new Date() - t0;
    // 完成度
    var p = Math.min(t / T, 1);
    // t时刻相对起点的位移s
    var s = S * p;
    document.body.style[property] = s0 + s + 'px';

    if (p !== 1) setTimeout(tick, interval);
}, interval);

marginLeft100px から 200px まで均等に変化し、body がまず右に 100px ジャンプした後、1秒かけて等速で右に 100px 移動します。

このようなアニメーションを実現するために直面する唯一の問題は、総変位Sと総時間Tが既知であるとき、任意の時刻tにおける起点からの変位sを求めることです。

等速直線運動を実装しましたが、s = vt を使っていないように見えて、実は使っています:

s = v * t
  = (S / T) * t
  = S * (t / T)
  = S * p

アニメーション関数は s = f(t) であり、その中に v は含まれないため、v既知の量に置き換える必要があります。進捗度 p = t / T であるため、アニメーションは変位が進捗度の関数であるとも言えます。

2. 等加速度運動

同様に、等加速度運動の変位公式における va を置き換え、変位 s を時間 t の関数として求めます。

等加速

変位公式:

// v0 = 0时,只有一个未知量a
s = 1/2at^2

総時間T、総変位S、進捗度p = t / Tが既知であるとき、任意の時刻tにおける起点からの変位sを求めます:

// 终点处有
S = 1/2 * a * T^2
// 得
a = 2S / T^2
// 任意时刻
s = 1/2 * a * t^2
  = 1/2 * (2S / T^2) * t^2
  = 1/2 * 2S * (t^2 / T^2)
  = S * p^2

等減速

変位公式:

// 含有2个未知量v0和a
s = v0t - 1/2at^2

総時間T、総変位S、進捗度p = t / Tが既知であるとき、任意の時刻tにおける起点からの変位sを求めます:

// 1.逆向匀加速求v0
// 起点处有
S = 1/2 * a * T^2
// 得
a = 2S / T^2
v0 = aT = 2S / T^2 * T = 2S / T
// 2.任意时刻
s = v0 * t - 1/2 * a * t^2
  = (2S / T) * t - 1/2 * (2S / T^2) * t^2
  = 2S * (t / T) - S * (t^2 / T^2)
  = 2S * p - S * p^2
  = S * p * (2 - p)

3. 曲線運動

単純な曲線運動は直線運動に分解できます。例えば、正弦関数 y = sinx は以下のように分解できます:

// x轴匀速直线
x = S * p = 2PI * p
// y轴sinx
y = sinx = sin(2PI * p)

水平投射運動は以下のように分解できます:

// x轴匀速直线
x = S * p = X * p
// y轴匀加速
y = S * p^2 = Y * p^2

放物線の左半分は、左方向への水平投射の逆運動とみなすことができます:

// x轴匀速直线
x = S * p = X * p
// y轴匀减速
y = S * p * (2 - p) = Y * p * (2 - p)

円運動は少し特殊です。代数方程式 (x - a)^2 + (y - b)^2 = r^2 では、x, y の計算で符号の問題が発生します(面倒ですが可能です)。そのため、媒介変数表示(パラメータ方程式)を検討します:

// 对于圆上任意一点,有
sinθ = y / r, cosθ = x / r
// 得参数方程
x = a + r * cosθ, y = b + r * sinθ

// 圆心为(0, 0)时
x = r * cosθ = r * cos(θ * p), y = r * sinθ = r * sin(θ * p)

角度 [0, 2PI] が均一に変化し、それに応じて x, y が変化します。

P.S. 円運動を極座標で説明するのは少し強引かもしれません(r(θ) = r とし、中心を設定して [0, 360] 度を均一に rotate させる場合、transform がなかった時代はどうやって位置を計算していたのでしょうか?)。

4. イージング関数

上記で得られた公式を比較します:

s = S * p               // 匀速
s = S * p^2             // 匀加速
s = S * p * (2 - p)     // 匀减速
s = S * cos(2PI * p)    // cos
s = S * sin(2PI * p)    // sin

総変位 S は変わらず、後ろの部分が異なっていることがわかります。したがって:

var easings = {
    linear: function(p) { return p; },
    acceleration: function(p) { return p * p; },
    deceleration: function(p) { return p * (2 - p); },
    sin: function(p) { return Math.sin(2 * Math.PI * p); },
    cos: function(p) { return Math.cos(2 * Math.PI * p); }
}

これらの easing 関数は p を補正するために使用されるため、アニメーションは以下のようになります:

// 任意时刻t对应的位移
st = s0 + S * easing(p)
// 即
// 当前值 = 初始值 + totalDelta * easing函数修正后的完成度

p = t / T であるため、実際には easingt に作用します。これは時間制御関数timingFunction)とも呼ばれます。

アニメーションライブラリはすべてこのように動作します。例えば jQuery

// from https://github.com/jquery/jquery/blob/2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce/src/effects/Tween.js
jQuery.easing = {
    linear: function( p ) {
        return p;
    },
    swing: function( p ) {
        return 0.5 - Math.cos( p * Math.PI ) / 2;
    },
    _default: "swing"
};

velocity

// from https://github.com/ayqy/velocity-1.4.1/blob/master/velocity.js
Velocity.Easings = {
    linear: function(p) {
    // 线性,直接返回完成度
        return p;
    },
    swing: function(p) {
    // 两头慢中间快,cos从+1到-1变化,中间斜率最大变化最快
        return 0.5 - Math.cos(p * Math.PI) / 2;
    },
    /* Bonus "spring" easing, which is a less exaggerated version of easeInOutElastic. */
    spring: function(p) {
    // easeInOutElastic的温和版
        return 1 - (Math.cos(p * 4.5 * Math.PI) * Math.exp(-p * 6));
    }
};

摩擦力(spring)や重力(bounce)などの物理効果を伴う他の複雑な easing、一般的な時間制御 easing シリーズ(各種ベジェ曲線に対応するイージング関数)、step 効果も同じ原理です。進捗度を補正すること、つまりいわゆる速度制御です。

5. オンラインデモ

velocity でカスタム easingRedirects を使用して、これらの曲線軌道を実現します。例えば:

// 自定义缓动函数
// 匀加速
Velocity.Easings.acceleration = function (p, opts, tweenDelta) {
    return p * p;
};
// 匀减速
Velocity.Easings.deceleration = function (p, opts, tweenDelta) {
    return p * (2 - p);
};

// 自定义动画效果
Velocity.Redirects['throw-h'] = function(element, options, elementsIndex, elementsSize, elements, promiseData) {
    Velocity(this, {
        translateX: [300, 'linear', 0],
        translateY: [300, 'acceleration', 0]
    }, options);
};

// run
Velocity(document.body, 'throw-h', 3000);

詳細はデモを参照:http://ayqy.net/temp/curve-path-animation.html

デモの過程で、 velocity は進捗度が 1 になったときに終点の値を強制的に代入することがわかりました。これは sin のようなケースでは問題が発生します。ソースコードは以下の通りです:

else if (percentComplete === 1) {
// 已完成,手动赋值,确保终点准确(不受计算精度影响)
//!!! 不应该手动赋值终点了,因为sin之类的,终点是0
//!!! 强制赋值就错了
    /* If this is the last tick pass (if we've reached 100% completion for this tween),
     ensure that currentValue is explicitly set to its target endValue so that it's not subjected to any rounding. */
    // currentValue = tween.endValue;
    currentValue = tween.currentValue;
}

修正方法は、 easing 関数を信頼することです(上記のコード部分を削除します)。終点においても easing によって現在の値を計算します。この方法の欠点は計算精度の問題があること(例えば sin2PI は 0 ですが、計算結果が 0 ではなく極小値になるなど)ですが、実用上の影響はほとんどありません。

参考文献

コメント

コメントはまだありません

コメントを書く