JavaScript动画工作原理之(完结篇)

标签: javascript 动画 工作 | 发表时间:2014-05-04 08:02 | 作者:tianxuezhi_s
出处:http://blog.csdn.net

原作者:Steven Riche

发布时间:2014年2月18日

原文链接:http://code.tutsplus.com/tutorials/javascript-animation-that-works-part-4-of-4--net-35263

翻译:子毅 --------- 将JavaScript进行到底

碎碎两句 

折腾了一个多月,杂七杂八的事情加在一起,简直糟透了。博客停了大概有一个月了,从今天起一切都是新的,做好自己就OK了

----------------------------------------------------------------------------------------------

在本系列的 第一篇文章中,我们介绍了用精灵在web上创建简单、跨浏览器的可交互动画。在 第二篇文章中,我们让动画工作起来,而在第三篇文章中,我们整理好我们的代码,并为它在web上使用做好准备


介绍


现在,在我们的最后一部分中,我们将通过建立事件处理程序,而不是在点击按钮时机器人做出响应,我们的机器人将在屏幕上跟随着鼠标而移动。在这个过程中,我们将讨论跨浏览器的代码,并且触摸屏也可用

假如你看一下我们 上一次的代码,你可以看到,尽管我们的代码运行得很好(并且同时有多个机器人),然而这里没有一个简单的方式来运行代码。

事件处理程序


事件处理程序是这样的命令,当特定的事件触发时,它告诉某些代码运行。例如,任何时候,你可以让一个用户点击有“my_div' id的‘div’时,'my_function()'执行。或者,当用户在‘my_other_div'上移动鼠标时,'my_other_function()'执行

理论上,这个一个相当简单且直观的主意。不幸的是,当你卷入 不同的浏览器,这将会有一点迷惑。理想情况下,每个浏览器都会以同样的方式在解释代码,而开发者将只写需要写一次代码就可以让每个用户看到相同的结果。在真实世界中,不同的浏览器可能会有完全不同的命名来做同一件事(*咳**咳* IE),所以有时候想要一段代码在所有的浏览器中都运行得一样,会让人觉得像是在放牧一群猫。最近,情况已经变得很好了,Chrome,Firefox, Safgari,Opera对代码的响应已经非常相似了,IE9和IE10已经变得比早期的版本更加符合标准。并且几乎没有人在使用IE7和IE6了。因此,我们代码将使得事件处理程序在现代浏览器和IE8种可工作

作为一方面的说明,这是一种使用一个强大JavaScript库的原因,比如jQuery。jQuery已经为你做好了所有的跨浏览器测试,因此,你只需要输入一个命令,jQuery会在幕后翻译它使得它在每个浏览器中都可用。此外,许多jQuery命令都比核心JavaScript更加直观、简单。

但是,由于我的固执,并且这是一次学习的机会,我们将继续在这条艰难的路上努力,只用JavaScript来做这一切,不依赖任何库

页面交互


我们第一步要决定到底将怎么和页面交互。当我在舞台区域移动鼠标时,我想要所有的机器人朝着鼠标移动的方向跑。当它们抵达鼠标或者鼠标正好在它们上面,我想要它们停止移动。假如鼠标放在它们身上,我想要它们跳起来。最后,当鼠标离开舞台,我想要它们停止跑动。我们将从绑定事件到RobotMaker函数内部开始。

stage.addEventListener('mousemove', stage_mousemove_listener, false);
robot.addEventListener('mouseover', robot_mouseover_listener, false);
stage.addEventListener('mouseout', stage_mouseout_listener, false);

因此,在上面的几行代码中,我们说过,无论什么时候用户在舞台(stage)元素上移动鼠标,将触发一个叫做stage_mousemove_listener()的函数(注意,在命令中,我们并没有包含参数)。相似地,当用户在robot元素上移动鼠标,将触发robot_mouseover_listener(),当用户从舞台上移开鼠标,触发stage_mouseout_listenr()

