0%

JavaScript-学习笔记(8):动画

JavaScript-学习笔记(8):动画效果

动画是 CSS-DOM 最具动感的内容,让网页上的元素动起来。

基础知识

js 可以按照预定的时间重复调用函数,,这意味着我们可以随着时间的失衡而不断改变某个元素的样式。

  • 位置:网页元素在浏览器窗口里的位置是一种表示性信息,一般由 CSS 负责。例如:

    1
    2
    3
    4
    5
    element {
    position: absolute;
    top: 50px;
    left: 100px;
    }

    下面的 DOM 代码实现了同样的效果:

    1
    2
    3
    element.style.position = "absolute";
    element.style.top = "50px";
    element.style.left = "100px";

    假设有这么一个元素:

    1
    <p id="message">Wheel</p>

    再用 js 设置位置:

    1
    2
    3
    4
    5
    6
    7
    8
    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";
    }

    添加到windows.onload

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function addLoadEvent(func) {
    var oldonload = window.onload;
    if (typeof window.onload != "function") {
    window.onload = func;
    } else {
    window.onload = function () {
    oldonload();
    func();
    };
    }
    }
    addLoadEvent(positionMessage);

    结果:
    Image

    再编写一个移动的函数:

    1
    2
    3
    4
    5
    6
    function moveMessage() {
    if (!document.getElementById) return false;
    if (!document.getElementById("message")) return false;
    var elem = document.getElementById("message");
    elem.style.left = "200px";
    }

    结果:
    Image

    这是立即改变位置,要想获得动画效果,还要让位置随着时间的改变而不断改变。

  • 时间

    js 的 setTimeout 能够让某个经过一段预定的时间后才开始执行。
    用法:

    1
    variable = setTimeout("function", interval);

    可以用 clearTimeout 的函数来消 “等待执行”队列的某个函数。

    1
    clearTimeout(variable);

    修改 postitonMessage 函数,让其在 5 秒后才调用 moveMessage 函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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);
    }

    positionMessage 依旧在加载时调用。

  • 时间递增量

    动画应该是一个渐变的过程,元素应该从出发点逐步移动到目的点,而不是一下子跳过去。

    更新 moveMessage 函数,让元素以逐渐移动的方式发生。步骤:

    • 获得元素的当前位置;
    • 如果元素已经到达目的地,则退出;
    • 如果元素尚未到达它的目的地,则把它向目的地移动;
    • 一段时间后重复上述步骤。

    获取位置的值:

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

    只有使用 DOM 脚本或 style 属性分配位置后,这里的 parseInt 才起作用。

    编辑 moveMessage 函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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";
    // 每隔10毫秒递归
    movement = setTimeout("moveMessage()", 10);
    }
  • 抽象

    上面的代码只能完成特定的函数,如果把里面的常数改为变量,那么就通用性就会大大提升。

    将移动元素的 ID、元素的目的地的位置,以及停顿间隔改为变量。
    定义一个新的函数 function moveElement(elementID, final_x, final_y, interval),具体代码为。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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";
    // 每隔 interval 毫秒递归
    var reqpeat =
    "moveElement('" +
    elementID +
    "'," +
    final_x +
    "," +
    final_y +
    "," +
    interval +
    ")";
    movement = setTimeout(reqpeat, interval);
    }

    使用 moveElement 函数。
    创建一个名为 message.html 的文件,包含一个 id=message 的 p 元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Position</title>
    </head>

    <body>
    <p id="message">Wheel</p>
    </body>
    </html>

    再编写一个 positionMessage.js 的文件,添加 positionMessage() 函数:
    id=message2 的 p 元素,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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);
    }

    addLoadEvent(positionMessage);

    再在 HTML 文件上添加相对应的 js 文件,就可以显示出简单的动画效果。
    改变 interval 的值即可改变动画的速度,改变 x 和 y 的值即可改变最终位置。

    添加一个 id=message2 的 p 元素,<p id="message2">Whoal</p>,再在 positionMessage 函数中添加相对应的操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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";

    var elem_2 = document.getElementById("message2");
    elem_2.style.position = "absolute";
    elem_2.style.left = "50px";
    elem_2.style.top = "50px";

    moveElement("message", 125, 25, 10);
    moveElement("message2", 125, 125, 20);
    }

    结果:
    Image

