高中物理學運動公式實現js動畫

高中物理學運動公式實現js動畫,在網頁上創建動畫一般有兩種方式:css和javascript。它們在創建動畫的時間和性能上是不一樣的,各有利弊。選擇哪種方法實際上取決於項目,以及想要實現什麼類型的動畫。

一般使用css動畫來實現比較簡單的“一次性轉換”,為UI元素轉換比較小的獨立狀態。例如從側面引入導航欄菜單,模太框彈出等。
要實現高級效果時,例如彈跳,加速,減速等比較復雜的動畫,則使用Javascript動畫。現在有很多比較好的JS動畫框架,例如TweenMax,Velocity,animo.js,jquery。

不管是css還是javascript來創建動畫,我們都會聽到一個詞“緩動”。自然界中沒有東西從一點呈線性的移動到另一點,一般可能需要加速或減速。在經典動畫中,經常會出現“緩入”,“緩出”,“緩入緩出”效果。緩動使動畫不再那麼尖銳或生硬。
css中,我們要想達到這些效果,隻需要使用一些關鍵字:
* linear
* ease-in
* ease-out
* ease-in-out

那這麼關鍵字的背後到底是什麼原理呢?如何用javascript來實現這些緩動效果。

動畫是關於時間的函數,本質就是利用瀏覽器和GPU的渲染過程定時改變元素的屬性。

使用javascript實現動畫時一般是使用requestAnimationFrame,我們可能經常也會用setInterval和setTimeout來實現動畫,但是它們實現的動畫都不會與屏幕的刷新率同步,並且很可能出現抖動和跳幀,例如jQuery就是采用setInterval來實現動畫,所以jQuery動畫的幀率會偏低(jQuery為甚麼不采用RAF)。

“Talk is cheap, show me the code”。下面學習實現下月影總結的動畫原理。原理簡單,實現經典。

常見運動

勻速運動

讓小球在2s內向右勻速移動200px

時間: t = T * p 位移: St = S * p = v * t 速度: v = St / t = S / T 加速度: a = 0

circle.on('click', function() {
    var self = this;
    var startTime = Date.now();
    var distance = 200;
    var T = 2000;
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() - startTime) / T);
        self.style.transform = 'translateX(' + (distance * p) + 'px)';
        if(p < 1.0){
            requestAnimationFrame(step);
        }
    })
})

勻速運動

2.勻加速運動

讓小球在2s內向右勻加速向右移動200px, 速度從0開始
* 時間: t = T * p
* 位移: St = S * p^2 = (S * t^2) / T^2
* 速度: v = (2*S / T^2) * t = 2Sp/T
* 加速度 a = 2*S / T^2

circle.on('click', function() {
        var self = this;
        var startTime = Date.now();
        var distance = 200;
        var T = 2000;
        requestAnimationFrame(function step() {
            var p = Math.min(1.0, (Date.now() - startTime) / T);
            self.style.transform = 'translateX(' + (distance * p * p) + 'px)';
            if(p < 1.0){
                requestAnimationFrame(step);
            }
        })
    })

勻加速

3.勻減速運動

讓小球在2s內向右勻減速向右移動200px, 速度從最大減為0
* 時間: t = T * p
* 位移: St = (2*S / T) * t – (S / T^2) * t^2 = Sp * (2 – p)
* 速度: v = 2*S / T – 2*S / t^2 * t
* 加速度 a = -2*S / T^2vcD4NCjxwcmUgY2xhc3M9″brush:sql;”>
circle.on('click', function() {
var self = this;
var startTime = Date.now();
var distance = 200;
var T = 2000;
requestAnimationFrame(function step() {
var p = Math.min(1.0, (Date.now() – startTime) / T);
self.style.transform = 'translateX(' + (distance * p * (2 – p)) + 'px)';
if(p < 1.0){
requestAnimationFrame(step);
}
})
})

勻減速

4.拋物線運動

circle.on('click', function() {
    var self = this;
    var startTime = Date.now();
    var disX = 200, disY = 200;
    var T = 1000 * Math.sqrt(2 * disY / 98);
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() - startTime) / T);

        var tx = disX * p;
        var ty = disY * p * p;
        self.style.transform = 'translate(' + tx + 'px, ' +ty +  'px)';
        if(p < 1.0){
            requestAnimationFrame(step);
        }
    })

拋物線

5.簡諧擺動

 circle.on('click', function() {
    var self = this;
    var startTime = Date.now();
    var distance = 100;
    var T = 2000;
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() - startTime) / T);
        var tx = distance * Math.sin(2 * Math.PI * p);
        self.style.transform = 'translateX(' + tx + 'px)';
        if(p < 1.0){
            requestAnimationFrame(step);
        }
    })
})

擺動

6.正弦線

circle.on('click', function() {
    var self = this;
    var startTime = Date.now();
    var distance = 100;
    var T = 2000;
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() - startTime) / T);
        var ty = distance * Math.sin(2 * Math.PI * p);
        var tx = 2 * distance * p;
        self.style.transform = 'translate(' + tx + 'px,'+ ty +'px)';
        if(p < 1.0){
            requestAnimationFrame(step);
        }
    })
})

正弦

7.圓周運動