不幸的是,之前我们提到过,IE8及其低版本使用不同(但是相似)的命令来做相同的事,因此,我们需要进行测试,以便知道用户的浏览器将会理解哪条命令,并执行相应的方法。

if (stage.addEventListener){ // We will test to see if this command is available
  stage.addEventListener('mousemove', stage_mousemove_listener, false);
  robot.addEventListener('mouseover', robot_mouseover_listener, false);
  stage.addEventListener('mouseout', stage_mouseout_listener, false);
} else { // If not, we have to use IE commands
  stage.attachEvent('onmousemove', stage_mousemove_listener);
  robot.attachEvent('onmouseover', robot_mouseover_listener);
  stage.attachEvent('onmouseout', stage_mouseout_listener); 
}

你可能会注意到那些命令的格式非常相似,但是还是有一些主要的区别:一个叫做‘addEventListener',一个叫做'attachEvent',一个叫做'mousemove',然而另一个叫做'onmousemove'。一个需要第三个参数,而另个只用了两个。混淆它们之间的任何一个都会导致命令不执行。这一系列的事会使你有用脑袋撞墙的冲动。不幸的是,为了是具有跨浏览器的能力,这并不是我们需要额外编写的最后代码

监听函数


接下来,我们将编写监听函数。从编写用户在舞台上移动而触发的函数开始。正因为它是一个mousemove侦听器,当鼠标每次在舞台区域内移动时,都将触发它(这意味着在一秒钟内将会触发多次)这个函数需要将机器人的位置和鼠标的位置作比较,并使机器人见机行事。每次函数触发,它将检测机器人需要继续朝相同的方向跑动,还是变换为其他行为。因此,它需要像下面这样编写。


// Inside of RobotMaker
 
// We will need to introduce a few extra variables to track
var mouseX; // For tracking horizontal mouse position
var running_dir = ''; // For tracking if (and where) robot is currently running
var stageOffset; // For tracking the position of the stage
 
function stage_mousemove_listener(e){
   
  // Find the horizontal position of the mouse inside of the stage ...  
  // That position will be saved in 'mouseX'
   
  // Then we compare 'mouseX' to the robot, and decide if we need to run differently
  if (((robot.offsetLeft + (15 * run_speed)) < (mouseX - robot.offsetWidth)) && running_dir !== 'r' && (!jump_timer || jump_timer === undefined)){ 
    // If the mouse is in the stage and to the right of the robot, make run right, if not already
    running_dir = 'r';
    clearTimeout(run_timer);
    run_r(1, robot.offsetLeft);
  } else if ((mouseX < robot.offsetLeft - (15 * run_speed)) && running_dir !== 'l' && (!jump_timer || jump_timer === undefined)) {
    // If the mouse is in the stage and to the left of the robot, make run left, if not already
    running_dir = 'l';
    clearTimeout(run_timer);
    run_l(1, robot.offsetLeft);
  } else if ((robot.offsetLeft < mouseX) && ((robot.offsetLeft + robot.offsetWidth) > mouseX) && running_dir !== '' && (!jump_timer || jump_timer === undefined)) {
    // If the mouse is in the stage and over a robot, stop and clear running_dir
    running_dir = '';
    clearTimeout(run_timer);
    if (face_right){
      robot.style.backgroundPosition = "0px 0px";
    } else {
      robot.style.backgroundPosition = "0px -50px";
    }
  }
  // If none of the above is true, then we let our current behavior continue
}


在上面的函数中,一旦我们找到mouseX,我们就可以和机器人的位置作比较,如果需要的话,触发或停止不同的跑动函数。不幸的是,找出mouseX有一些棘手,因为鼠标位置是另一件不同浏览器表现不同的事。为避免找出mouseX而进行复杂冗长的解释,正如灵感来自优秀的 Quirksmode博客 (这是一个学习更多高级JavaScript技术的伟大源地):

