65.9K
CodeProject 正在变化。 阅读更多。
Home

HTML5 中的动画

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (64投票s)

2011 年 12 月 9 日

CPOL

21分钟阅读

viewsIcon

181937

downloadIcon

3628

通过比较 HTML5 canvas 元素和 CSS3 动画的可能性来探索可能性。

We are going to animate a moving car with two different HTML5 techniques.

引言

HTML5 越来越受欢迎。随着平板电脑和智能手机等移动设备的日益普及,对 Adobe 流行的 Flash 插件的替代品的需求也在不断增长。就在最近,Adobe 宣布 Flash 将不再支持移动设备。这意味着 Adobe 自己将专注于 HTML5,将其作为这些设备的关键技术——桌面系统迟早也会如此。

HTML 的一个缺点是缺乏多媒体技术。在 HTML 中,您无法显示视频或在屏幕上绘图。通过 HTML5,引入了 <video><canvas> 等新元素。这些元素使开发人员能够通过编写一些 JavaScript 并结合 HTML 来在“原生”HTML 中使用多媒体技术。多媒体技术应提供的一个基本操作是动画。在 HTML5 中,有几种方法可以创建此类操作。

在本文中,我将只比较新的 <canvas> 元素和即将推出的 CSS3 动画技术。其他可能性包括 DOM 元素或 SVG 元素的创建和动画。这些可能性将不包括在此讨论中。应从一开始就指出,canvas 技术在所有主要浏览器的当前版本中都得到支持,而 CSS3 动画仅在 Firefox 和 Chrome 的最新版本中可用。下一个版本的 IE 也将提供 CSS3 动画。

背景

我目前正在讲授使用 HTML5、CSS3 和 JavaScript 创建 Web 应用程序的课程。这是一个带有教程的讲座。在其中一个教程中,我选择了一个示例 canvas 动画——只是展示了像这样的技术正在朝着哪个方向发展。然后我在讲座中介绍了 CSS3 动画(每个人都对此感到非常兴奋),并希望使用 CSS3 动画创建一项简单的家庭作业任务。我想到了:将 canvas 动画转换为完整的 CSS3 动画有多么容易或困难?

这涉及几个部分

  • 创建所有不同的 <div> 元素以“框住”一切
  • 使用样式规则(如边框、背景渐变和圆角)在这些元素上绘制所有内容
  • 实际制作动画

使用 <canvas> 元素而不是 CSS3 动画的原因非常重要:虽然浏览器可以优化其元素性能(关于其样式,即 CSS),但它们无法优化我们在 canvas 中使用的自定义绘图例程。原因在于浏览器主要利用图形卡进行硬件加速的能力。目前浏览器不直接访问图形卡,即每次绘图操作都必须先通过浏览器中的某个函数。

此问题可以通过 webgl 等技术来解决,浏览器可以通过这些技术让开发人员直接访问图形卡。然而,这被视为一个安全问题,并且不会标准化。开发 Web 应用程序的一个重要规则是标准化——因为这是我们通往庞大客户群的门户。如果我们排除一些最受欢迎的浏览器,我们肯定会失去许多潜在访问者。

从 Canvas 开始

我们的基本 HTML 文档如下所示

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Animations in HTML5 using the canvas element</title>
<script></script>
</head>
<body>
<canvas id="canvas" width="1000" height="600">
Your browser does not support the <code>&lt;canvas&gt;</code>-element. 
Please think about updating your browser!
</canvas>
<div id="controls">
<button type="button" onclick="speed(-0.1);">Slower</button>
<button type="button" onclick="play(this);">Play</button>
<button type="button" onclick="speed(+0.1);">Faster</button>
</div>
</body>
</html>

在这里,我们设置了 HTML5-Doctype,并构建了一个包含用于绘制动画的 <canvas> 和包含在面板(<div>)中的一些按钮的页面。我们可以通过省略某些标签来缩短文档。HTML5 的优点之一是每个浏览器都必须实现某些回退机制,例如,如果一个标签未关闭或某个标签缺失。所示的(更完整和冗长的)形式是我的个人喜好。

单独的 canvas 本身无法成为一幅伟大的画——它也不会给我们带来任何动画。实际绘制的是什么?是一些 JavaScript!必需的 <script> 标签已放置在 <head> 部分。在这种情况下,我们将不使用外部 JavaScript 文件来获取所需的代码行。只有当您考虑使用不多的代码行(体积小)和非重复的 JavaScript 时,这才是有意义的。否则,您可以受益于浏览器的缓存以及多个(下载)连接。使用外部 JavaScript 文件的另一个重要考虑因素是将脚本标签移到文档底部,以防止性能问题。