实用的动画

上面的动画用处不大,还有可能使用户厌烦。

  • 提出问题:有一个怨念一系列链接的网页。当用用户的鼠标指针悬停在某个链接上时,展示预览一张图片。
    添加一个文件 list.html,代码为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Web Design</title>
    </head>

    <body>
    <h1>Web Design</h1>
    <p>These are the things you should know:</p>
    <ol id="linklist">
    <li>
    <a href="./structure.html">structure</a>
    </li>
    <li>
    <a href="./presentation.html">presentation</a>
    </li>
    <li>
    <a href="./behavior.html">behavior</a>
    </li>
    </ol>
    </body>
    </html>

    这次需要在 onmouseover 事件被触发时显示一张图片。

  • 解决问题:

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

    集体照:
    Image
    添加到 list.html 最后面:

    1
    <img src="./topic.png" alt="building blocks of web Design" id="preview" />
  • CSS

    css 的 overflow 属性用来处理一个元素的尺寸超出其容器尺寸的情况。
    overflow 属性有 4 种取值:visible(不作任何改动)、hidden(将溢出部分隐藏)、scroll(隐藏,但提供一个滚动条)和 auto(类似 scroll,但只有溢出时才显示滚动条)。

    这里选取 hidden 属性。

    添加 div,将 img 包进去,再添加对应的样式:

    1
    2
    3
    <div id="slideshow">
    <img src="./topic.png" alt="building blocks of web Design" id="preview" />
    </div>
    1
    2
    3
    4
    5
    6
    #slideshow {
    width: 130px;
    height: 100px;
    position: relative;
    overflow: hidden;
    }

    接下来解决的时,让网页对用户的操作做出正确响应,当鼠标指针指在某个地方时,把对应的图片显示出来。

  • JavaScript

    编写一个 prepareSlideshow 来完成工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    function prepareSlideshow() {
    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");

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

    将 js 文件添加到 html 文件,运行后将鼠标移到对应的链接上会出来动画效果:
    Image

    但是来回移动时会出现混乱的情况。

  • 变量的作用域

    动画效果出错是由全局变量引起的。

    1
    movement = setTimeout(reqpeat, interval);

    但是直接使用局部变量也会出错,需要一种介乎二者之间的东西,也就是属性。
    js 允许为元素创建属性:

    1
    element.property = value;

    所以更改为属性即可解决问题:

    1
    elem.movement = setTimeout(reqpeat, interval);
  • 改进动画效果:
    每次移动 1px,速度未免过慢。
    可以设置每次前进这个距离的十分之一。

    1
    2
    dist = (final_x - xpos) / 10;
    xpos = xpos + dist;

    当差距小于 10 的时候,用这个差距除 10 的结果小于 1,将移动不了。
    解决方法是使用 Math 对象的 ceil 函数,它可以返回一个不小于 dist 的值的一个整数,用法:

    1
    Math.ceil(number);

    最终代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    function moveElement(elementID, final_x, final_y, interval) {
    if (!document.getElementById) return false;
    if (!document.getElementById(elementID)) return false;
    var elem = document.getElementById(elementID);

    // 声明 dist
    var dist = 0;
    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) {
    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";
    // 每隔 interval 毫秒递归
    var reqpeat =
    "moveElement('" +
    elementID +
    "'," +
    final_x +
    "," +
    final_y +
    "," +
    interval +
    ")";
    // movement = setTimeout(reqpeat, interval);
    elem.movement = setTimeout(reqpeat, interval);
    }
  • 添加安全检查
    诸如 elem.style.left 是否存在等。

  • 生成 HTML 标记:
    一些内容是为了存在动画效果而存在的,但是如果不支持 js,那么那些内容留在那里岂不是很尴尬,所以干脆让 js 来生成这些标记。

小结

实现动画效果并不难,问题是尖不应该使用动画。
动画技术可以让我们创建出很多非常酷的效果,但四处移动的元素用处不大。现在有一个通用性的函数,在有必要创建动画效果时帮上大忙。

------------ 感谢你的阅读 ------------