function stage_mousemove_listener(e){
  var posX = 0;
  if (!e){
    var e = window.event;
  }
  
  if (e.pageX) {
    posX = e.pageX;
  } else if (e.clientX) {
    posX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
  }
  mouseX = posX - stageOffset.xpos; // 我们找到了mouseX!
}

我们有个叫做e的参数在函数中,尽管我们没有传递任何东西给它,但是这是一个事件侦听器,我们将自动拥有一个叫做e的变量,这个变量存储有和事件相关的信息,比如鼠标数据。但是不同浏览器存储的方式不同,我们不得不增加额外的一个步骤。

我们最终通过找到posX(鼠标在页面上的水平坐标)并用它减去stage左边距离页面的值(存储在stageOffset.xpos),进而找到mouseX,这给出了我们鼠标距离stage左边界的距离,并且我们可以用它直接和robot.offsetLeft作比较。根据布局,stage可能会位于不同的位置,我们同样需要找出stage精确的像素偏移,并将它存储在stageOffset中。幸运的是,我们可以使用一个巧妙的花招来找到元素的绝对偏移量,这个函数来自于 Vishal Astik的博客

// 在RobotMaker内
var x = 0;
var y = 0;
function find_stage_offset (el){
  x = el.offsetLeft;
  y = el.offsetTop;
  el = el.offsetParent;
     
  while(el !== null) {
    x = parseInt(x) + parseInt(el.offsetLeft);
    y = parseInt(y) + parseInt(el.offsetTop);
    el = el.offsetParent;
  }
 
  return {xpos: x, ypos: y};
}
var stageOffset = find_stage_offset(stage);

现在我们已经写好了mousemove侦听器,其他将会容易很多。对于机器人mouseover侦听器,我们只需要检测机器人是否在跳跃,如果不是,停止跑动,使之跳跃。

function robot_mouseover_listener(){
  if (!jump_timer || jump_timer === undefined){
    clearTimeout(run_timer);
    jmp(true, robot.offsetTop);
  }
}

mouseout侦听器同样也很简单。只需要重置一些我们用来跟踪robot的变量,假如机器人没有跳跃,则将机器人置为站立的精灵

function stage_mouseout_listener(){
  mouseX = undefined;
  running_dir = '';
  if (!jump_timer || jump_timer === undefined){
    clearTimeout(run_timer);
    if (face_right){
      robot.style.backgroundPosition = "0px 0px";
    } else {
      robot.style.backgroundPosition = "0px -50px";
    }
  }
}

动画函数