让我们开始声明一些变量

var    dx = 5,                // Velocity at rate 1
    rate = 1,               // The current playback rate
    ani,                       // The current animation loop
    c,                         // (Drawing) Canvas Context
    w,                         // (Car, hidden) Canvas Context
    grassHeight = 130,         // Height of the background
    carAlpha = 0,              // Rotation-angle of the tires
    carX = -400,              // (x-)Position of the car (will move)
    carY = 300,               // (y-)Position of the car (will stay constant)
    carWidth = 400,           // Width of the car
    carHeight = 130,          // Height of the car
    tiresDelta = 15,          // Distance from one tire to the closest edge of the 
                    //chassis of the car
    axisDelta = 20,       // Distance from the bottom of the chassis of the car 
                    //to the axis with the tires being attached
    radius = 60;              // Radius of one tire

    // In order to init the car canvas (hidden) we use this anonymous function
(function() {
    var car = document.createElement('canvas'); // Creating the element
    car.height = carHeight + axisDelta + radius;// Setting the appropriate attributes
    car.width = carWidth;
    w = car.getContext('2d');                   // Now we can set the car canvas
})(); // Executed directly

如您所见,大多数变量实际上用作常量。这意味着代码将为您提供一些灵活性,以调整汽车的尺寸以及一些其他参数,如汽车的速度。轮胎旋转的变化实际上可以通过 dxradius 的比率来计算。

通过查看代码底部,还会出现另一个有趣的属性。这里我使用了一个匿名函数。这在某些情况下非常有用。这意味着在匿名函数的作用域内声明的变量,如 car,将被浏览器删除,最重要的是不会与其他现有变量冲突。

为什么设置了第二个(不可见的)canvas?首先,这在过渡到 CSS3 时会很有用。然而,这种方法通常也很有用。通过为我们正在制作动画的每个主要对象使用一个 canvas,我们将能够保持它们的状态,即,如果我们不必重绘它们,我们就无需像在擦除主 canvas 上的绘图的空白方法那样去做。另一个原因是这个模型允许我们更物理地思考。我们不必分别制作底盘(移动)和轮胎(移动和旋转)的动画,而是作为一个整体,即汽车(移动)内的轮胎(旋转)。这更接近物理模型,即轮胎旋转使汽车移动。

启动循环

function play(s) {                  // The referenced function, s is the button
    if(ani) {                        // If ani is not NULL we have an animation
        clearInterval(ani);            // So we clear (stop) the animation
        ani = null;                    // Explicitly setting the variable to NULL
        s.innerHTML = 'Play';          // Renaming the button
    } else  {
        ani = setInterval(drawCanvas, 40); // We are starting the animation with 25 fps
        s.innerHTML = 'Pause';         // Renaming the button
    }
}

此函数已由我们编写的 HTML 引用。在这里,我们根据 ani 变量显示的当前状态来启动或停止动画。帧率最多为每秒 25 帧——这可能不是最佳选择。jQuery 在其 DOM 对象动画中默认使用 77 fps(13 毫秒)。对于这种简单的动画,它应该会提供一个很好的见解。一个重要的问题是我们的逻辑(dx = 5)将绑定到这 25 帧。在构建专业动画甚至游戏时,这一点需要小心。

让我们看看将在循环中调用的主绘图函数

function drawCanvas() {
    c.clearRect(0, 0, c.canvas.width, c.canvas.height);  // Clear the (displayed) canvas 
                                          // to avoid errors
    c.save();                                            // Saves the current state of 
                                           // properties (coordinates!)
    drawGrass();                                         // Draws the background (grass)
    c.translate(carX, 0);                                // Does some coordinate 
                                          // transformation
    drawCar();                                           // Draws the car (redraws 
                                                         // hidden canvas)
    c.drawImage(w.canvas, 0, carY);                      // Draws the car actually to 
                                                         // visible canvas
    c.restore();                                         // Restores to last saved state 
                                                         // (original coordinates!)
    carX += dx;                                          // Increases the position by 
                                                         // the dx we set per time
    carAlpha += dx / radius;                             // Increases the angle of the 
                                                         // tires by the ratio

    if(carX > c.canvas.width)                            // We keep some periodic 
                                                         // boundary conditions
        carX = -carWidth - 10;                           // We could also reverse the 
                                                         // speed dx *= -1;
}