circle.on('click', function() {
    var self = this;
    var startTime = Date.now();
    var distance = 100;
    var T = 2000;
    var r = 100;
    requestAnimationFrame(function step() {
        var p = Math.min(1.0, (Date.now() - startTime) / T);
        var rotation = p * 360;
        self.style.transformOrigin = 'r' + 'px ' + r + 'px';
        self.style.transform = 'rotate(' + rotation +'deg)';
        if(p < 1.0){
            requestAnimationFrame(step);
        }
    })

圓周運動

動畫封裝

可以將動畫封裝成通用的對象,每次隻需要實例定義單幀執行函數就可以瞭。

參數:duration-動畫持續時間,progress每一幀執行的函數,easing-緩動效果(可傳可不傳)
function Animator(duration, progress, easing) {
    this.duration = duration;
    this.progress = progress;
    this.easing = easing || function(p){return p};
}
//調用start函數時可傳入參數,如果參數為false或者傳入的函數返回false,那麼就說明進行循環動畫,相當於將css animation中的animation-iteration-count:infinite
Animator.prototype.start = function(finished) {
    var self = this;
    var startTime = Date.now();
    var duration = self.duration;

    requestAnimationFrame(function step(){
        var p = (Date.now() - startTime) / duration;
        var next = true;
        if(p < 1.0) {
            self.progress(self.easing(p), p);
        }else { //第一次動畫之行完後開始執行回調函數
            if(typeof finished === 'function') {
                next = finished() === false;
            }else {
                next = finished === false;
            }
          //如果調用start時傳遞的參數為false或函數返回false,next便為true說明進行循環運動
            if(!next) {
                self.progress(self.easing(1.0), 1.0);
            }else {
                startTime += duration;  //如果這裡不加的話那麼小球不會循環,而是一直向前運動
                self.progress(self.easing(p), p);
            }
        }
       //next為true,動畫繼續執行
        if(next) {
            requestAnimationFrame(step);
        }
    })
}

目前這個函數存在的缺點就是雖然可以設置動畫執行為一次或一直循環,但是並不能設置動畫執行次數為2,3,4..,也不能設置動畫循環的方向,即不能像css animation設置animation-direction

折線運動
讓小球先向右運動再向下運動

var a1 = new Animator(1000, function(p) {
    var tx = 100 * p;
    circle.style.transform = 'translateX(' + tx + 'px)';     
})
var a2 = new Animator(1000, function(p) {
    var ty = 100 * p;
    circle.style.transform = 'translate(100px,' + ty + 'px)';   
})
a1.start(function(){
   a2.start();
})

折線運動

動畫隊列

為瞭能使多個動畫順序執行,需要用數組來模擬一個隊列管理動畫執行順序。

function AnimationQueue(animators) {
    this.animators = animators || [];
}
AnimationQueue.prototype = {
    append: function() {
        var args = [].slice.call(arguments);
        this.animators.push.apply(this.animators, args);
    },
    flush: function() {
        if(this.animators.length) {
            var self = this;
            function play() {
                var animator = self.animators.shift();
                        //如果數組中的對象是Animator的實例,則直接調用
                if(animator instanceof Animator) {
                    animator.start(function() {
                    if(self.animators.length) {
                            play();
                        }
                    })
                }else { //數組中的對象不是Animator的實例,調用Animator的方法,可以在這裡將之前的對象再次添加到animators數組中
                    animator.apply(self);//通過這裡可以進行循環運動
                    if(self.animators.length) {
                        play();
                    }
                }
            }
            play();
        }
    }
}

彈跳的小球
設20px為1米,下落距離為200px, 為10米 g = 10m/s S = 1/2 * g * T^2 = 10 T = 1.414s = 1414ms 下落階段 St = S * p^2 上升階段 St = S – Sp * (2 – p)

var c1 = new Animator(1000, function(p) {
    var ty = 200 * p * p;
    circle.style.transform = 'translateY(' + ty + 'px)';
})
var c2 = new Animator(1414, function(p) {
    var ty = 200 - 200 * p * (2 - p);
    circle.style.transform = 'translateY(' + ty + 'px)';
})
circle.addEventListener('click', function() {
    var animators = new AnimationQueue();
    animators.append(c1, c2, function execute() {
        this.append(c1,c2, execute);
    });
    animators.flush();
})

彈跳

彈跳幅度逐漸減小的小球
上升時間:T = 0.7 * T 上升距離:S = 0.49 * S

circle.addEventListener('click', function() {
    var T = 1414;
    var a1 = new Animator(T, function(p) {
        var s = this.duration * 200 / T;
        var ty = s * (p * p - 1);
        circle.style.transform = 'translateY(' 
      + ty + 'px)';     
    })
    var a2 = new Animator(T, function(p) {
        var s = this.duration * 200 / T;
        var ty = - s * p * (2 - p);
        circle.style.transform = 'translateY(' 
      + ty + 'px)'; 
    })
    var animators = new AnimationQueue();
    function foo() {
        a2.duration *= 0.7;
        if(a2.duration <= 0.0001) {
            animators.animators.length = 0;
        }
    }
    animators.append(a1, foo, a2, function b() {
        a1.duration *= 0.7;
        this.append(a1, foo, a2, b);
    });
    animators.flush();
})

彈跳幅度減小的小球

滾動的小球
小球直徑:d = 50px 圓周長: l = π * d; 周期: T = 2s 滾動時間: t = 4s 滾動距離:S = π * d * t / T = 314px

circle3.addEventListener('click', function() {
    var a1 = new Animator(4000, function(p) {
        var rotation = 'rotate(' + 720 * p + 'deg)';
        var x = 50 + 314 * p + 'px';
        circle3.style.transform = rotation;
        circle3.style.left = x;
    });
    var animators = new AnimationQueue();
    animators.append(a1, function b() {
        animators.append(a1, b);
    })
    animators.flush();
})

滾動的小球

以上就是利用我們學過的常見物理公式實現的js動畫效果。當然還可以有更多復雜的效果,如何實現各種優美的動畫也是值得深入學習的。

發佈留言