[笔记11]JavaScript DOM编程艺术_实现动画效果

添加样式信息,CSS仍是最佳工具,但是如果想随着时间的变化而改变某个元素的样式,则只能使用JS。JS能够按照预定的时间间隔重复调用一个函数,这就意味着我们可以随着时间的推移而不断改变某个元素的样式。

时间

JS函数setTimeout能够让某个函数在经过一段预定的时间之后才开始执行。

setTimeout("function",interval)
// 第一个参数,是要执行的函数的名字
//第二个参数,它以毫秒为单位设定了需要经过多长时间后菜开始执行第一个参数所给出的函数
clearTimeout("function")
//取消等待执行队列里的某个函数
如:movement=setTimeout("moveMessage()",5000);
clearTimeout(movement);
//movement在声明它时未使用关键字var,它是一个全局变量。

时间递增量

上面的动画实现是跳跃式的,那我们实际想实现的效果是渐进式的,那该怎么办呢?
正确的处理逻辑是:

  • 获得元素的当前位置
  • 如果元素已经达到它的目的地,则退出这个函数
  • 如果元素尚未达到它的目的地,则把它向目的地移近一点儿。
  • 经过一段段时间间隔之后从步骤1开始重复上述步骤

JS函数parseInt可以把字符串里的数值信息提取出来。
如parseInt("39 steps")返回的数值是39。parseInt函数的返回值通常是整数。如果需要提取的是带小数点的数值,就应该使用相应的parseFloat函数。parseFloat(string)

抽象

抽象的JS代码如下所示。对网页中的message元素进行缓慢移动。

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";
    moveElement("message",200,100,10);
}

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);
}
addLoadEvent(positionMessage);

实用动画

网页上的动画元素不仅容易引起访问者的反感,还容易导致各种各样的可访问性的问题。下面来看一个实用性的例子。

提出问题

当用户把鼠标指针悬停在其中的某个链接时,我们想用一种先睹为快的方式告诉用户这个链接将他们带往何方。

点击链接时,希望立刻显示一张图片。如果只是通过网络加载的话,肯定需要时间,就不能做到及时响应。为了解决这个问题的方案如下:

  • 为所有的预览图片生成为一张“集体照”形式的照片
  • 隐藏这张“集体照”图片的绝大部分
  • 当用户把鼠标指针悬停在某个链接上方时,只显示这张“集体照片”的相应部分。

每次只让图片的某个100*100像素出现,我们无法用JS做到这一点,但是可以用CSS来做。

CSS

CSS的overflow属性用来处理一个元素的尺寸超出其容,器尺寸的情况。当一个元素包含的内容超出自身大小时,就会发生内容溢出,这种情况,你可以对内容进行“裁剪”只让一部分内容可见。你还可以通过overflow属性告诉浏览器是否需要显示滚动条,以便让用户能够看到内容的其余部分。

overflow属性可取值有4种:visible、hidden、scroll和auto。

  • visible:不裁剪溢出的内容。浏览器将把溢出的内容显示在其容器元素的显示区域以外,全部内容都可见。
  • hidden:隐藏溢出的内容。内容只显示在其容器元素的显示区域里,这意味着只有一部分内容可见。
  • scroll:类似于hidden,浏览器将对溢出的内容进行隐藏,但显示一个滚动条以便让用户能够滚动看到内容的其它部分。
  • auto:类似于scroll,但浏览器只在确实发生溢出时才显示滚动条,如果内容没有溢出,就不显示滚动条。

上面的四种可取值种,最能满足我们要求的是hidden。

首先将图片放到容器中。
设置容器的显示样式。

//容器的id为sildeshow
#slideshow{
    width: 100px;
    height: 100px;
    position: relative;
    overflow: hidden;
}

JS

上面的代码现在可以只显示一部分图片。如果根据用户正把鼠标指针悬停在哪个链接上,我们需要将这个图片向左或者向右移动。我们需要把moveElement函数的行为与链接清单里每个链接的onmouseover事件关联起来。

function prepareSlideshow() {
    // 确定浏览器支持DOM方法
   if(!document.getElementsByTagName)return false;
   if(!document.getElementById)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=list.getElementsByTagName("a");
     //为mouseover事件添加动画效果
     links[0].onmouseover=function(){
        moveElement("preview",-100,0,10);
     }
     links[1].onmouseover=function(){
        moveElement("preview",-200,0,10);
     }
     links[2].onmouseover=function(){
        moveElement("preview",-300,0,10);
     }
}
addLoadEvent(prepareSlideshow);