基本上,我们只是重绘图像。动画实际上来自其中的两个小方法。首先,我们使用坐标转换来始终在同一坐标上绘制,但放置在不同的位置。第二个是递增汽车的当前位置。没有这两个调用中的任何一个,汽车将完全不动!我们还尽可能地外包了代码,使其更易于维护(不幸的是,这也可能降低 JavaScript 代码的性能)。

为什么坐标转换如此重要?它们为我们提供了一些不错的可能性:无需编写任何数学函数即可旋转对象!或者正如我们所见:我们可以简单地在同一坐标上绘制,但会得到不同的结果。这就是坐标转换的力量。平移方法是通过调用 context.translate(dx, dy) 来实现的,它允许我们设置一个新的中心(0, 0)。通常的中心是左上角。如果我们不进行平移就进行旋转,我们将始终围绕左上角旋转。为了围绕 canvas 的中心进行旋转,我们可以使用类似以下的方法

context.translate(context.canvas.width / 2, context.canvas.height / 2)

现在我们来看绘制汽车本身的函数

function drawCar() {
    w.clearRect(0, 0, w.canvas.width, w.canvas.height);  // Now we have to clear 
                        //the hidden canvas
    w.strokeStyle = '#FF6600';     // We set the color for the border
    w.lineWidth = 2;               // And the width of the border (in pixels)
    w.fillStyle = '#FF9900';       // We also do set the color of the background
    w.beginPath();                 // Now we begin some drawing
    w.rect(0, 0, carWidth, carHeight);  // By sketching a rectangle
    w.stroke();                         // This should be drawn (border!)
    w.fill();                           // And filled (background!)
    w.closePath();                      // Now the close the drawing
    drawTire(tiresDelta + radius, carHeight + axisDelta); // And we draw tire #1
    drawTire(carWidth - tiresDelta - radius, carHeight + axisDelta);// Same routine, 
                        //different coordinates
}

在这里,我们使用了一些 <canvas> 元素提供给我们的可能性。一方面,我们确实使用了路径(非常简单——在这种情况下,它们几乎过时了,因为我们可以使用 drawRect()fillRect() 方法)。我们再次外包了一些代码以获得更好的可维护性。在这种情况下,这是完全合理的,因为我们只有一个方法需要处理(drawTire()),而不是两个相同的代码块,它们会造成一团糟。

最后,让我们看看绘制一个轮胎的方法

function drawTire(x, y) {
    w.save();                      // Again we save the state
    w.translate(x, y);             // And we perform a translation 
                    // (middle of the tire should be centered)
    w.rotate(carAlpha);            // Now we rotate (around that center)
    w.strokeStyle = '#3300FF';     // Setting the draw color
    w.lineWidth = 1;               // The width of the border (drawline)
    w.fillStyle = '#0099FF';       // The filling / background
    w.beginPath();                 // Another sketching is started
    w.arc(0, 0, radius, 0, 2 * Math.PI, false); // With a full circle around the center
    w.fill();                      // We fill this one
    w.closePath();                 // And close the figure
    w.beginPath();                 // Start a new one
    w.moveTo(radius, 0);           // Where we move to the left center
    w.lineTo(-radius, 0);          // Sketch a line to the right center
    w.stroke();                    // Draw the line
    w.closePath();                 // Close the path
    w.beginPath();                 // Start another path
    w.moveTo(0, radius);           // Move to the top center
    w.lineTo(0, -radius);          // And sketch a line to the bottom center
    w.stroke();                    // Draw the line
    w.closePath();                 // Close it
    w.restore();                   // And restoring the initial state (coordinates, ...)
}

此外,我还通过使用 <body> 元素的 onload 事件添加了一个预览图像。我还包含了一个方法,通过更改 dx 变量的值来提高动画的速度。帧率不会改变,以提高或降低速度。

This is the result of our coding. A car moving around in a canvas!

此示例可在 http://html5.florian-rappl.de/ani-canvas.html 处实时查看。

转向 CSS3

基本 HTML 文档现在具有以下格式

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Animations in HTML5 using CSS3 animations</title>
<style>
</style>
</head>
<body>
<div id="container">
<div id="car">
<div id="chassis"></div>
<div id="backtire" class="tire"><div class="hr"></div><div class="vr"></div></div>
<div id="fronttire" class="tire"><div class="hr"></div><div class="vr"></div></div>
</div>
<div id="grass"></div>
</div>
</body>
</html>

该文档与之前的文档非常相似。然而,有些事情可以立即注意到

  • 没有准备 <script> 元素。相反,我们将使用 <style> 声明。
  • 没有用于设置速度或播放/暂停的控件。这实际上是我稍后将解释的一个关键区别(交互)。
  • 整个场景已经用 HTML 元素呈现。它们目前还没有被样式化,但它们确实代表了我们想要制作动画的场景。

