一、动画基础知识
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开始重复执行
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个参数
- elementID:打算移动的元素的ID
- final_x:该元素的目的地”左“位置
- final_y:该元素的目的地”上“位置
- interval:两次移动之间的停顿时间。
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.解决问题
如果为每个链接分别准备一张预览图片,在切换时显示这些图片会有一些延迟,简单地切换显示这些图片也不是期望的效果。
我们可以这样做:
- 为所有的预览图片生成为一张“集体照”形式的图片
- 隐藏这张“集体照”图片的绝大部分
- 当用户把鼠标指针悬停在某个链接的上方时,显示这张“集体照”图片的相应部分
3.CSS
CSS的overflow属性用来处理一个元素的尺寸超出容器尺寸的情况,当一个元素包含的内容超出自身的大小时,就会发生内容溢出。
overflow属性的取值:
- visible(默认值):内容不会被裁剪,会呈现在元素框之外
- hidden:内容会被裁剪,并且其余内容是不可见的
- scroll:内容会被裁剪,但浏览器会显示滚动条以便查看其余的内容
- auto:如果内容被裁剪,则浏览器会显示滚动条以便查看其余的内容。
首先需要将这个图片放在一个容器元素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代码实现动画效果加进来的:
文章图片
这里的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;
}
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- web网页模板|如此优秀的JS轮播图,写完老师都沉默了
- 接口|axios接口报错-参数类型错误解决
- JavaScript|vue 基于axios封装request接口请求——request.js文件
- vue.js|vue中使用axios封装成request使用
- JavaScript|JavaScript: BOM对象 和 DOM 对象的增删改查
- JavaScript|JavaScript — 初识数组、数组字面量和方法、forEach、数组的遍历
- JavaScript|JavaScript — call()和apply()、Date对象、Math、包装类、字符串的方法
- JavaScript|JavaScript之DOM增删改查(重点)
- 前端|web前端dya07--ES6高级语法的转化&render&vue与webpack&export