上面的实现根据鼠标指针悬停在哪个链接上,placeholder.gif图片的不同部分就会进入我们的视线。不过,如果把鼠标指针在链接之间快速来回移动,动画效果将变得混乱起来。

变量作用域问题

动画效果不正确的问题是由一个全局变量引起的,把moveMessage函数抽象化为moveElement函数过程中,我们未对变量movement做任何修改。

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);

这留下了一个隐患。每当用户把鼠标指针悬停在某个链接上,不管上一次调用是否已经把图片移动到位,moveElement函数都会被再次调用并试图把这个图片移动到另一个地方去。如果用户移动鼠标速度够快,积累在setTimeout队列里的事件就会导致动画效果产生滞后。为了消除动画滞后的现象,可以用clearTimeout函数清除积累在setTimeout队列里的事件。
clearTimeout(movement);

PS:这个clearTimeout应该放在setTimeout函数之前调用,但是有个问题,movement还没有声明的话(没有声明就是全局),执行clearTimeout函数会出错。如果在这之后声明,明显又不行。

JS允许我们为元素创建属性。element.property=value。它很像在创建一个变量,但区别是这个变量专属于某个特定的元素。

修改代码如下:

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);
    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);
}

改进动画效果

之前动画的移动是每次移动1px,移动速度有点慢,改进效果是:如果那个元素与它的目的地距离较远,就让它每次前进一大步;如果那个与目的地juli较近,就让它每次前进一小步。
具体步骤如下:

  • 首先计算出元素与它的目的地之间的距离。
  • 然后让每个元素每次前进这个距离的1/10。

需要注意的问题是:当元素离目的地不到10的时候,除以10将小于1,而我们不可能把一个元素移动不到一个像素的距离。这个问题可以用Math对象的ceil方法来解决。它可以返回一个不小于 number值的整数。语法是Math.ceil(number)

改进后的代码如下:

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){
        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);
}

addLoadEvent(positionMessage);

添加安全检查

moveElement函数表现很好,但是函数开头部分需要一个假设,

var xpos=parseInt(elem.style.left);
var ypos=parseInt(elem.style.top);

所以我们可以在函数开头添加这样一个判断。

if(!elem.style.left||!elem.style.top){
    return false;
}
或者没有值的话,我进行一个赋值。
if(!elem.style.left){
elem.style.left="0px";
}
if(!elem.style.top){
elem.style.top="0px";
}

生成HTML标记

list.html文档里包含一些只是为了能够用JS代码实现动画效果而存在的标记。

<div id="slideshow">
![](images/placeholder.gif)
</div>

如果用户没有启动JavaScript,以上的内容未免太多余了。与其将这些元素硬编码在文档里,不如用JS代码来生成它们。
最终代码如下:

function prepareSlideshow() {
    // 确定浏览器支持DOM方法
   if(!document.getElementsByTagName)return false;
   if(!document.getElementById)return false;
    //确保元素存在
    if(!document.getElementById("linklist"))return false;
    if(!document.getElementById("preview"))return false;

    var slideshow=document.createElement("div");
    slideshow.setAttribute("id","slideshow");
    var preview=document.createElement("img");
    preview.setAttribute("src","images/placeholder.gif");
    preview.setAttribute("alt","building blocks of web design");
    preview.setAttribute("id","preview");
    slideshow.appendChild(preview);

    //取得列表中的所有链接
    var list=document.getElementById("linklist");
    insertAfter(slideshow,list);
    var links=list.getElementsByTagName("a");
    //为mouseover事件添加动画效果
    links[0].onmouseover=function(){
     moveElement("preview",-100,0,10);
    }
    links[1].onmouseover=function(){
     moveElement("preview",-200,0,10);
    }
    links[2].onmouseover=function(){
     moveElement("preview",-300,0,10);
    }
}
addLoadEvent(prepareSlideshow);

效果图如下所示:

11.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,285评论 25 707
  • 前言 归根结底,代码都是思想和概念的体现。没人能把一种程序设计语言的所有语法和关键字都记住,可以查阅参考书来解决。...
    朱细细阅读 2,909评论 4 14
  • 我们为什么旅行呢?我想是为了寻找久违的自己吧。 似乎被生活牵绊了太久,我们要工作,我们要应酬,习惯早出晚归,习惯排...
    影子写作阅读 1,151评论 3 1
  • 今天,老师讲了一些高级控件,相比之前的控件,今天讲的控件在操作使用上更需要对代码的需求。
    老翟_4856阅读 119评论 0 0
  • 2017年的脚步在浑然不觉中又已过去大半,这大半年来给自己总结一下:忙忙碌碌也好,浑浑噩噩也罢,时间还是没...
    yiran1314阅读 363评论 1 1