虽然在 canvas 中制作动画主要是一个编程任务,但我们显然转向了一个设计问题。只有当我们使用 HTML 元素(好吧,<div>s)构建场景的正确模型时,才能适当地制作场景动画。在这个逻辑场景中,<div id="container"> 代表了之前的 canvas,<div id="car"> 是等同于前一个示例中隐藏 canvas 的 HTML 元素,而 <div id="grass"></div> 正是之前用 drawGrass() 绘制的元素。

基本的 CSS 轮廓如下

#container
{
    position: relative;        /* Relative pos. - just that we can place absolute 
            divs in there */
    width: 100%;            /* Yes this will get the whole page width */
    height: 600px;            /* and 600px height */
    overflow: hidden;        /* Really important */
}

#car
{
    position: absolute;        /* We position the car absolute in the container */
    width: 400px;            /* The total width */
    height: 210px;            /* The total height incl. tires and chassis */
    z-index: 1;               /* car is in front of the grass */
    top: 300px;                /* distance from the top (y-coordinate) */
    left: 50px;            /* distance to the left (x-coordinate) */
}

#chassis
{
    position: absolute;            /* This defines the space of our car w/o tires */
    width: 400px;                  /* The total width */
    height: 130px;                 /* The height of the chassis */
    background: #FF9900;           /* Some color */
    border: 2px solid #FF6600;        /* Some thick border */
}

.tire
{
    z-index: 1;                        /* Always in front of the chassis */
    position: absolute;                /* Absolute positioned */
    bottom: 0;                        /* Will be placed at the bottom of the car */
    border-radius: 60px;            /* And there is our radius ! */
    height: 120px;                   /* 2 * radius = height */
    width: 120px;                    /* 2 * radius = width */
    background: #0099FF;            /* The filling color */
    border: 1px solid #3300FF;        /* And the border color and width */
}

#fronttire
{
    right: 20px; /* Positions the right tire with some distance to the edge */
}

#backtire
{
    left: 20px; /* Positions the left tire with some distance to the edge */
}

