前端学习|【javascript】实现动画效果

一、动画基础知识
1.位置
位置信息通常由CSS负责设置

element { position:absolute; top:50px; left:100px; }

也可以使用DOM实现相同的效果
element.style.position = "absolute"; element.style.left = "100px"; element.style.top = "50px";

position属性的值有static,fixed,relative和absolute,其默认是值static。
top属性是元素距离文档顶部的距离,bottom则是元素距离文档底部的距离。left属性是元素距离文档左边界的距离,right是元素距离文档右边的距离。
为防止它们发生冲突,最好只是用top和bottom属性其中之一,left和right属性也是如此。
假设有一个p元素(其余html代码已省略)
Whee!

可以使用javascript代码来设置这个元素的位置
function positionMessage(){ if (!document.getElementById) return false; if (!document.getElementById("message")) return false; var elem = document.getElementById("message"); elem.style.position = "absolute"; elem.style.left = "50px"; elem.style.top = "100px"; }

我们可以观察到,调用该函数,会把这段文本摆放在距浏览器窗口左边界50像素,距离浏览器窗口的顶边界100像素的位置。
改变某个元素的位置,主要执行一个函数去改变这个元素的style.top或style.left等属性即可。
function moveMessage(){ if (!document.getElementById) return false; if (!document.getElementById("message")) return false; var elem = document.getElementById("message"); elem.style.left = "200px"; }

调用这两个函数,这个元素的显示位置立刻发生了变化,但是并不是我们想要的动画效果。这是由于javascript太有效率了,函数的执行间隔我们尚未察觉,因此需要创造时间间隔。
2.时间
setTimeout函数能使函数在经过一段预定的时间后才被执行,该函数有2个参数:第一个参数是一个字符串,代表将要执行的函数名,第二个参数是一个数值,代表经过多长时间后才开始执行该函数,单位是毫秒。
clearTimeout函数可以取消”等待执行“队列中的某个函数,该函数需要1个参数:保存着某个setTimeout函数调用返回值的变量。
修改positionMessage函数,使得在5秒后才调用moveMessage函数。
function positionMessage(){ if (!document.getElementById) return false; if (!document.getElementById("message")) return false; var elem = document.getElementById("message"); elem.style.position = "absolute"; elem.style.left = "50px"; elem.style.top = "100px"; movement = setTimeout("moveMessage()",5000); }

如此一来,文本元素将先出现在它原始位置上,经过5秒之后向右移动150像素
3.时间递增量
真正的动画效果是一个渐变的过程,元素应该从出发点逐步移动到目的地,而不是跳跃到目的地。
为了让元素的移动以渐变的方式发生,以下是moveMessage函数的逻辑。
  1. 获得元素的当前位置
  2. 若元素已经到达目的地,则退出该函数
  3. 若元素尚未到达目的地,则将它向目的地移动一点儿
  4. 经过一段时间间隔后从步骤1开始重复执行
第一步是确定元素的当前位置,可以通过查询style.top和style.left等位置属性完成。
var xpos = elem.style.left; var ypos = elem.style.top;

调用该函数后,xpos,ypos分别将被赋值50px,100px,这两个值都是字符串,然而我们需要的是数值。
parseInt函数将字符串中的数值提取出来,返回值通常是整数;若需要提取浮点数,则使用parseFloat函数。
修改后的moveMessage函数:
function moveMessage(){ if (!document.getElementById) return false; if (!document.getElementById("message")) return false; var elem = document.getElementById("message"); var xpos = parseInt(elem.style.left); var ypos = parseInt(elem.style.top); if (xpos == 200 && ypos ==100) return true; if (xpos < 200) xpos++; if (xpos > 200) xpos--; if (ypos < 100) ypos++; if (ypos > 100) ypos--; elem.style.left = xpos + "px"; elem.style.top = ypos + "px"; movement = setTimeout("moveMessage()",10); }

