js动画如何运作?

时间:2011-07-14 18:35:48

标签: javascript animation frame

我试图了解如何使javascript动画顺利运行,我一直在这里阅读一些答案,我发现了一些我不理解的东西。

以下是问题Smooth javascript animation

的链接

在大多数选票的答案中,它说“这就是为什么通常将位置/帧基于自动画开始以来经过的时间量(使用新的Date()。getTime())的好主意。而不是每帧移动/更改固定数量。“

有人能告诉我一个非常简单的例子,它使用了这个答案的方法,然后解释你如何控制动画的速度?

6 个答案:

答案 0 :(得分:3)

概述

通常使用动画,您有三个组件

  1. 更新
  2. 绘制
  3. 定时器
  4. 这些在循环中运行,称为动画循环。典型的动画循环可能如下所示(我将在下面详细解释所有函数):

    function animate() {
        update(); // Executes all game logic and updates your world
    
        draw(); // Draws all of the animated elements onto your drawing context
    
        timer(); // Controls the timing of when animate will be called again
    };
    
    animate(); // Start animating
    

    动画循环是动画内容的主要流控制器。基本上,一遍又一遍地调用动画循环中的代码。动画功能的每次执行构成一个帧。在一个框架中,您的世界将在屏幕上更新并重新绘制。动画函数运行的频率称为帧速率,由计时器控制。

    您还需要一个对绘图上下文的引用,该上下文将用于保存您希望设置动画的元素,也称为精灵:

    // Create a variable with a reference to our drawing context
    // Note this is not the same as using a canvas element
    var canvas = document.getElementById("canvas");
    

    更新

    更新负责每帧更新一次您希望设置动画的项目的状态。举一个简单的例子,假设你有一个包含三辆汽车的阵列,每辆汽车都有一个x和y位置以及一个速度。在每个框架上,您需要更新汽车的位置以反映其应根据速度行进的距离。

    我们的汽车阵列,用于生成汽车的代码可能如下所示:

    // A method to create new cars
    var Car = new Car(x, y, vx, vy) {
        this.className = "car"; // The CSS class name we want to give the car
        this.x = x || 0; // The x position of the car
        this.y = y || 0; // The y position of the car
        this.vx = vx || 0; // the x component of the car's velocity
        this.vy = vy || 0 // the y component of the car's velocity
    };
    
    // A function that can be called to update the position of the car on each frame
    Car.prototype.drive = function () {
        this.x += this.vx;
        this.y += this.vy;
    
        // Return an html string that represents our car sprite, with correct x and y 
        // positions
        return "<div class='" 
                + this.className 
                + "' style='left:" 
                + this.x 
                + "px; top:"
                + this.y
                + "px;'></div>";
    };
    
    // Create a variable to hold our cars
    var cars = [
        new Car(10, 10, 5, 3),
        new Car(50, 22, 1, 0),
        new Car(9, 33, 20, 10)
    ];
    

    当我们调用更新时,我们将要调用每辆车的驱动方法,以便在屏幕上移动汽车精灵。此驱动方法将返回表示精灵的html字符串,包括它的当前位置。我们希望将此字符串附加到一个变量,该变量可用于设置canvas div的内部HTML:

    // An empty string that will be used to contain the innerHTML for our canvas
    var htmlStr = "";
    
    // Update the position of each car
    function update() {
        // Clear the canvas content
        htmlStr = "";
    
        for (var i = 0, len = cars.length; i < len; i++) {
            // Add the car sprite to the html string to be rendered
            htmlStr += cars[i].drive();
        }
    };
    

    绘制

    当更新函数输出我们的精灵时,我们需要在画布上实际绘制元素。为了做到这一点,我们使用canvas变量的innerHTML方法,将其传递给htmlStr,其中包含用于表示所有sprite的标记。这将要求浏览器解析文本并在屏幕上吐出DOM元素:

    function draw() {
        // Parse the text containing our sprites and render them on the DOM tree
        canvas.innerHTML = htmlStr;
    };
    

    你可以说有更好的方法可以做到这一点,而且可能有。但是,为了保持简单,我保持简单。这也不是最高效的方法,因为DOM API非常慢。如果您查看此应用程序的系统资源使用情况,其中大部分将被innerHTML调用占用。如果您想要更快的绘图上下文,则应使用HTML 5画布。

    定时器

    最后,你需要一些反复调用动画的方法。你可以通过几种方式做到这一点。首先,有一个很好的旧setTimeout:

    setTimeout(animate, 0);
    

    这指示浏览器在延迟0ms后调用动画。在实践中,动画将在延迟0ms后永远不会执行。大多数浏览器中setTimeout的最小分辨率大约为15ms,但由于UI线程在javascript中的工作方式,即使这样也无法保证。

    更好的解决方案是使用requestAnimationFrame,它基本上告诉浏览器你正在做动画。浏览器将为您做一堆很好的优化。但是,浏览器并不完全支持此功能。您应该将此解决方案用于跨浏览器的requestAnimationFrame polyfill:

    http://paulirish.com/2011/requestanimationframe-for-smart-animating/

    然后你可以使用:

    requestAnimFrame(animate);
    

    最后,您完成的程序将如下所示:

    // shim layer with setTimeout fallback
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function(/* function */ callback, /* DOMElement */ element){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
    
    // Create a variable with a reference to our drawing context
    // Note this is not the same as using a canvas element
    var canvas = document.getElementById("canvas");
    
    // A method to create new cars
    var Car = new Car(x, y, vx, vy) {
        this.className = "car"; // The CSS class name we want to give the car
        this.x = x || 0; // The x position of the car
        this.y = y || 0; // The y position of the car
        this.vx = vx || 0; // the x component of the car's velocity
        this.vy = vy || 0 // the y component of the car's velocity
    };
    
    // A function that can be called to update the position of the car on each frame
    Car.prototype.drive = function () {
        this.x += this.vx;
        this.y += this.vy;
    
        // Return an html string that represents our car sprite, with correct x and y positions
        return "<div class='" 
                + this.className 
                + "' style='left:" 
                + this.x 
                + "px; top:"
                + this.y
                + "px;'></div>";
    };
    
    // Create a variable to hold our cars
    var cars = [
        new Car(10, 10, 5, 3),
        new Car(50, 22, 1, 0),
        new Car(9, 33, 20, 10)
    ];
    
    // An empty string that will be used to contain the innerHTML for our canvas
    var htmlStr = "";
    
    // Update the position of each car
    function update() {
        // Clear the canvas content
        htmlStr = "";
    
        for (var i = 0, len = cars.length; i < len; i++) {
            // Add the car sprite to the html string to be rendered
            htmlStr += cars[i].drive();
        }
    };
    
    function draw() {
        // Parse the text containing our sprites and render them on the DOM tree
        canvas.innerHTML = htmlStr;
    };
    
    function animate() {
        update(); // Executes all game logic and updates your world
    
        draw(); // Draws all of the animated elements onto your drawing context
    
        requestAnimFrame(animate); // Controls the timing of when animate will be called again
    };
    
    animate(); // Start animating
    

    结论

    我有很多优化,调整,抽象和其他很多优点,我没有进入这里。有很多方法可以实现更新和绘制,并且都有优点和缺点。

    然而,您将要编写的每个动画都将使用某种动画循环,它们都具有这种基本架构。希望这能说明javascript中动画的基础知识。

答案 1 :(得分:0)

这篇文章描述了制作动画的最佳方式:http://paulirish.com/2011/requestanimationframe-for-smart-animating/

那将以60fps或尽可能快的速度运行。

一旦你知道了,那么你可以决定你想要动画拍摄的时间以及物体移动的距离,然后计算出每帧移动的距离。

当然,对于流畅的动画,你应该尽可能使用CSS Transitions - 看看http://css3.bradshawenterprises.com

答案 2 :(得分:0)

快速回答是setTimeout并不能保证在您调用回调后 n 毫秒执行回调。它只是保证它会在 n 毫秒之后执行。 John Resig在this piece中介绍了其中的一部分内容。

出于这个原因,您需要检查动画回调实际执行的时间,而不是您使用setTimeout计划它的时间。

答案 3 :(得分:0)

我写了一篇博客文章,演示了使用画布和精灵表的“基于时间”动画的概念。

该示例为精灵设置动画并考虑经过的时间,以确保它在不同的帧速率下保持一致。通过稍微更改代码,您可以轻松地将其应用于在页面周围移动元素。与时间和动画本身相关的概念基本保持不变。

http://codeutopia.net/blog/2009/08/21/using-canvas-to-do-bitmap-sprite-animation-in-javascript/

(我总是觉得垃圾邮件留下的答案基本上只是链接到我的博客,即使链接实际上完全相关:D)

答案 4 :(得分:0)

以这种方式看待它:你有一些想要移动的物体。假设你给它5秒钟从A移动到B,你希望它以30fps的速度完成。这意味着您必须更新对象的位置150次,并且每次更新最多为1/30 = 0.0333秒。

如果您正在使用“自上次调整后的时间”,并且您的代码需要0.05秒才能执行,那么您将不会使用30fps。但是,如果你在动画开始的时候关闭它,那么你的代码需要多长时间并不重要 - 当你的更新代码触发时,它会计算并设置对象的位置应该在动物的那个阶段,无论实际显示了多少帧。

答案 5 :(得分:0)

基本思路如下:

  • 您希望在1秒内将DIV从0,0移动到100,0。
  • 你希望它每秒有50帧(使用setTimeout(有趣,20))
  • 但是,由于回调不能保证在20毫秒内运行,因此您的回调需要确定实际运行的时间。例如,假设您的动画在时间X开始,但是直到X + 5ms才会调用您的第一个动画回调。你需要计算div应该在动画的25ms位置,你的动画回调不能假设50步。

这是一个非常简单的代码示例。我通常不用全局变量编码,但这是展示技术的最简单方法。

http://jsfiddle.net/MWWm6/2/

var duration = 1000; // in ms
var startTime; // in ms
var startX = 0;
var endX = 500;
// 0 means try to get as many frames as possible
// > 1: Remember that this is not guaranteed to run as often as requested
var refreshInterval = 0;
var div = document.getElementById('anim');

function updatePosition() {
    var now = (new Date()).getTime();
    var msSinceStart = now - startTime;
    var percentageOfProgress = msSinceStart / duration;
    var newX = (endX - startX) * percentageOfProgress;
    div.style.left = Math.min(newX, endX) + "px";
    if (window.console) {
        console.log('Animation Frame - percentageOfProgress: ' + percentageOfProgress + ' newX = ' + newX);
    }
    if (newX < endX) {
        scheduleRepaint();
    }
}

function scheduleRepaint() {
    setTimeout(updatePosition, refreshInterval);
}

div.onclick = function() {
    startTime = (new Date()).getTime();
    scheduleRepaint();
}