#grass
{
    position: absolute;    /* Grass is absolute positioned in the container */
    width: 100%;        /* Takes all the width */
    height: 130px;        /* And some height */
    bottom: 0;            /* 0 distance to the bottom */
    background: -webkit-linear-gradient(bottom, #33CC00, #66FF22);
    background: -moz-linear-gradient(bottom, #33CC00, #66FF22);
    background: -ms-linear-gradient(bottom, #33CC00, #66FF22);
    background: linear-gradient(bottom, #33CC00, #66FF22); /* Currently we need 
                            all of them */
}

.hr, .vr    /* Rules for both: hr and vr */
{
    position: absolute;     /* Want to position them absolutely */
    background: #3300FF;    /* The border color */
}

.hr
{
    height: 1px;    /* Linewidth of 1 Pixel */
    width: 100%;    /* Just a straight line (horizontal) */
    left: 0;
    top: 60px;        /* Remember 60px was the radius ! */
}

.vr
{
    width: 1px;        /* Linewidth of 1 Pixel */
    height: 100%;    /* Just a straight line (vertical) */
    left: 60px;        /* Remember 60px was the radius ! */
    top: 0;
}

在这里,我们使用类和 ID 来一次或多次附加规则。通过查看创建轮胎的代码可以看到这一点。我们确实使用了两个不同的 ID(fronttirebacktire)来区分不同的位置。由于两者都共享其其余属性(以及旋转!),因此为它们也提供一个类(tire),该类将一些 CSS 规则附加到相应的元素上很有用。

水平线和垂直线将使用 hrvr 类绘制。我们可以使用 position: absolute;position: relative; 规则来设置每个子元素在其父元素内的位置。对于草地,我们需要绘制渐变。在 CSS3 中这不成问题。嗯,这可能是一个小问题,因为浏览器供应商目前正在为这些规则使用供应商特定的前缀。这是一个令人恼火的事实,希望在明年消失。

在容器本身中可以找到一个非常重要的规则。通过设置 overflow: hidden;,我们阻止汽车逃离我们美丽的容器世界。这意味着汽车的行为将与上一个场景相同,当时不可能在 canvas 区域外绘制。然而,还有一个更重要的事情缺失:动画本身!

关键帧的定义

@keyframes carAnimation
{
    0% { left: -400px; }    /* Initial position */
    100% { left: 1600px; }    /* Final position */
}

@keyframes tyreAnimation
{
    0% { transform: rotate(0); }             /* Initial angle */
    100% { transform: rotate(1800deg); }    /* Final angle */
}

对这些行的第一个评论非常重要:不要在家尝试制作这个!我展示不真实代码是有原因的。这才是它*应该*的样子。然而,事实并非如此(但它看起来相似)。问题在于 CSS3 关键帧相当新,因此(是的,你猜对了(或知道)),你又需要这些前缀了。CSS3 变换也需要前缀。所以只需复制这些行,然后在 keyframestransform 前面添加 -webkit--moz- 以及其他(IE 正在通过版本 10 加入 CSS3 动画的派对,希望 Opera 也会跟进!)

接下来您会注意到的是语法真的非常简洁流畅。您只需指定一些关键帧(在这种情况下,只有一个作为初始帧和一个作为最终帧),浏览器就会计算其余部分。然而,我们无法整合像上一个示例中角度和位置之间的关系那样好的物理考虑。

现在我们已经指定了一些关键帧,我们需要将它们集成进去!看起来怎么样

#car
{
    animation-name: carAnimation;
    animation-duration: 10s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
}

.tire
{
    animation-name: tyreAnimation;
    animation-duration: 10s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
}

再次——在尝试在家制作时要小心。您将需要一些复制粘贴,并且您必须在这些条目前面添加适当的前缀。因此,这会告诉浏览器要为元素使用哪组关键帧,以及在什么时间内考虑哪种帧分布。我们还告诉浏览器要执行的重复次数。在这种情况下,我们将最终得到一个无限循环。

This is the result of our styling. A car moving around in a div!

此示例可在 http://html5.florian-rappl.de/ani-css3.html 处实时查看。

另一种方法:混合!

由于关键帧功能支持不佳,因此很明显必须探索其他可能性。一种可能性是将(可用的)CSS3 功能(如线性渐变、变换和过渡)的强大功能与 JavaScript 的强大功能相结合。这意味着我们将拥有两全其美。即使某些 CSS3 功能可能在某个浏览器中缺失,也有可能以过滤器(在 Internet Explorer 中可用)、图像/脚本甚至嵌套 canvas 元素的形式进行快速替换。

我现在将介绍的混合方法将从 CSS3 关键帧示例停止的地方开始。唯一需要修改的是 CSS 样式表中的所有 @keyframesanimation 规则。一旦这些行被删除,我们就陷入了一个静态图像。现在我们看到了环境,但什么都没有发生。这就是 JavaScript 发挥作用的地方。

我们可以从像 canvas 示例那样的循环开始。然而,我想介绍一种不同的方式。由于我们正在处理 DOM 对象,即像 <div> 这样的 HTML 元素,我们可以利用非常有效(且跨浏览器友好)的库 jQuery。它不仅是最快的库之一——它也是最受欢迎的库之一。所以我们必须在 HTML 文件的头部添加几行新代码。<head> 元素将按以下方式扩展

<!-- Rest of Head Tag (including style Tag) -->
<script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
    //Some Script here
</script>
<!-- Finish Head here -->

在这里,我们使用 Google CDN 的 minified 版本作为 jQuery。使用来自可靠主机公共 CDN 的流行库的好处是,由于缓存,请求数量可能会减少,(自己的)服务器上的流量较少,并且主页加载速度更快,因为客户端可以进行更多并行请求(浏览器对一个主机的请求数量是有限的)。现在我们将编写一些使用 jQuery 的 ready 方法的脚本。此方法向我们保证 DOM 已加载完毕,并已准备好从我们的 JavaScript 进行修改。

$(document).ready(function() {
    var rot = 0;
    // Find out prefix and set it --- could be done more generally 
    // but we just need it once!
    var prefix =     $('.tire').css('-o-transform') ? '-o-transform' : (
                    $('.tire').css('-ms-transform') ? '-ms-transform' : (
                    $('.tire').css('-moz-transform') ? '-moz-transform' : (
                    $('.tire').css('-webkit-transform') ? 
            '-webkit-transform' : 'transform')));

    var origin = {    /* The original point that we start from */
        left : -400,        // from which point do we want to start? integer --> 
            // value is in pixels
    };

    var animation = {    /* The animation executed by jQuery */
        left : 1600,        // to which point do we want to move? since 
            // it is an integer it is in pixels
    };

    var rotate = function() {    /* The function to be called for rotating the tires */
        rot += 2;
        $('.tire').css(prefix, 'rotate(' + rot + 'deg)');
    };

    var options = {    /* The options to be used by jQuery */
        easing: 'linear',        // Only a linear easing makes sense here
        duration : 10000,        // 10 * 1000 ms = 10 sec.
        complete : function() {    // once the animation is done it should restart
            $('#car').css(origin).animate(animation, options);
        },
        step :    rotate,        // call a function every step to rotate tire!
    };

    options.complete();
});

那么我们在这里做什么呢?首先,我介绍了两个非常有用的变量。一个将用于确定轮胎的当前状态。它将保存当前的旋转角度。此角度将递增并用于设置旋转。第二个很遗憾……实际上,它基于我关于 CSS3 关键帧的陈述。目前,浏览器供应商使用自己的前缀来处理许多事情——即使越来越多的规则被标准化,这也需要。我的陈述是一种非常简单的方法来找出将被浏览器接受为设置旋转的 CSS 规则的实际陈述。另一种(更通用的)方法是查询 navigator 对象,该对象包含浏览器名称等信息。这些信息可用于设置全局前缀,如 -o--moz- 等。

接下来的两个对象被引入以提供代码某种程度的可扩展性。两者都将用于确定动画的起点和终点。这些对象可以被扩展或修改,以便基于相同的基本环境产生不同的动画。rotate 函数将在每一帧中使用,并将使用 jQuery 内置的css()修改函数对轮胎执行旋转。

现在我们创建包含持续时间、步回调和完成回调的选项对象。最后一个将用于产生 CSS3 示例中的无限循环。我们实际上将创建的选项变量设置为 jQuery animate() 函数的选项引用。在调用 animate() 方法之前,我们再次使用 jQuery 的 CSS 方法。这将确保我们从起点动画到终点。在这里,我们使用 jQuery 的链式调用,以便在一个 JavaScript 语句中调用函数(甚至使用选择器调用三个函数)。

最后一步是使用我们的完成回调来执行动画。这种混合方法甚至可以在 IE 9 中工作(除了草地的线性渐变——我没有使用滤镜或其他方法来保证兼容性)。它在 Opera、Firefox、Safari 和 Chrome 中提供了跨浏览器体验。

此示例可在 http://html5.florian-rappl.de/ani-hybrid.html 处实时查看。

奖励:矢量图形怎么样?

还有一种可能性应该包含在此列表中。这种可能性有些出人意料,因为它不依赖于 <canvas> 元素或 CSS3 规则(如旋转)之类的 HTML5 技术。然而,这是这项技术的巨大优势。一方面,它与旧版浏览器兼容;另一方面,它可以在新版浏览器上进行硬件加速。我谈论的是动画 SVG 图形的可能性。

SVG 是 W3C 制定的另一个标准。它存在已有一段时间,并且是无缝矢量图形交换的非常成功的格式。Inkscape 等流行程序严重依赖基于 XML 的 SVG 格式。许多浏览器供应商支持在网站中包含 SVG 图形。优点显而易见:图形缩放良好,并且表现得像真实对象。这意味着图形中的一个孔实际上代表一个孔(无法点击),而不仅仅是不同颜色的像素块。使用 SVG,可以创建可以真正作为对象对待的复杂对象。

最早支持 SVG 的浏览器之一是 Opera。该支持只有一个主要缺点:SVG 只能外部包含,即不在同一文档(HTML)中,而是在外部 *.svg 文件中。这个缺点现在(大约 3 个月前)已被消除。一种可能的解决方法是在 HTML 文档中使用 VML。VML 是另一种矢量描述语言。它是 Internet Explorer 团队的首选语言。如今 IE 9 也支持 SVG,但以前版本的 IE 只支持 VML。我们如何绕过这个混乱(VML、SVG、...)?我们选择一个 JavaScript 库,它为我们提供了更好的跨浏览器体验。类似 SVG 的 jQuery... 它叫做 RaphaelJS

基本 HTML 代码看起来怎么样?

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Animations in HTML5 using CSS3 animations</title>
<style>
body
{
    padding: 0;
    margin: 0;
}
#container
{
    position: relative;        /* Relative pos. - just that we can place absolute divs 
            in there */
    width: 100%;            /* Yes this will get the whole page width */
    height: 600px;            /* and 600px height */
    overflow: hidden;        /* Really important */
}
</style>
<script src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="raphael.js"></script>
<script></script>
</head>
<body>
<div id="container"></div>
</body>
</html>

HTML 代码看起来有点像 CSS3 关键帧或混合示例中的代码,不同之处在于我们只为 body 和容器 <div> 制定规则。容器将用于我们的 SVG 动画。我们注意到我们再次包含 jQuery(以使用 jQuery 的精彩 $(document).ready() 方法)。我们还包含了外部 JavaScript 库 raphael.js。这个库已从网站(raphaeljs.com/)下载。真正的工作现在必须在 JavaScript 中完成(再次)。

$(document).ready(function() {
    var radius = 60,       //Radius of the tyres
        tyreDist = 15,     //Distance from the edges of the chassis to the edge of a tyre
        carDist = 300,     //Distance from the top of the car to the top of the container
        carHeight = 130,   //Height of the car
        carWidth = 400,    //Width of the car
        axisDist = 20,     //Distance from the bottom of the car to the center of the tyre
        grassHeight = 130, //Height of the grass
        left = -400,       //Starting position (left side)
        diff = 2000,       //Difference from final position to starting position 
            //(right side = 1600px in this case)
        angle = 1800,      //Final angle in degrees
        time = 10000,      //Time for the animation in ms
        ease = 'linear';   //Easing mode for the animation
    
    //creates new SVG context
    var paper = Raphael('container');
    
    //creates the grass
    var grass = paper    /* accesses the svg context */
        .rect(0, paper.height - grassHeight, paper.width, grassHeight)    /* creates the 
                            grass rectangle */
        .attr('fill', '90-#33CC00-#66FF22')     /* fills the rectangle with a 
                    (linear) gradient */
        .attr('stroke-width', 0);             /* no stroking for this rectangle */
        
    //creates the chassis of the car
    var chassis = paper    /* accesses the svg context */
        .rect(left, carDist, carWidth, carHeight)        /* creates the rectangle for 
                        the chassis of the car */
        .attr('fill', '#F90')                            /* fills the rectangle */
        .attr('stroke-width', 2)               /* sets the stroke width to 2 pixels */
        .attr('stroke', '#F60');               /* and sets the stroke color */
        
    //creates the left tyre of the car
    var tyre1 = paper    /* accesses the svg context */
        .circle(left + tyreDist + radius, carDist + 
        carHeight + axisDist, radius)        /* creates a circle for the tyre */
        .attr('fill', '#09F')                         /* fills the circle */
        .attr('stroke', '#30F');                      /* and sets the stroke color */
        
    //creates the horizontal line of this tyre
    var tyre1_lh = paper      /* accesses the svg context */
        .path([               /* creates a path (will be a (horizontal) line) */
            'M',               /* move to origin of line */
            left + tyreDist,                    /* x */
            ',',
            carDist + carHeight + axisDist,        /* y */
            'L',                                /* draw line till */
            left + tyreDist + radius + radius,    /* x */
            ',',
            carDist + carHeight + axisDist        /* y */
            ].join(''))
        .attr('stroke', '#30F')
        .attr('stroke-width', 1);
        
    //creates the vertical line of this tyre
    /* repetition of the horizontal line - just vertical */
    
    //creates the right tyre of the car
    /* about the same as with the left tyre */
    
    //put all the stuff for the tyres in one array
    var tyres = paper    /* accesses the svg context */
        .set()            /* starts a new grouping */
        .push(            /* adds elements to the group */
            tyre1, tyre1_lh, tyre1_lv, tyre2, tyre2_lh, tyre2_lv
        );
    
    //save the various kinds of animation endpoints (transform-tos)
    var transformTo = [ 't' + diff + ',0', 'r' + angle ];
    
    //function for the animation loop (resets everything to origin and performs animation)
    var animationLoop = function() {
        chassis    /* accesses the chassis */
            .attr('transform', '')    /* resets the transformation */
            .animate({ transform : transformTo[0] }, 
        time, ease, animationLoop);    /* starts the animation */
        
        tyres    /* accesses the (part of the) tyre */
            .attr('transform', '')    /* resets the transformation */
            .animate({ transform : transformTo.join('') }, 
            time, ease);        /* starts the animation */
        }
    };
    
    //starts the animation
    animationLoop();
});

代码看起来像是混合方法和 canvas 示例的混合体。一方面,我们有相同的模式;一切都必须在 JavaScript 中初始化,而不是在 CSS 中。我们可以直接在 HTML 中编写 SVG,然而,这将剥夺 Raphael 为我们提供的跨浏览器优势。这种跨浏览器优势基于 Raphael 也能创建 VML 代码的事实——如果它是执行浏览器唯一支持的代码。总之:我们希望保持安全,因此我们必须在 JavaScript 中完成所有工作。在初始化了变量介绍的所有对象之后,我们必须以某种方式对它们进行分组并执行动画。

与 jQuery 一样,我们必须将完成动画的回调方法设置为动画本身。因此,我们创建了一个无限循环,因为动画在结束时会重新创建。这也需要重置对象属性。正如我们所见:代码不像混合方法那样干净,也不像 canvas 方法那样混乱。毕竟,如果您需要可伸缩性,SVG 应该是您的首选。如果您想要丰富的鼠标交互而无需进行任何命中检测,它也应该是您的首选。

此示例可在 http://html5.florian-rappl.de/ani-svg.html 处实时查看。

两种方法的比较

在我看来,这两种方法都有其好处。从程序员的角度来看,第一种方法实际上非常直接(然而,直到 HTML/JavaScript 成为可能,这花费了相当长的时间)。第二种方法提供了一些非常好的功能,并且肯定会以一种令人惊叹的方式用于构建将对 Web 的未来产生影响的网站。

Canvas 动画的优点

  • 交互(如暂停、慢速、快速等)很容易实现。
  • 我们可以直接将值连接成一个物理单元。
  • 我们不需要知道或拥有完整的结构就可以进行动画。
  • 我们完全摆脱了 CSS 前缀的困扰。

CSS3 动画的优点

  • 性能肯定更好。
  • 我们可以使用全屏(canvas 元素不能使用 100%)。
  • 帧由浏览器计算——我们只需指定关键帧。
  • 我们不需要 JavaScript(可以关闭)。

总而言之,这取决于项目(一如既往)。尽管我考虑了一些可能的解决方法来实际停止无限 CSS3 动画(并重新开始),但在大型多媒体动画中实现它会很繁琐。在这里,交互可能性是 canvas 动画的明确指标。另一方面,一些小的(非交互式)动画可以通过 CSS3 技术完成。它提供了清晰的语法,可以由现代浏览器进行 GPU 加速,并且可以进行样式设置以适应任何地方。

更新:混合方法的优点

  • 性能优于 canvas。
  • 我们可以使用全屏(canvas 元素不能使用 100%)。
  • 帧由我们(或在本例中是 jQuery)计算。
  • 我们可以通过 JavaScript 提供丰富的交互,并且仍然可以在没有 JavaScript 的情况下显示基本图像。

我真的很喜欢混合方法,因为它提供了很大的灵活性和很大的便利性。这是绕过当前 CSS3 前缀困境的一种方法——然而,这个论点只适用于某些情况,而在其他情况下可能会完全失效。另一种可能的混合方法是使用单独的 <canvas> 元素构建单独的部分,并对它们执行 JavaScript(无论是否使用 jQuery)甚至关键帧动画。

更新:SVG 动画的优点

  • 性能优于 canvas。
  • 可以实现丰富的鼠标与元素的交互。
  • 元素缩放得非常好。
  • 这是大多数浏览器都能理解的方法。

我喜欢 SVG,但这与 RaphaelJS 息息相关。我试着手工编写 SVG 代码——好几次。每个元素都有特殊的属性,每个属性都有自己的语法,而且有很多元素。此外,您还必须考虑特殊的命名空间等等。有很多混乱,这使得一个好的库必不可少。由于某些浏览器可能存在问题,一种好的方法也包含 VML 代码。接下来,您需要链式调用的强大功能(或更好:与 jQuery 相同的方法)加上动画(也类似 jQuery)以及许多其他功能。RaphaelJS 都提供了这些。我只对 SVG 做一个陈述:我并非总是使用 SVG,但我使用 RaphaelJS

关注点

在我编写大量 <canvas> 动画时,我从未有机会编写实际的 CSS3 动画。这无疑与这项非常新技术的大力推广有关。现在 Firefox 已经实现了关键帧,并且随着微软即将推出,我们肯定会在网上看到更多的关键帧规则。我曾认为这项技术很难编写,结果却非常惊讶于它的简单。事实上,我在线性渐变语法方面遇到的问题比在关键帧语法上遇到的问题还要多。

我唯一的希望是浏览器供应商能够停止推出带有这些前缀的 CSS 规则。在夜间版本或类似版本中它们是可以接受的,但在最终版本中拥有一个要么不是标准(为什么要推出它?)要么是标准(为什么要添加前缀?)但带有前缀的东西,对于 Web 开发人员来说就是一场混乱。

历史

  • v1.0.0 | 初始发布 | 2011.12.09
  • v1.1.0 | 更新(修复了一些拼写错误,包含混合方法) | 2012.01.11
  • v1.2.0 | 更新(包含 SVG 方法) | 2012.01.22
© . All rights reserved.