这一次,跑跳运动函数并没有改变很多。我们只是增添了一些跟踪变量running_dir,取出机器人将要撞击到强的声明(因为这是mouseout函数的冗余,并增加一些代码到跳跃函数,它用于再次检测,假如鼠标仍在stage内,当机器人在跳跃后落下,它是否需要开始跑动。下面是最终的代码(相当大):

function run_r(phase, left){
  face_right = true;
  running_dir = 'r';
  if ((left + (15 * run_speed)) < (mouseX - robot.offsetWidth)){ // if mouse is to the right, run
         
    left = left + (15 * run_speed);
    robot.style.left = left+"px";
    switch (phase){
      case 1:
        robot.style.backgroundPosition = "-40px 0px";
        run_timer = setTimeout(function(){run_r(2, left);}, 200);
        break;
      case 2:
        robot.style.backgroundPosition = "-80px 0px";
        run_timer = setTimeout(function(){run_r(3, left);}, 200);
        break;
      case 3:
        robot.style.backgroundPosition = "-120px 0px";
        run_timer = setTimeout(function(){run_r(4, left);}, 200);
        break;
      case 4:
        robot.style.backgroundPosition = "-80px 0px";
        run_timer = setTimeout(function(){run_r(1, left);}, 200);
        break;
    }
} else if ((left + (15 * run_speed)) < mouseX) { // if mouse if above, stop
    robot.style.backgroundPosition = "0px 0px";
    running_dir = '';
} else { // if mouse is to the left, run left
    running_dir = 'l';
    run_l(1, robot.offsetLeft);
  }
}
 
function run_l(phase, left){
  face_right = false;
  running_dir = 'l';
  if (mouseX < robot.offsetLeft - (15 * run_speed)){ // if mouse is to the left, run
     
    left = left - (15 * run_speed);
    robot.style.left = left+"px";
    switch (phase){
      case 1:
        robot.style.backgroundPosition = "-40px -50px";
        run_timer = setTimeout(function(){run_l(2, left);}, 200);
        break;
      case 2:
        robot.style.backgroundPosition = "-80px -50px";
        run_timer = setTimeout(function(){run_l(3, left);}, 200);
        break;
      case 3:
        robot.style.backgroundPosition = "-120px -50px";
        run_timer = setTimeout(function(){run_l(4, left);}, 200);
        break;
      case 4:
        robot.style.backgroundPosition = "-80px -50px";
        run_timer = setTimeout(function(){run_l(1, left);}, 200);
        break;
    }
} else if (mouseX < (robot.offsetLeft + robot.offsetWidth - (15 * run_speed))){ // if mouse overhead, stop
    robot.style.backgroundPosition = "0px -50px";
    running_dir = '';
} else { // if mouse is to the right, run right
    running_dir = 'r';
    run_r(1, robot.offsetLeft);
  }
}
                 
function jmp(up, top){
  running_dir = '';
  if (face_right){
    robot.style.backgroundPosition = "-160px 0px";
  } else {
    robot.style.backgroundPosition = "-160px -50px";
  }
 
  if (up && (robot.offsetTop > (20 * (1 / jump_height)))){
    top = top - (top * 0.1);
    robot.style.top = top+"px";
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else if (up) {
    up = false;
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else if (!up && (robot.offsetTop < 115)){
    top = top + (top * 0.1);
    robot.style.top = top+"px";
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else {
    robot.style.top = "120px";
    if (face_right){
      robot.style.backgroundPosition = "0px 0px";
    } else {
      robot.style.backgroundPosition = "0px -50px";
    }
     
    jump_timer = false;
    if (mouseX !== undefined){
      if (((robot.offsetLeft + (15 * run_speed)) < (mouseX - robot.offsetWidth)) && running_dir !== 'r'){ 
        // make run right, if not already
        running_dir = 'r';
        clearTimeout(run_timer);
        run_r(1, robot.offsetLeft);
      } else if ((mouseX < robot.offsetLeft - (15 * run_speed)) && running_dir !== 'l') {
        // make run left, if not already
        running_dir = 'l';
        clearTimeout(run_timer);
        run_l(1, robot.offsetLeft);
      }
    }
  }
}

现在我们完全重写了函数并且跨浏览器工作得很好……除非浏览器有触屏输入。我们仍需要向前进一步,使得我们的机器人可以在任何设备上跑动。因为触摸屏表现得有些不同,我们需要在事件侦听器上做一些额外的编码。

支持触摸屏


我们需要为触摸屏制定一些新规则:在stage上,屏幕被触摸到任何地方,机器人都会向那个点跑去,直到指尖离开。假如用户触摸机器人,机器人则跳起来。总之,我们需要为之前的函数添加一些额外的事件处理器,并且我们将以这样的方式来写代码:无论什么时候RobotMaster函数被调用,它都会自动运行。

(function (){
  if (stage.addEventListener){
    stage.addEventListener('touchstart', stage_mousemove_listener, false);
    stage.addEventListener('touchmove', stage_mousemove_listener, false);
    stage.addEventListener('touchend', stage_mouseout_listener, false);
         
    stage.addEventListener('mousemove', stage_mousemove_listener, false);
    robot.addEventListener('mouseover', robot_mouseover_listener, false);
    stage.addEventListener('mouseout', stage_mouseout_listener, false);
  } else {
    stage.attachEvent('onmousemove', stage_mousemove_listener);
    robot.attachEvent('onmouseover', robot_mouseover_listener);
    stage.attachEvent('onmouseout', stage_mouseout_listener);
  }
})();

我们不需要担心触摸事件在IE8中的格式,假如有任何设备不支持触摸触摸,它将忽略这些侦听器。现在,假如浏览器具有触摸功能,我们需要更新stage_mousemove_listener()函数使具有不同的表现,。

function stage_mousemove_listener(e){   
/*
 * First we check if this is a touch screen device (if it has e.touches)
 */
  if (e.touches){
    e.preventDefault(); // we want to cancel what the browser would usually do if touched there
    // If the touch was within the boundaries of the stage...
    if ((e.touches[0].pageX > stageOffset.xpos) 
    && (e.touches[0].pageX < (stageOffset.xpos + stage.offsetWidth))
    && (e.touches[0].pageY > stageOffset.ypos)
    && (e.touches[0].pageY < (stageOffset.ypos + stage.offsetHeight))){
      // we set the mouseX to equal the px location inside the stage
      mouseX = e.touches[0].pageX - stageOffset.xpos; 
    } else { // if the touch was outside the stage, we call the mouseout listener
      stage_mouseout_listener();
    }
     
    /*
     * If the touch is directly on the robot, then we stop the run timer and make the robot jump
     */
    if ((e.touches[0].pageX > robot.offsetLeft) && (e.touches[0].pageX < (robot.offsetLeft + robot.offsetWidth))
    && (e.touches[0].pageY > (stageOffset.ypos + stage.offsetHeight - robot.offsetHeight))
    && (e.touches[0].pageY < (stageOffset.ypos + stage.offsetHeight))
    && (!jump_timer || jump_timer === undefined)){
      clearTimeout(run_timer);
      jmp(true, robot.offsetTop);
    }
     
  } else { // Finding the mouseX for non-touch devices...
    // All of our non-touch device code here
  }
}

你可能注意到,在RobotMaker函数里,这里不再具有任何”门“,但是,因为我们调用的所有具有事件处理程序的代码都在RobotMaker里,所以我们不再需要它们。对于我们的stage和characters,需要为 触摸设备添加一点特别的CSS,使得在用户手指按住它们时,它们不会尝试剪切和粘贴任何图片。

#stage, .character {
  -webkit-user-select: none;
}

最后,我们声明所有的机器人在页面的底部,使用相同的格式,当页面加载时,事件处理器使得代码自动运行- 这个方法同样阻止了那些机器人对象成为全局变量,因此,在整个脚本中,我们唯一有的全局变量就是RobotMaker()函数

(function(){
  var j = RobotMaker(document.getElementById('j'), 1, 1);
  var j2 = RobotMaker(document.getElementById('j2'), .8, 5);
  var j3 = RobotMaker(document.getElementById('j3'), 1.1, .5);
  var j4 = RobotMaker(document.getElementById('j4'), .5, .75);
})();

请在glory中查看 最终的结果

总结


我强烈建议你学习 全部的代码(和所有的注释),同时你可以在这里 下载  四个  机器人 精灵

Happy animating!
作者:tianxuezhi_s 发表于2014-5-4 0:02:28 原文链接
阅读:121 评论:0 查看评论

相关 [javascript 动画 工作] 推荐:

JavaScript动画工作原理之(完结篇)

- - CSDN博客Web前端推荐文章
原作者:Steven Riche. 发布时间:2014年2月18日. 原文链接:http://code.tutsplus.com/tutorials/javascript-animation-that-works-part-4-of-4--net-35263. 翻译:子毅 --------- 将JavaScript进行到底.

10个css3/javascript动画插件/框架

- - ria之家--RIA三部曲:jquery、ext、flex
jquery的一个用的人比较多的动画插件,非常的易用,效果也非常全面. 应该是目前针对transform最为全面的动画库,特点支持3D动画特性:. 非常牛逼的动画效果,文档和demo都很详细,虽然用于实战的场景不多(基于canvas的动画),不过依旧推荐认真看下. 相当不错的小动画库,能够产生大部分css3动画效果,关键是API非常易于理解,比如下面的代码:.

JavaScript 工作线程实现方式

- zhibin - IBM developerWorks 中国 : Web development : Articles,Tutorials
随着 Ajax 应用的流行,浏览器所承担的职责也越来越多. 一些原来由服务器端执行的计算操作也被迁移到浏览器端来执行. 通过 JavaScript 工作线程,可以在不影响页面本身运行的情况下,在后台运行耗时的任务. 本文详细了 JavaScript 工作线程的三种实现方式:使用 setTimeout()、Google Gears 和 Web Worker.

9个javascript和gif动画解决方案

- 泽 - 我爱互联网
我开始学习制作loading动画是在我学习flash的时候,当时还是flash5. 如今,我们看到html/css/JS的网站,特别是由Ajax驱动的,loading动画是极其有用的让用户知道服务器到此有止处理请求. 不要低估它,它可以网站更具有用户交换性. 它用于表示在当前场景背后有事情发生,通知用户等一会.

10 个顶级 JavaScript 动画框架推荐

- - ITeye资讯频道
使用JavaScript可以做出一些引人注目的动画效果,但通常不太容易实现. 本文为你整理了10个非常优秀的JavaScript动画框架,使用它们你可以轻松实现动画效果. Raphaël是一个小型JavaScript库,用于简化你的Web矢量图形工作. 如果你想创建独特的图表或图形的裁剪和旋转部件,你可以使用这个简单方便的库来实现.

使用javascript开发超棒的动画文字书写效果

- - 前端观察
在线演示  本地下载. 今天我们将介绍一个来自 script-tutorials的 javascript教程,在这个教程中,我们将介绍如何使用 javascript来生成一个动画的文字书写效果. Grant was not|||||||was very happy.

He took the letter to a shop in London where they bought and sold old papers.

Collie——基于 HTML5 的高性能 JavaScript 动画库

- - 博客园_梦想天空
JavaScript 库,用于创建高度优化的. Collie 可以运行在 PC 和手机上,使用 HTML5 Canvas 和 DOM. Collie 能够多线程稳定的处理多个对象,支持很多实用的功能,包括精灵动画和用户事件. 稳定支持 iOS 和 Android,并为每个平台的渲染提供优化的方法. Drag&Drop(地址:.

【译】常见的10个JavaScript动画函数库

- - 淡忘~浅思
原文: Javascript Animation Libraries. 译文:JavaScript的动画函数库. SVG是一种创建交互式动画非常棒的方式,独立的分辨率的矢量图形在任何大小的屏幕上看起来效果都很好. Snap.svg库使操作SVG变得更jQuery操作DOM一样简单. 一个基于动画和平移的简单但强大的JavaScript库.

Javascript高性能动画与页面渲染

- - 极客521 | 极客521
如果你不得不使用setTimeout或者setInterval来实现动画,那么原因只能是你需要精确的控制动画. 但我认为至少在现在这个时间点,高级浏览器、甚至手机浏览器的普及程度足够让你有理由有条件在实现动画时使用更高效的方式. 页面是每一帧变化都是系统绘制出来的(GPU或者CPU). 但这种绘制又和PC游戏的绘制不同,它的最高绘制频率受限于显示器的刷新频率(而非显卡),所以大多数情况下最高的绘制频率只能是每秒60帧(frame per second,以下用fps简称),对应于显示器的60Hz.

Javascript诞生记

- Milido - 阮一峰的网络日志
二周前,我谈了一点Javascript的历史. 今天把这部分补全,从历史的角度,说明Javascript到底是如何设计出来的. 只有了解这段历史,才能明白Javascript为什么是现在的样子. 我依据的资料,主要是Brendan Eich的自述. "1994年,网景公司(Netscape)发布了Navigator浏览器0.9版.