这个函数使得message元素以每次1像素的的方式在浏览器窗口中移动。
4.抽象
之前编写的moveMessage函数所存放的信息是硬编码在函数中的,如果将这些常数都改为变量,函数的灵活性会增强。
创建moveMessage函数,有4个参数
  1. elementID:打算移动的元素的ID
  2. final_x:该元素的目的地”左“位置
  3. final_y:该元素的目的地”上“位置
  4. interval:两次移动之间的停顿时间。
moveMessage函数:
function moveElement(elementID,final_x,final_y,interval){ if (!document.getElementById) return false; if (!document.getElementById(elementID)) return false; var elem = document.getElementById(elementID); var xpos = parseInt(elem.style.left); var ypos = parseInt(elem.style.top); if (xpos == final_x && ypos == final_y) return true; if (xpos < final_x) xpos++; if (xpos > final_x) xpos--; if (ypos < final_y) ypos++; if (ypos > final_y) ypos--; elem.style.left = xpos + "px"; elem.style.top = ypos + "px"; var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")"; movement = setTimeout(repeat,interval); }

二、实用的动画
网页上的动画元素不仅容易引起访问者的反感,还容易导致各种的可访性问题。W3C给出建议:除非浏览器允许用户“冻结”移动着的内容,否则就避免让内容在页面上移动,如果页面上有移动着的内容,就应该用脚本或插件的机制允许用户冻结这种移动或动态更新行为。
1.提出问题
我们有一个包含一系列链接的网页,当用户把鼠标悬停在某个链接上时,我们想用一种先睹为快的方式告诉用户这个链接将把他们带往何方,我们可以展示一张预览图片。
list.html
Web Design - 锐客网 Web Design These are the things you should konw.

这个案例与图片库相似,这次我们使用onmouseover事件被触发时显示一张图片。我们只需要将onclick事件处理函数改为onmouseover,但图片显示得不够流畅:当用户第一次把鼠标悬停在某个链接上时,新图片将被加载过去。即使是在一个高速的网络连接上,也需要不少事件,而我们希望能够立刻响应。
【前端学习|【javascript】实现动画效果】2.解决问题
如果为每个链接分别准备一张预览图片,在切换时显示这些图片会有一些延迟,简单地切换显示这些图片也不是期望的效果。
我们可以这样做:
  • 为所有的预览图片生成为一张“集体照”形式的图片
  • 隐藏这张“集体照”图片的绝大部分
  • 当用户把鼠标指针悬停在某个链接的上方时,显示这张“集体照”图片的相应部分
制作一张宽800,高200像素的图片,将它如茶list.html文档中,并把id设置为preview。
3.CSS
CSS的overflow属性用来处理一个元素的尺寸超出容器尺寸的情况,当一个元素包含的内容超出自身的大小时,就会发生内容溢出。
overflow属性的取值:
  • visible(默认值):内容不会被裁剪,会呈现在元素框之外
  • hidden:内容会被裁剪,并且其余内容是不可见的
  • scroll:内容会被裁剪,但浏览器会显示滚动条以便查看其余的内容
  • auto:如果内容被裁剪,则浏览器会显示滚动条以便查看其余的内容。
最能满足我们需求的是hidden,我们有一张800200像素的图片,但每次只想显示200200像素的部分。
首先需要将这个图片放在一个容器元素div中,并把id属性设置为slidehsow
设置样式overflow.css
#slideshow { width:100px; height:100px; position:relative; overflow:hidden; }

position属性设置为relative,能使子元素的(0,0)坐标固定在div容器的左上角,overflow属性设置为hidden,确保其中的内容被会剪裁。
将该css引入到html文档中,就能看到原本的图片只显示一部分。
接下来要解决的问题是,我们想在用户将鼠标指针悬停在某个链接上时,把图片中与之对应的部分显示出来,能够使用javascript和DOM来实现。
4.Javascript
我们计划用moveElement函数来移动图片,根据用户将鼠标指针悬停在哪个链接上,将这个图片向左或向右移动。我们需要把调用moveElement函数行为,与链接清单中每个链接的onmouseover事件关联起来。
为preview图片设置一个默认位置
var preview = document.getElementById("preview"); preview.style.position = "absolute"; preview.style.left = "0px"; preview.style.top = "0px";

