06-JS特效-02动画&冒泡

动画

  • 定义:运动的图片(让图片或者图画动起来)。
  • 原理:未来位置等于当前位置加上步长(盒子的位置 = 盒子本身所在位置 + 步长)。
  • 位置获取和赋值方式:用style.left赋值,用offsetLeft获取值;style.left获取值不方便,只能获取行内式,如果没有就返回"",容易出现NaN,即使有值也是包含px的字符串;offsetLeft获取值特别方便,而且获取到的值是现成的number类型,方便计算,因为它是只读的不能赋值,所以一般用style.left赋值。

动画的种类

闪现(很少使用)
<body>
<button>闪动</button>
<div></div>

<script src="../05-jQuery/jquery-1.11.1.js"></script>
<script type="text/javascript">
    var btn = $("button");
    var divs = $("div");

    $(btn[0]).click(function () {
        divs[0].style.left = "500px";
    });
</script>

</body>
匀速(重点
<body>
<button>匀动到200</button>
<button>匀动到400</button>
<div></div>

<script>
    var btn200 = document.getElementsByTagName("button")[0];
    var btn400 = document.getElementsByTagName("button")[1];
    var div = document.getElementsByTagName("div")[0];
    var timer = null;
    btn200.onclick = function () {
        animation(200);
    };
    btn400.onclick = function () {
        animation(400);
    };

    function animation(distance) {
        timer = setInterval(function () {
            div.style.left = div.offsetLeft + 10 + "px";
            if (div.offsetLeftd == distance){
                clearInterval(timer);
            }
        },30);
    }
</script>
</body>
  • 系列bug:
  • 定时器:多次使用定时器之后,动画会越来越快:
    • 解决方案:在使用定时器之前,先清除定时器
  • 不能返回:
    • 解决方案:判断目标位置和当前位置的大小,如果目标位置大,步进就为正,否则就为负。
  • 如果目标位置和当前位置的距离小于步进,就不再进行运动,但是会出现抖动现象:
    • 解决方案:直接令当前位置等于目标位置。
  • 一个盒子只能有一个定时器,这样的话,不会出现定时器冲突;
  • 将定时器本身绑定成为盒子的一个属性,不会和其它盒子定时器产生冲突
<head>
    <meta charset="UTF-8">
    <title>匀速运动</title>

    <style type="text/css">
        body{
            margin:0;
            padding:0;
        }
        .box1{
            margin:0;
            position: relative;
        }
        .box2{
            width: 100px;
            height: 100px;
            background-color: pink;
            position: absolute;
        }
        .box3{
            width: 100px;
            height: 100px;
            background-color: red;
            position: absolute;
            margin-top: 130px;
        }
    </style>
</head>

<body>
<div class="box1">
    <!--匀速运动-->
    <button>匀动到200</button>
    <button style="left: 50px">匀动到400</button>
    <div class="box2"></div>
    <div class="box3"></div>
</div>

<script>
    var btn200 = document.getElementsByTagName("button")[0];
    var btn400 = document.getElementsByTagName("button")[1];
    var box2 = document.getElementsByTagName("div")[1];
    var box3 = document.getElementsByClassName("box3")[0];
    btn200.onclick = function () {
        animation(box2,200);
    };
    btn400.onclick = function () {
        animation(box3,400);
    };

    function animation(ele,distance) {
        //1.使用定时器之前必须先清除,排除多次点击越来越快的bug
        clearInterval(ele.timer);   //将定时器作为元素的属性,这样一个盒子绑定一个自己的定时器,排除了其它盒子定时器的干扰
        //2.为了能让div可以从后往前走,要先进行判断目标位置和当前位置的大小,如果目标位置大,步进就正,否则就负
        var speed = distance>box2.offsetLeft?10:(-10);
        ele.timer = setInterval(function () {
            var val = distance - ele.offsetLeft;   //此句位置至关重要,不能和下一句倒换位置,必须要先获取目标位置和当前位置的距离
            ele.style.left = ele.offsetLeft + speed + "px";   //此时盒子会进行运动

            //3.到达目标位置后,再次点击会出现抖动现象。解决方案,判断目标位置和当前位置距离是否小于步进,如果小于就让两者相等,并立刻清除定时器
            if (Math.abs(val)<Math.abs(speed)){
                ele.style.left = distance + "px";
                clearInterval(ele.timer);
            }
        },30);
    }
</script>
</body>
  • 练习:
  • 滑动轮播;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>练习</title>

    <style type="text/css">
        *{
            margin: 0;
            padding: 0;
        }

        .box{
            border: 1px solid #999;
            width: 490px;
            height: 170px;
            margin: 60px auto 0;
            padding:5px;
        }
        .inner{
            position: relative;
            width: 490px;
            height: 179px;
            overflow: hidden;
        }
        ul{
            width: 500%;
            position: absolute;
        }
        li{
            list-style: none;
            float: left;
        }

        .square{
            position: absolute;
            right: 10px;
            top: 140px;
        }
        .square span{
            display: inline-block;
            width: 18px;
            height: 18px;
            background-color: #aaa;
            text-align: center;
            line-height: 18px;
            cursor: pointer;
        }

        .inner .current{
            background-color: darkorange;
        }
    </style>

</head>

<body>

<div class="box">
    <div class="inner">
        <ul>
            <li>![](images/01.jpg)</li>
            <li>![](images/02.jpg)</li>
            <li>![](images/03.jpg)</li>
            <li>![](images/04.jpg)</li>
            <li>![](images/05.jpg)</li>
        </ul>

        <div class="square">
            <span class="current">1</span>
            <span>2</span>
            <span>3</span>
            <span>4</span>
            <span>5</span>
        </div>
    </div>
</div>

<script>
    window.onload = function () {
        //获取事件源和相关元素
        var inner = document.getElementsByClassName("inner")[0];
        var spans = inner.children[1].children;
        var ul = inner.children[0];
        //获取窗口宽度
        var scrollWidth = inner.offsetWidth;
        for(var i=0; i<spans.length; i++){
            spans[i].index = i;
            spans[i].onmouseover = function () {
                //使用排它思想点亮盒子
                for(var j=0; j<spans.length; j++){
                    spans[j].className = "";
                }
                this.className = "current";
                //移动盒子
                console.log(ul);
                animation(ul,-this.index*scrollWidth);

            }
        }

        function animation(ele,target) {
            clearInterval(ele.timer);
            var speed = target>ele.offsetLeft ? 10:-10;
            ele.timer = setInterval(function () {
                var val = target - ele.offsetLeft;
                ele.style.left = ele.offsetLeft + speed + "px";

                if (Math.abs(val)<Math.abs(speed)){
                    ele.style.left = target + "px";
                    clearInterval(ele.timer);
                }
            },1);
        }
    };

</script>

</body>
</html>
  • 左右焦点图;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>练习</title>

    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .box{
            width: 490px;
            height: 170px;
            margin: 60px auto;
            border: 1px solid #aaa;
            padding: 5px;
        }
        .inner{
            width: 490px;
            height: 170px;
            position: relative;
            overflow: hidden;
        }
        ul{
            width: 500%;
            position: absolute;
        }
        li{
            list-style: none;
            float: left;
        }

        .rl{
            display: none;
        }
        .rl span{
            position: absolute;
            width: 30px;
            height: 30px;
            top: 70px;
            color: #fff;
            font-size: 30px;
            line-height: 30px;
            text-align: center;
            background: #000 ;
            left: 10px;
            cursor: pointer;
            opacity: 0.7;
        }
        .rl .right{
            right: 10px;
            left: auto;
        }
    </style>

</head>

<body>

<div class="box">
    <div class="inner">
        <ul>
            <li>![](images/01.jpg)</li>
            <li>![](images/02.jpg)</li>
            <li>![](images/03.jpg)</li>
            <li>![](images/04.jpg)</li>
            <li>![](images/05.jpg)</li>
        </ul>
        <div class="rl">
            <span class="left"><</span>
            <span class="right">></span>
        </div>
    </div>
</div>

<script>
    window.onload = function () {
        //1.获取事件源及相关元素
        var box = document.getElementsByClassName("box")[0];
        var inner = document.getElementsByClassName("inner")[0];
        var ul = document.getElementsByTagName("ul")[0];
        var rl = document.getElementsByClassName("rl")[0];
        var left = document.getElementsByClassName("left")[0];
        var right = document.getElementsByClassName("right")[0];
        //获取窗口宽度
        var scrollW = inner.offsetWidth;

        //2.左右按钮的显示和隐藏
        box.onmouseover = function () {
            rl.style.display = "block";
        };
        box.onmouseout = function () {
            rl.style.display = "none";
        };

        //3.点击左右按钮
        //定义一个计数器
        var index = 0;
        //点击左边按钮
        left.onclick = function () {
            index --;
            //如果现实第一个图片时,再点击左边按钮时仍让其显示第一个图片
            if (index < 0){
                index = 0;
            }
            animation(ul,- index * scrollW);
        };
        //点击右边按钮
        right.onclick = function () {
            index ++;
            //如果当前图片已经是最后一个,再点击右边按钮时始终让它展示最后一个
            if (index > ul.children.length - 1){
                index = ul.children.length - 1;
            }
            animation(ul,- index * scrollW)
        };

        //动画封装
        function animation(ele,target) {
            //清除定时器
            clearInterval(ele.timer);
            //设置步进大小
            var speed = target>ele.offsetLeft ? 10:-10;
            //设置定时器
            ele.timer = setInterval(function () {
                //先获取元素目标位置和当前位置的距离val
                var val = target - ele.offsetLeft;
                ele.style.left = ele.offsetLeft + speed + "px";
                //如果val小于步进,就使元素位置等于目标位置,并清除定时器
                if (Math.abs(val) < Math.abs(speed)){
                    ele.style.left = target + "px";
                    clearInterval(ele.timer);
                }
            },10);
        }
    }

</script>

</body>
</html>
  • 带有定时器的轮播图。
缓动(重点
  • 现象:开始走很快,越走越慢,步长越来越小,类似电梯停止。

  • 好处:

  • 有非常逼真的缓动效果,实现的动画效果更细腻;

  • 如果不清除定时器,物体永远跟着目标在移动。

  • 缓动动画公式:

  • 动画原理 = 盒子位置 + 步长(步长越来越小);

  • 盒子位置 = 盒子本身位置+(目标位置-盒子本身位置)/ 10;
    公式:position = position + (target - position) / 10;

<body>
    <button class="animate1">缓动到400</button>
    <div class="huanDong"></div>

    <script>
        var btn = document.getElementsByClassName("animate1")[0];
        var div1 = document.getElementsByClassName("huanDong")[0];

        btn.onclick = function () {
            var timer = setInterval(function () {
                div1.style.left = div1.offsetLeft + (400 - div1.offsetLeft) / 10 + "px";
                if (div1.offsetLeft == 400){
                    clearInterval(timer);
                }
            },30);
        };

    </script>
</body>
  • 但是有个现象,动画会永远执行下去,不会停止。举个例子:把一桶水,倒进另一通,每次倒一半,永远倒不完;还有个原因就是offsetLeft会将获取值四舍五入之后再进行计算。

  • 疑问:

  • 原理:数学算法。其他运动形式其他算法。

  • 为什么是10?:更符合我们的计算,逻辑,审美,人体工程学。

  • (实际工作中,领导给我们什么技术,我们就直接用,空余时间研究源码和原理)。

  • 动画封装问题:

  • 小数只能给定样式,最好不要参与计算。(offsetLeft会将获取值四舍五入取整后计算);

  • 如果正向运动,步长是大于0;如果反向运动,步长就小于0。大于0向上取整,小于0向下取整。大于0的时候向上取整能够取到1,小于零时向下取整才能取到-1(会在9和-9距离的时候出现问题)。

<body>

    <button class="bigger">缓动到400</button>
    <button class="smaller">缓动到200</button>
    <button class="zero">缓动到0</button>
    <div class="huanDong"></div>

    <script>
        var btn1 = document.getElementsByClassName("bigger")[0];
        var btn2 = document.getElementsByClassName("smaller")[0];
        var btn3 = document.getElementsByClassName("zero")[0];
        var div1 = document.getElementsByClassName("huanDong")[0];
        btn1.onclick = function () {
            animation(div1,400);
        };
        btn2.onclick = function () {
            animation(div1,200);
        };
        btn3.onclick = function () {
            animation(div1,0);
        };

        function animation(ele,target) {
            //清除定时器
            clearInterval(ele.timer);

            ele.timer = setInterval(function () {
                //计算步长
                var step = (target - ele.offsetLeft) / 10;
                step = step>0 ? Math.ceil(step):Math.floor(step);
                //移动位置
                ele.style.left = ele.offsetLeft + step + "px";
                //如果距离小于步长就将目标位置赋值给盒子,并清除定时器
                if (Math.abs(target - ele.offsetLeft)<=Math.abs(step)){
                    ele.style.left = target + "px";
                    clearInterval(ele.timer);
                }
            },30);
        }

    </script>
</body>
  • 案例:
  • 筋斗云(光标移动到对应标签时,筋斗云图片也移动到对应位置,在没有点击的情况下如果移开光标筋斗云到最初位置;如果点击了某个标签,移开光标时,筋斗云仍然停留在点击的标签处)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>筋斗云练习</title>

    <style>
        *{
            margin: 0;
            padding: 0;
        }

        body{
            background-color: rgba(0,0,0,0.2);
        }

        .tab{
            width: 600px;
            height: 42px;
            border-radius: 8px;
            background: #fff url("images/wifi.png") right center no-repeat;
            margin: 160px auto;
            position: relative;
        }
        ul{
            list-style: none;
            position: absolute;
        }
        li{
            float: left;
            width: 83px;
            height: 42px;
            text-align: center;
            line-height: 42px;
            cursor: pointer;
        }
        span[class=cloud]{
            position: absolute;
            width: 83px;
            height: 42px;
            background: url("images/cloud.gif");
        }
    </style>
</head>

<body>
    <div class="tab">
        <span class="cloud"></span>
        <ul>
            <li>家乡文化</li>
            <li>沿海风光</li>
            <li>文人骚客</li>
            <li>特色景点</li>
            <li>特产美食</li>
        </ul>
    </div>

    <script>
        //入口函数
        window.onload = function () {

            //获取事件源和一些相关的元素
            var tab = document.getElementsByClassName("tab")[0];
            var lis = document.getElementsByTagName("li");
            var liW = lis[0].offsetWidth;
            var cloud = document.getElementsByClassName("cloud")[0];

            //书写事件代码
            //定义一个计数器,移除光标和点击光标时会用到
            var count = 0;

            for(var i=0; i<lis.length; i++){
                lis[i].index = i;
                //绑定鼠标进入事件
                lis[i].onmouseover = function () {
                    //cloudAnimate(cloud,this.index * liW);
                    cloudAnimate(cloud,this.offsetLeft);
                };

                //鼠标点击事件
                lis[i].onclick = function () {
                    count = this.index;
                    cloudAnimate(cloud,count * liW);
                }
            }

            //鼠标离开事件
            tab.onmouseout = function () {
                cloudAnimate(cloud,count * liW);
            };


            //定义动画函数
            function cloudAnimate(ele,target) {
                //清空定时器
                clearInterval(ele.timer);
                //设置定时器
                ele.timer = setInterval(function () {
                    //计算步长
                    var step = (target - ele.offsetLeft) / 10;
                    step = step>0 ? Math.ceil(step):Math.floor(step);
                    //动画原理
                    ele.style.left = ele.offsetLeft + step + "px";
                    //如果距离小于等于步长,就将目标位置直接赋值,并清除定时器
                    if (Math.abs(target - ele.offsetLeft) <= Math.abs(step)){
                        ele.style.left = target + "px";
                        clearInterval(ele.timer);
                    }
                },10);
            }
        };
    </script>
</body>
</html>

冒泡

  • 事件冒泡: 当一个元素上的事件被触发的时候,比如说鼠标点击了一个按钮,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡;这个事件从原始元素开始一直冒泡到DOM树的最上层。(BUG)。(好比如:本来应该一人做事一人当,结果,我做错了事情,你去告诉我妈)

  • 事件传播阶段:

  • 事件传播的三个阶段:捕获、冒泡、目标阶段;

  • 事件捕获阶段:事件从最上一级标签开始往下查找,直到捕获到事件目标(target)【从上往下】;

  • 事件冒泡阶段:事件从事件目标开始,往上冒泡直到页面的最上一级标签【从下往上】。

  • 什么是冒泡:子元素事件被触动,父盒子的同样的事件也会被触动。

  • 冒泡顺序:

  • IE6.0:div->body->html->document;

  • 其它浏览器:div->body->html->document->window。

  • 不是所有的事件都能冒泡,以下事件不能冒泡:blur、focus、load、unload、onmouseenter、onmouseleave。

  • 取消冒泡就是取消这种机制。

阻止冒泡
  • w3c的方法是:
  • 火狐、谷歌、IE11:event.stopPropagation()
  • IE10以下:event.cancelBubble = true
  • 兼容代码如下:
var event = event || window.event;
if(event && event.stopPropagation){
    event.stopPropagation();
}else{
    event.cancelBubble = true;
}
获取当前对象
  • IE678:event.srcElement(事件源);
  • 火狐、谷歌等:event.target(事件源);,谁触动事件,谁就会被放进target中;
  • 兼容写法获取元素ID:
var event = event || window.event;
var targetId = event.target ? event.target.id:event.srcElement.id;

addEventListenner(参数1,参数2,参数3)

  • 调用者是:事件源;

  • 参数1:事件去掉on;

  • 参数2 :调用的函数

  • 参数3:可有可无。默认false,false情况下,支持冒泡,true支持捕获。

  • 案例

  • 点击空白隐藏模态框

    • Document事件的绑定,无论绑定什么事件,只要事件被出发,传递过来的应该是指定的元素本身,而不是document。
  • 事件委托

    • 先绑定,后创建的元素(没有事件);
    • 组织冒泡,组织自己向父系盒子冒泡,所有的泡冒泡到组织的位置停止向上冒泡;
    • event.target是在事件被触动的时候把事件源绑定到event的target属性中。而之前的target是我们自定义的一个变量触动。

补充

获取行内式和内嵌式属性的值

  • 行内式:div.style.width,只能获取行内式属性的值,有单位px,只包含盒子的width,不包含padding和border;

  • 内嵌式:div.offsetWidth,没有单位,包含width、padding、border。

  • 获取行内式和内嵌式属性的值:

  • window.getComputedStyle(div,null)获取到的值是对象类型:

var w = window.getComputedStyle(div,null).width;
var color = window.getComputedStyle(div, null)["backgroundColor"];
- 获取到的值有单位`px`,只包含`width`,不包含padding和border;
  • div.currentStyle.width只支持IE678:
console.log(div.currentStyle.width);
console.log(div.currentStyle.padding);
  • 兼容方式获取元素某个样式
function getStyle(ele, attr) {
    if(window.getComputedStyle){
        return window.getComputedStyle(ele,null).attr;
    }else {
        return ele.currentStyle[attr];
    }
}

缓动的高级封装

单个属性封装
  • 利用通过兼容方式获取元素某个属性的值:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>缓动封装-单个属性</title>

    <style>
        div{
            width: 100px;
            height: 100px;
            border: 5px solid #ddd;
            margin-top: 10px;
            background-color: pink;
            position: absolute;
        }
    </style>
</head>
<body>
    <button>缓动到400</button>
    <button>长度变成300</button>
    <div></div>

    <script>
        //入口函数
        window.onload = function () {
            //获取事件源
            var btns = document.getElementsByTagName("button");
            var area = document.getElementsByTagName("div")[0];
            //绑定事件
            btns[0].onclick = function () {
                animate(area,"left",400);
            };
            btns[1].onclick = function () {
                animate(area,"width",300);
            };

            //缓动动画封装
            function animate(ele,attr,target) {

                //获取元素属性的值
                var currentP = parseInt(getStyle(ele,attr)) || 200;

                //清除定时器
                clearInterval(ele.timer);
                //设置定时器
                ele.timer = setInterval(function () {
                    //计算步长
                    var step = (target - currentP) / 10;
                    step = step>0 ? Math.ceil(step):Math.floor(step);
                    //动画原理
                    currentP = currentP + step;
                    ele.style[attr] = currentP + "px";
                    //清除定时器
                    if (Math.abs(target - currentP) <= Math.abs(step)){
                        ele.style[attr] = target + "px";
                        clearInterval(ele.timer);
                    }
                },30);
            }

            //获取某个属性的值
            function getStyle(ele, attr) {
                if (window.getComputedStyle){
                    return window.getComputedStyle(ele,null)[attr];
                }else {
                    return ele.currentStyle[attr];
                }
            }
        }
    </script>
</body>
</html>
多个属性封装
  • 单次运动:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多个属性</title>

    <style>
        div{
            width: 100px;
            height: 100px;
            position: absolute;
            right: 0;
            background-color: palevioletred;
            border: 5px solid #ccc;
        }
    </style>
</head>
<body>
    <button>点击运动</button>
    <div></div>

    <script>
        window.onload = function () {
            //获取事件源和相关元素
            var btn = document.getElementsByTagName("button")[0];
            var div = document.getElementsByTagName("div")[0];

            //绑定事件
            btn.onclick = function () {
                //通过JSON修改盒子多个属性的值
                var json = {"left":200,"width":200,"height":200,"top":50};
                animate(div,json);
            };

            //封装动画
            function animate(ele,json) {
                //清除定时器
                clearInterval(ele.timer);

                //设置定时器
                ele.timer = setInterval(function () {
                    //设置一个标志,记录是否符合清除定时器的条件
                    var bool = true;
                    //遍历json
                    for (var k in json){
                        //获取元素属性值
                        var currentP = parseInt(getStyle(ele,k)) || 200;
                        //计算步长
                        var step = (json[k] - currentP) / 10;
                        step = step>0 ? Math.ceil(step):Math.floor(step);
                        currentP = currentP + step;
                        ele.style[k] = currentP + "px";
                        //记录状态
                        if (Math.abs(json[k] - currentP) > Math.abs(step)){
                            bool = false;
                        }
                    }

                    //判断是否符合清除定时器的条件
                    if (bool){
                        for (var k in json){
                            ele.style[k] = json[k] + "px";
                        }
                        clearInterval(ele.timer);
                    }
                },30);
            }

            //获取元素属性值
            function getStyle(ele, attr) {
                if (window.getComputedStyle){
                    return window.getComputedStyle(ele,null)[attr];
                }else {
                    return ele.currentStyle[attr];
                }
            }
        }
    </script>
</body>
</html>
  • 往返运动:方法一
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多个属性</title>

    <style>
        div{
            width: 100px;
            height: 100px;
            position: absolute;
            left: 30px;
            top: 40px;
            background-color: palevioletred;
            border: 5px solid #ccc;
        }
    </style>
</head>
<body>
    <button>点击运动</button>
    <div></div>

    <script>
        window.onload = function () {
            //获取事件源和相关元素
            var btn = document.getElementsByTagName("button")[0];
            var div = document.getElementsByTagName("div")[0];

            //绑定事件
            btn.onclick = function () {
                //通过JSON修改盒子多个属性的值
                var json1 = {"width":200,"height":200,"left":100,"top":150};
                var json2 = {"width":100,"height":100,"left":30,"top":40};

                animate(div,json1,json2);
            };

            //封装动画
            function animate(ele,json,fn) {
                //清除定时器
                clearInterval(ele.timer);

                //设置定时器
                ele.timer = setInterval(function () {
                    //设置一个标志,记录是否符合清除定时器的条件
                    var bool = true;
                    //遍历json
                    for (var k in json){
                        //获取元素属性值
                        var currentP = parseInt(getStyle(ele,k)) || 200;
                        //计算步长
                        var step = (json[k] - currentP) / 10;
                        step = step>0 ? Math.ceil(step):Math.floor(step);
                        currentP = currentP + step;
                        ele.style[k] = currentP + "px";
                        //记录状态
                        if (Math.abs(json[k] - currentP) > Math.abs(step)){
                            bool = false;
                        }
                    }

                    //判断是否符合清除定时器的条件
                    if (bool){
                        for (var k in json){
                            ele.style[k] = json[k] + "px";
                        }
                        clearInterval(ele.timer);
                        if (fn){
                            animate(ele,fn);
                        }
                    }
                },30);
            }

            //获取元素属性值
            function getStyle(ele, attr) {
                if (window.getComputedStyle){
                    return window.getComputedStyle(ele,null)[attr];
                }else {
                    return ele.currentStyle[attr];
                }
            }
        }
    </script>
</body>
</html>
  • 往返运动:方法二
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多个属性</title>

    <style>
        div{
            width: 100px;
            height: 100px;
            position: absolute;
            left: 30px;
            top: 40px;
            background-color: palevioletred;
            border: 5px solid #ccc;
        }
    </style>
</head>
<body>
    <button>点击运动</button>
    <div></div>

    <script>
        window.onload = function () {
            //获取事件源和相关元素
            var btn = document.getElementsByTagName("button")[0];
            var div = document.getElementsByTagName("div")[0];

            //绑定事件
            btn.onclick = function () {
                //通过JSON修改盒子多个属性的值
                var json1 = {"width":200,"height":200,"left":100,"top":150};
                var json2 = {"width":100,"height":100,"left":30,"top":40};

//                animate(div,json1,json2);
                animate(div,json1,function () {
                    animate(div,json2,function () {
                        animate(div,json1,function () {
                            animate(div,json2,function () {
                                animate(div,json1);
                            });
                        });
                    });
                });
            };

            //封装动画
            function animate(ele,json,fn) {
                //清除定时器
                clearInterval(ele.timer);

                //设置定时器
                ele.timer = setInterval(function () {
                    //设置一个标志,记录是否符合清除定时器的条件
                    var bool = true;
                    //遍历json
                    for (var k in json){
                        //获取元素属性值
                        var currentP = parseInt(getStyle(ele,k)) || 200;
                        //计算步长
                        var step = (json[k] - currentP) / 10;
                        step = step>0 ? Math.ceil(step):Math.floor(step);
                        currentP = currentP + step;
                        ele.style[k] = currentP + "px";
                        //记录状态
                        if (Math.abs(json[k] - currentP) > Math.abs(step)){
                            bool = false;
                        }
                    }

                    //判断是否符合清除定时器的条件
                    if (bool){
                        for (var k in json){
                            ele.style[k] = json[k] + "px";
                        }
                        clearInterval(ele.timer);
                        if (fn){
//                            animate(ele,fn);
                            fn();
                        }
                    }
                },30);
            }

            //获取元素属性值
            function getStyle(ele, attr) {
                if (window.getComputedStyle){
                    return window.getComputedStyle(ele,null)[attr];
                }else {
                    return ele.currentStyle[attr];
                }
            }
        }
    </script>
</body>
</html>
案例:
  • 手风琴;
  • 360开机动画。

缓动框架存在的问题:

  • 有很多属性依然无法获取值和赋值:
  • border-radius:1px 2px 3px 4px;
    • 渲染有问题,可以通过拆解来解决,解决方法如下:
div.style.borderBottomLeftRadius = "30px";
div.style.borderTopRightRadius = "30px";
  • opacity:0.5;(没有单位的)
    • 火狐、谷歌、IE9+,opacity:0.5;,内容一起透明,取值范围:0~1,但是赋值的时候一般用百分制,但最后赋值时候再除以一百,因为小数有精度问题,防止中途需要运算,使误差变大;
    • IE678(一般不研究),filter:alpha(opacity=50);,取值范围:0~100
  • background:rgba(0,0,0,0.3);
  • z-index:1;...等等。
增加了透明度和层级之后的封装
/**
 * Created by YJW on 2017/8/11.
 */

//匀速运动动画
function conSpeedAnimate(ele,distance) {
    //1.使用定时器之前必须先清除,排除多次点击越来越快的bug
    clearInterval(ele.timer);   //将定时器作为元素的属性
    //2.为了能让div可以从后往前走,要先进行判断目标位置和当前位置的大小,如果目标位置大,步进就正,否则就负
    var speed = distance>ele.offsetLeft ? 10:-10;
    ele.timer = setInterval(function () {
        var val = distance - ele.offsetLeft;   //测试发现:此句位置至关重要,不能和下一句倒换位置
        ele.style.left = ele.offsetLeft + speed + "px";

        //3.到达目标位置后,再次点击会出现抖动现象。解决方案,判断目标位置和当前位置距离是否小于步进,如果小于就让两者相等,并立刻清除定时器
        if (Math.abs(val)<Math.abs(speed)){
            ele.style.left = distance + "px";
            clearInterval(ele.timer);
        }
    },10);
}

//缓动动画
function slowMovingAnimateX(ele,targetX) {
    //清空定时器
    clearInterval(ele.timer);
    //设置定时器
    ele.timer = setInterval(function () {
        //计算步长
        var stepX = (targetX - ele.offsetLeft) / 10;
        stepX = stepX>0 ? Math.ceil(stepX):Math.floor(stepX);
        //动画原理
        ele.style.left = ele.offsetLeft + stepX + "px";
        //如果距离小于等于步长,就将目标位置直接赋值,并清除定时器
        if (Math.abs(targetX - ele.offsetLeft) <= Math.abs(stepX)){
            ele.style.left = targetX + "px";
            clearInterval(ele.timer);
        }
    },10);
}
function slowMovingAnimateY(ele,targetY) {
    //清空定时器
    clearInterval(ele.timer);
    //设置定时器
    ele.timer = setInterval(function () {
        //计算步长
        var stepY = (targetY - ele.offsetTop) / 10;
        stepY = stepY>0 ? Math.ceil(stepY):Math.floor(stepY);
        //动画原理
        ele.style.top = ele.offsetTop + stepY + "px";
        //如果距离小于等于步长,就将目标位置直接赋值,并清除定时器
        if (Math.abs(targetY - ele.offsetTop) <= Math.abs(stepY)){
            ele.style.top = targetY + "px";
            clearInterval(ele.timer);
        }
    },10);
}

//对scroll()函数进行封装
function scroll(){
    //如果window.pageYOffset存在,返回值为0~无穷大,如果不存在返回undefined。
    if (window.pageYOffset !== undefined){
        var json = {
            "top":window.pageYOffset,
            "left":window.pageXOffset
        }
        return json;
    } else if (document.compatMode === CSS1Compat) {
        return {
            "top":document.documentElement.scrollTop,
            "left":document.documentElement.scrollLeft
        }
    } else {
        return {
            "top":document.body.scrollTop,
            "left":document.body.scrollLeft
        }
    }
}

// function scroll() {
//     return {
//         "top" : window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop,
//         "left" : window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft
//     }
// }

//对clientWidth进行封装(获取盒子/body、html可视区域的宽高)
function client() {
    if (window.innerWidth !== undefined){
        return{
            "width":window.innerWidth,
            "height":window.innerHeight
        }
    }else if (document.compatMode === "CSS1Compat"){
        return{
            "width":document.documentElement.clientWidth,
            "height":document.documentElement.clientHeight
        }
    }else {
        return{
            "width":document.body.clientWidth,
            "height":document.body.clientHeight
        }
    }
}

//盒子的隐藏和显示
function show(div) {
    div.style.display = "block";
}
function hide(div) {
    div.style.display = "none";
}

//获取鼠标在盒子中的位置
function positionXY(event, div) {
    //获取鼠标的位置
    event = event || window.event;
    var pagex = event.pageX || event.clientX + scroll().left;
    var pagey = event.pageY || event.clientY + scroll().top;
    //获取鼠标在盒子中的位置,并让鼠标处在遮盖中间位置
    var positionX = pagex - content.offsetLeft;
    var positionY = pagey - content.offsetTop;

    return{
        "positionX":positionX,
        "positionY":positionY
    }
}

//缓动动画高级封装--单个属性
function animate(ele,attr,target) {
    //获取元素属性的值
    var currentP = parseInt(getStyle(ele,attr)) || 0;

    //清除定时器
    clearInterval(ele.timer);
    //设置定时器
    ele.timer = setInterval(function () {
        //计算步长
        var step = (target - currentP) / 10;
        step = step>0 ? Math.ceil(step):Math.floor(step);
        //动画原理
        currentP = currentP + step;
        ele.style[attr] = currentP + "px";
        //清除定时器
        if (Math.abs(target - currentP) <= Math.abs(step)){
            ele.style[attr] = target + "px";
            clearInterval(ele.timer);
        }
    },30);
}

//封装缓动动画----设置多个属性
function animate(ele,json,fn) {
    //清除定时器
    clearInterval(ele.timer);

    //设置定时器
    ele.timer = setInterval(function () {
        //设置一个标志,记录是否符合清除定时器的条件
        var bool = true;
        //遍历json
        for (var k in json){
            //获取元素属性值
            var currentP = parseInt(getStyle(ele,k)) || 0;
            //计算步长
            var step = (json[k] - currentP) / 10;
            step = step>0 ? Math.ceil(step):Math.floor(step);
            currentP = currentP + step;
            ele.style[k] = currentP + "px";
            //记录状态
            if (Math.abs(json[k] - currentP) > Math.abs(step)){
                bool = false;
            }
        }

        //判断是否符合清除定时器的条件
        if (bool){
            for (var k in json){
                ele.style[k] = json[k] + "px";
            }
            clearInterval(ele.timer);
            if (fn){
//                            animate(ele,fn);
                fn();
            }
        }
    },30);
}

//缓动动画封装----增加了透明度和层级
function animate(ele,json,fn) {
    //清除定时器
    clearInterval(ele.timer);

    //设置定时器
    ele.timer = setInterval(function () {
        //设置一个标志,记录是否符合清除定时器的条件
        var bool = true;
        //遍历json
        for (var k in json){
            //获取元素属性值
            var currentP;
            // 如果是opacity获取值之后需要进行特殊处理
            if (k === "opacity"){
                currentP = getStyle(ele,k)*100 || 10;
                console.log(getStyle(ele,k));
            }else {
                console.log(getStyle(ele, k) + " -- " + k);
                currentP = parseInt(getStyle(ele,k)) || 0;
            }

            //计算步长
            var step = (json[k] - currentP) / 10;
            step = step>0 ? Math.ceil(step):Math.floor(step);
            currentP = currentP + step;

            //如果是opacity需要特殊赋值处理
            if (k === "opacity"){
                ele.style[k] = currentP / 100;
                //兼容IE678
                ele.style["filter"] = "alpha(opacity=" + currentP + ")";
            }else if (k === "z-index"){
                ele.style[k] = json[k];
            }else {
                ele.style[k] = currentP + "px";
            }

            //记录状态
            if (Math.abs(json[k] - currentP) > Math.abs(step)){
                bool = false;
            }
        }

        //判断是否符合清除定时器的条件
        if (bool){
            for (var k in json){
                ele.style[k] = json[k] + "px";
            }
            clearInterval(ele.timer);
            if (fn){
                fn();
            }
        }
    },30);
}

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

推荐阅读更多精彩内容

  • offset家族三大家族和一个事件对象三大家族(offset/scroll/client)事件对象/event ...
    Yuann阅读 960评论 0 5
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,107评论 25 707
  • 年少的幼稚,年轻的思绪 曾经的鲁莽,现在的沉稳 遗憾沦为遗忘,成全那句无常 如果你还别来无恙,只怪时间不够漫长 若...
    洪孩儿阅读 256评论 3 2
  • #连续留言31天# 【引子】概念与关联->价值观->方法论 1.概念与关联->价值观->方法论,改变你“看”到的世...
    毛豆爸的破冰之旅阅读 174评论 0 1