该图片将出现在它的容器元素的左上角,即id为slideshow的div元素的左上角。因为将一个position属性值absolute的元素A放在一个position属性值是relative的元素B中,则B是A的容器。则preview图片将出现在slideshow元素的左上角。
完整Javascript代码:
function prepareSlideshow(){ //确保浏览器支持DOM方法 if (!document.getElementById) return false; if (!document.getElementsByTagName) return false; //确保元素存在 if (!document.getElementById("linklist")) return false; if (!document.getElementById("preview")) return false; //为图片应用样式 var preview = document.getElementById("preview"); preview.style.position = "absolute"; preview.style.left = "0px"; preview.style.top = "0px"; //取得列表中的链接 var list = document.getElementById("linklist"); var links =document.getElementsByTagName("a"); //为mouseover事件添加动画效果 links[0].onmouseover = function(){ moveElement("preview",-200,0,10); } links[1].onmouseover = function(){ moveElement("preview",-400,0,10); } links[2].onmouseover = function(){ moveElement("preview",-600,0,10); } }addLoadEvent(prepareSlideshow);

但是我们如果把鼠标指针在链接之间快速来回移动,动画效果就会变得混乱。
5.变量作用于问题
动画效果不正确是由一个全局变量引起的,在把moveMessagae函数抽象为moveElement函数的过程中,我们未对变量movement做任何修改。
这留下了一个隐患:每当用户把鼠标指针悬停在某个链接上,不管上一次的调用是否已经把图片移动到位,moveElement函数都会被贼次调用并试图把这个图片移动到别的地方去,于是当用户在链接之间快速移动鼠标时,movement变量就会像拔河那样来回变化,而moveElement函数就会尝试把图片同时移动到两个不同的地方去。
如果用户移动鼠标过快,积累在setTimeout队列里的事件就会导致动画效果产生滞后,为了消除动画滞后现象,可以用clearTimeout函数清楚积累在setTimeout队列里的事件
clearTimeout(movement);
然而还没有设置movement之前就执行这条语句,会引发错误:我们不会使用局部变量。
如果这样做,clearTimeout函数调用工作将无法工作,因为局部变量movement在clearTimeout函数中不存在。
因此我们不能使用全局变量也不能使用局部变量。我们需要一个只与正在被移动的元素相关的变量,它就是属性。
创建属性:
element.property = value;

我们将变量movement改变成一个正在被移动的元素的属性,这样就能测试它是否已经存在,并在已经存在的情况下使用clearTimeout函数了。
改进的代码:
function moveElement(elementID,final_x,final_y,interval){ if (!document.getElementById) return false; if (!document.getElementById(elementID)) return false; var elem = document.getElementById(elementID); if (ele.movement) clearTimeout(elem.movement); var xpos = parseInt(elem.style.left); var ypos = parseInt(elem.style.top); if (xpos == final_x && ypos == final_y) return true; if (xpos < final_x) xpos++; if (xpos > final_x) xpos--; if (ypos < final_y) ypos++; if (ypos > final_y) ypos--; elem.style.left = xpos + "px"; elem.style.top = ypos + "px"; var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")"; elem.movement = setTimeout(repeat,interval); }

于是,不管moveElement这在移动哪个元素,该元素将获得一个名为movement的属性。如果该元素在moveElement函数开始执行时已经存在movement属性,就用clearTimeout函数对其进行复位。
此时,鼠标在两个连接之间快速切换不会再产生问题,setTimeout队列中不再有积累的事件。
6.改进的动画效果
在元素达到目的地之前,movement函数只会移动一个像素的距离,移动速度太慢。
这是由于moveElement.js中的代码:
if (xpos < final_x) xpos++;

我们可以让那个元素与它的目的地距离较远的时候前进一大步,在那个元素与它的目的地距离较近的时候前进一小步。
首先,需要计算元素与目的地之间的距离,然后让元素前进这个距离的十分之一。
dist = (final_x - xpos)/10 xpos = xpos+dist;

但当final_x与xpos之间的距离小于10,它的前进值将小于1,这个问题可以用Math对象中的ceil方法来解决,它将返回一个不小于dist值的一个整数,即向上取整。
Math.ceil(number);

还有两个相似的方法:
  • floor:向下取整
  • round:四舍五入
修改后的代码:
function moveElement(elementID,final_x,final_y,interval){ if (!document.getElementById) return false; if (!document.getElementById(elementID)) return false; var elem = document.getElementById(elementID); if (elem.movement) clearTimeout(elem.movement); var xpos = parseInt(elem.style.left); var ypos = parseInt(elem.style.top); var dist = 0; if (xpos == final_x && ypos == final_y) return true; if (xpos < final_x){ // xpos++ dist = Math.ceil((final_x - xpos)/10); xpos = xpos + dist } if (xpos > final_x){ dist = Math.ceil((xpos - final_x)/10); xpos = xpos - dist; } if (ypos < final_y){ dist = Math.ceil((final_y - ypos)/10); ypos = ypos + dist; } if (ypos > final_y){ dist = Math.ceil((ypos - final_y)/10); ypos = ypos - dist; } elem.style.left = xpos + "px"; elem.style.top = ypos + "px"; var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")"; elem.movement = setTimeout(repeat,interval); }

6.添加安全检查
moveElement函数有一个假设:假设elem元素存在left属性和top样式属性。
var xpos = parseInt(elem.style.left); var ypos = parseInt(elem.style.top);

如果elem元素的left和top属性不存在,有以下选择:
(1)退出该函数
if (!elem.style.left || !elem.style.top) return false;

(2)在moveElement函数中为left和top属性分别设置一个默认值,可以为0px
if (!elem.style.left) elem.style.left = "0px"; if (!elem.style.top) elem.style.top = "0px";

我们选择第二种方法,有了这个安全措施之后,在prepareSlideshow函数中可以删除两条语句
preview.style.left = "0px"; preview.style.top = "0px";

8.生成HTML标记
list.html文档中包含一些为了能够用javascript代码实现动画效果加进来的:
前端学习|【javascript】实现动画效果
文章图片

这里的div和img元素是为了动画效果添加进来的,如果没有启用javascript支持,这一段代码就显得多余,因此可以选择使用javascript代码来生成它们,修改prepareSlideshow.js文件
首先创建div元素和img元素,并将img元素放进div元素中
//创建div元素 var slideshow = document.createElement("div"); slideshow.setAttribute("id","slideshow"); //创建img元素 var preview = document.createElement("img"); preview.setAttribute("id","preview"); preview.setAttribute("src","images/topics.gif"); preview.setAttribute("alt","Picture"); //将img元素放进div元素中 slideshow.appendChild(preview);

然后将这些元素放在链接清单的后面,使用insertAfter来完成
var list = document.getElementById("linklist"); insertAfter(slideshow,list);

修改后的代码清单:
function prepareSlideshow(){ //确保浏览器支持DOM方法 if (!document.getElementById) return false; if (!document.getElementsByTagName) return false; //确保元素存在 if (!document.getElementById("linklist")) return false; //添加HTML标记 var slideshow = document.createElement("div"); slideshow.setAttribute("id","slideshow"); var preview = document.createElement("img"); preview.setAttribute("id","preview"); preview.setAttribute("src","images/topics.gif"); preview.setAttribute("alt","Picture"); slideshow.appendChild(preview); var list = document.getElementById("linklist"); insertAfter(slideshow,list); //为图片应用样式 var preview = document.getElementById("preview"); preview.style.position = "absolute"; //取得列表中的链接 var list = document.getElementById("linklist"); var links =document.getElementsByTagName("a"); //为mouseover事件添加动画效果 links[0].onmouseover = function(){ moveElement("preview",-200,0,10); } links[1].onmouseover = function(){ moveElement("preview",-400,0,10); } links[2].onmouseover = function(){ moveElement("preview",-600,0,10); } }addLoadEvent(prepareSlideshow);

还需要修改样式文件,因为在prepareSlideshow.js删除了代码
preview.style.position = "absolute";

需要将其删除的代码的效果,添加到CSS中
#slideshow { width:200px; height:200px; position:relative; overflow:hidden; } #preview { position:absolute; }

    推荐阅读