23

image

JS特效

前言

经过前面几篇文章的讲解,相信大家已经会操作DOMBOM了。为什么前面要花那么多精力去讲DOM呢?因为在后面的学习、工作中,会大量的使用DOM操作,一个表格需要增、删、改、查,一个图片需要改变大小..等,如果你想要动态的改变这些,必须要学会使用DOM

为了巩固前面的知识点,并且能够熟练地使用它们,这里单独写了一篇《JavaScript 进阶知识 - 特效篇》。本篇文章作为进阶篇很重要,不单单是对前面知识点的运用,期间也会有大量的新知识点注入,所以希望小伙伴们继续加油,认真阅读。

在本篇文章中主要会讲解一些案例,比如我们平时在页面中碰到的一些特效,一些动画效果。

注意: 所有的案例都在这里链接: 提取密码密码: 70ny,文章中的每个案例后面都有对应的序号。

1. offset 系列

offset系列用于用于获取元素自身的大小和位置,在网页特效中有广泛应用。offset系列主要有:offsetHeightoffsetWidthoffsetParentoffsetLeftoffsetTop

1.1 offsetWidth 和 offsetHeight

offsetWidthoffsetHeight获取的是元素的真实宽高
  • 获取的是元素真实的高度和宽度
  • 获取到的是数值类型,方便计算
  • offsetHeightoffsetWidth是只读属性,不能设置。

示例代码:获取一个盒子的真实宽高 [01-offset系列-offsetWidth&Height.html]

<!-- 样式部分 -->
<style>
    div {
        width: 200px;
        height: 100px;
        background-color: pink;
        padding: 10px;
        border: 10px solid salmon;
        margin: 10px;
    }
</style>

<!-- html 部分 -->
<div id="box"></div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    // offsetWidth是一个通过计算后得到的值, padding + border + width
    console.log(box.offsetWidth);   // 240
    console.log(box.offsetHeight);  // 140
</script>

offsetWidth是一个通过计算后得到的值, padding + border + width

思考: 之前我们不是也可以通过style来获取样式吗?他们有什么不同

style.heightstyle.width只能获取到行内样式里的widthheight
  • 获取的是字符串类型,还需要转换成数值类型
  • 写在css样式里的宽高是获取不到的,只能获取行内样式

总结:

  • 设置宽度高度使用style.widthstyle.height
  • 获取宽度和高度offsetWidthoffsetHeight
  • offset获取的宽高包括paddingborder

1.2 offsetParent

parentNodeoffsetParent
  • parentNode始终是父元素
  • offsetParent是离当前元素最近的定位元素(absoluterelative),如果没有,那就找body

示例代码: [02-offset系列-offsetParent.html]

<!-- 样式部分 -->
<style>
    #father {
        width: 500px;
        height: 500px;
        background-color: #FF9F68;
    }
    
    #son {
        width: 300px;
        height: 300px;
        background-color: #FEFF89;
    }
    
    #grandson {
        width: 100px;
        height: 100px;
        background-color: #AC005D;
        position: absolute;
        left: 100px;
        right: 100px;
    }
</style>

<!-- html 部分 -->
<div id="father">
    <div id="son">
        <div id="grandson"></div>
    </div>
</div>

<!-- js 部分 -->
<script>
    var grandSon = document.getElementById("grandson");
    // 找父节点  亲爹
    console.log(grandSon.parentNode);           // 返回<div id="son"></div>
    // 找最近有定位的爹,如果找不到,会找body
    console.log(grandSon.offsetParent);         // 返回<body></body>
</script>

1.3 offsetLeft与offsetTop

offsetLeft: 自身左侧到offsetParent左侧的距离:left + margin

offsetTop: 自身顶部到offsetParent顶部的距离 : top + margin

  • 元素自身与offsetParent真实的距离
  • 获取到的是数值类型,方便计算
  • 只读属性,只能获取,不能设置

示例代码:获取一个盒子距父盒子的距离 [03-offset系列-offsetTop&Left.html]

<!-- 样式部分 -->
<style>
    #father {
        width: 400px;
        height: 400px;
        background-color: pink;
        position: relative;
        margin-left: 100px;
    }
    
    #son {
        width: 200px;
        height: 200px;
        background-color: skyblue;
        position: absolute;
        left: 100px;
        margin: 20px;
    }
</style>

<!-- html 部分 -->
<div id="father">
    <div id="son"></div>
</div>

<!-- js 部分 -->
<script>
    //offsetLeft与offsetTop
    var son = document.getElementById("son");
    console.log(son.offsetLeft);    // 120
    console.log(son.offsetTop);     // 20
</script>

思考: 之前我们不是也可以通过style来获取样式吗?他们有什么不同

style.topstyle.left只能获取到行内样式里的topleft
  • 获取的是字符串类型,还需要转换成数值类型
  • 写在css样式里的宽高是获取不到的,只能获取行内样式

总结:

  • 设置定位left/top使用style.leftstyle.top
  • 获取定位left/top使用offsetLeftoffsetTop
  • offset获取的位置包括margin
  • 如果父元素没有定位,获取的就是相对body

一张图看清offset系列

image

2. 匀速动画框架

2.1 匀速动画初体验

如何让一个物体动起来?动画函数的实现原理其实就是利用间歇定时器每隔一段时间执行一次的原理实现的。

1、让一个物体动起来

点击按钮让一个盒子匀速往右执行一段距离:[04-匀速动画初体验(一).html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    
    #box {
        width: 100px;
        height: 100px;
        background-color: hotpink;
        position: absolute;
    }
</style>

<!-- html 部分 -->
<button id="btn">奔跑吧</button>
<div id="box"></div>

<!-- js 部分 -->
<script>
    var btn = document.getElementById('btn');
    var box = document.getElementById('box');

    btn.onclick = function() {
        setInterval(function() {
            // 定义一个距离 相当于每一次要跑的距离 step
            var step = 5;
            // 定义一个当前位置 leader
            var leader = box.offsetLeft;
            // 每次执行的时候 让leader都走step距离
            leader = leader + step;
            // 将距离赋值给box
            box.style.left = leader + "px";
        // 每15ms 就执行一次 人眼视觉停留 就有动画效果了
        }, 15);
    }
</script>

效果图:

image

BUG: 不知道细心的小伙伴有没有发现两个问题

  • 现在执行的时候是不会停下来的,一直往右跑
  • 点击按钮之后再去点击,会发现,按钮点击次数越多,盒子速度越快

2、让一个物体动起来,解决bug

我们让盒子运动到500px的位置停下来 [05-匀速动画初体验(二).html]

var btn = document.getElementById('btn');
var box = document.getElementById('box');
var timer = null;
/**
    为什么会越点越快?
    点击一次就会调用一次定时器,点击的次数越多,调用的就越多
    距离叠加的就会越来越大 视觉效果上看起来就跑的越来越快
    只要在每次点击后,定时器执行前清除上一次定时器,就不会出现越点越快的效果了
*/
btn.onclick = function() {
    // 一进来就清除定时器
    clearInterval(timer);
    timer = setInterval(function() {
        // 定义一个距离 相当于每一次要跑的距离 step
        var step = 5;
        // 定义一个当前位置 leader
        var leader = box.offsetLeft;
        /**
            当移动的位置在500px内的时候,执行动画函数
            否则就清除定时器,让盒子停下来
        */
        if (leader < 500) {
            // 每次执行的时候 让leader都走step距离
            leader = leader + step;
            // 将距离赋值给box
            box.style.left = leader + "px";
        } else {
            clearInterval(timer);
        }
    }, 15);
}

效果图:

image

总结:

  • setInterval间歇定时器,如果不手动清除,它就会一直运行下去
  • 点击事件触发定时器一定要注意,一进来就清除一次,否则会越点越快

2.2 匀速动画函数封装

函数需要独立,就不能使用全局变量。timer之前是一个全局变量,如果不独立,页面只有一个定时器在运作。封装的函数里将timer绑定给调用定时器的元素,这样就独立了。

1、封装一个动画函数 [06-封装一个匀速动画函数.html]

<!-- html 部分 -->
<button id="btn">奔跑吧,500</button>
<button id="btn2">奔跑吧,1000</button>
<div id="box"></div>

<!-- js 部分 -->
<script>
    var btn = document.getElementById('btn');
    var btn2 = document.getElementById('btn2');
    var box = document.getElementById('box');

    /**
        既然是封装的函数,有些不确定的,经常变的元素就要提出来
        比如:  1.每一次改变的距离 num
               2.调用动画的对象 box ==> element  
               3.运动的目标距离 500 ==> target
    */
    // 封装一个动画函数
    function animate(element, target,num) {
        // 一进来就清除定时器
        // 函数需要独立,就不能使用全局变量 所以将timer绑定在element上
        clearInterval(element.timer);
        element.timer = setInterval(function() {
            // 定义一个距离 相当于每一次要跑的距离 step
            var step = num;
            // 定义一个当前位置 leader
            var leader = element.offsetLeft;
            if (leader < target) {
                // 每次执行的时候 让leader都走step距离
                leader = leader + step;
                // 将距离赋值给box
                element.style.left = leader + "px";
            } else {
                clearInterval(element.timer);
            }
        }, 15);

    }
    // 点击按钮1 移动到500px的位置
    btn.onclick = function() {
        animate(box, 500, 9);
    }

    // 点击按钮2 移动到1000px的位置
    btn2.onclick = function() {
        animate(box, 1000, 5);
    }
</script>

注意: 上面的案例我们只是简单的实现了一个动画的封装效果,但是作为一个以后会经常用的函数,上面的代码还有很多需要优化的地方

  • 1、上面的函数只能往正方向跑,也就是说去到1000,想让它回到500是不好实现的;
  • 2、如果每次走的距离是5,目标距离是500,正好能整除。假如每次走的是9呢?每次走9,是不能被500整除的,所以最后停下里的距离会偏多一点。image

2、封装一个动画函数完整版 [07-封装一个匀速动画函数完整版.html]

  • 先说说第二个问题,距离的问题。如果走的距离不能被目标距离整除的话,最后会多出来一点距离,我们可以不用管这个距离,直接在清除定时器,停下里的时候让它的距离等于目标距离。
clearInterval(element.timer);        // 清除前位置在504,直接在下面设置让它位置等于500
element.style.left = target + "px";  // 500
  • 现在说说第一个问题,盒子到1000的时候不能回到500。假设现在盒子在1000,我们点击按钮1的时候想要让他回到500,这个时候我们可以发现时的leader = 1000,目标距离target为500,就是说当leader>target的时候,盒子是可以往回走的,这时候只要将步数设置为负数 ,盒子就是往回跑的。
var leader = element.offsetLeft;
// 当目标距离大于当前位置 说明往正方向走 step的值就是正的
var step = target > leader? 9 : -9;
  • 此时就不能再根据 if (leader < target){}, else { clearInterval(element.timer); }去判断,让盒子运动了。这时的判断条件应该是目标距离target 与盒子目前距离leader之间差的绝对值大于等于一步距离step绝对值的时候,让他们执行leader = leader + step;否则的话清除清除定时器,并将最后的距离直接设置为target的距离。
var distance = Math.abs(target - leader);
// 通过判断此时的差如果大于或者等于一步的距离step的时候,就应该执行动画
if (distance >= Math.abs(step)) {
    leader = leader + step;
    element.style.left = leader + "px";
}

完整代码:

<!-- html 部分 -->
<button id="btn">奔跑吧,500</button>
<button id="btn2">奔跑吧,1000</button>
<div id="box"></div>

<!-- js 部分 -->
<script>
    var btn = document.getElementById('btn');
    var btn2 = document.getElementById('btn2');
    var box = document.getElementById('box');

    function animate(element, target, num) {
        clearInterval(element.timer);
        element.timer = setInterval(function() {
            var leader = element.offsetLeft;
            // 判断此时每次走的距离,当目标距离大于当前位置 说明往正方向走 step的值就是正的
            var step = target > leader ? num : -num;
            // 获得此时的距离 与目标距离的差的绝对值
            var distance = Math.abs(target - leader);
            // 通过判断此时的差如果大于或者等于一步的距离step的时候,就应该执行动画
            if (distance >= Math.abs(step)) {
                leader = leader + step;
                element.style.left = leader + "px";
            } else {
                // 否则清除动画,并且将最后的距离设置为target的距离
                clearInterval(element.timer);
                element.style.left = target + "px";
            }
        }, 15);

    }
    btn.onclick = function() {
        animate(box, 500, 9);
    }

    btn2.onclick = function() {
        animate(box, 1000, 5);
    }
</script>

效果图:

image

如上,这就是封装的一个完美的动画函数了,下次有需要用到动画的地方,直接引用即可——[ js/animate.js ]

3. 轮播图

基本上每个网站都会用到轮播图,轮播图的使用可以说是必不可少的。以后我们用的最多的可能是插件,原生的可能并不常用,但是轮播图的原理我们必须知道,并且能够写出来。(之前一次面试就是让我讲出轮播图的具体实现步骤)

3.1 简单轮播图

现在我们先来学习下简单的轮播图实现原理。

轮播图样式的特点:

  • ul要足够的宽,要求能够一行放下所有的li
  • 父盒子的宽高和图片的宽高一样
  • 父盒子要有一个overflow:hidden ,仅显示一张图片,不多不少

要求ul很宽很宽,因为所有的li要左浮动,要保证所有的li在一行上显示,定义一个盒子,盒子的宽高要和显示的单张图片宽高一样,然后设置overflow:hidden这样其他的li就会被隐藏在下面,通过改变ul的位置就能实现图片的切换了

示例代码: [08-实现简单的轮播图.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    #slide {
        width: 560px;
        height: 315px;
        margin: 100px auto;
        position: relative;
        overflow: hidden;
    }
    #slide ul {
        width: 600%;
        position: absolute;
    }
    #slide ul li {
        float: left;
    }
    #slide ul img {
        display: block;
    }
    #slide ol {
        width: 100px;
        height: 14px;
        background-color: rgba(255, 255, 255, .6);
        /* background-color: pink; */
        position: absolute;
        bottom: 14px;
        left: 50%;
        margin-left: -50px;
        border-radius: 7px;
    }
    #slide ol li {
        width: 10px;
        height: 10px;
        float: left;
        background-color: #fff;
        border-radius: 50%;
        margin-top: 2px;
        margin-left: 8.5px;
        cursor: pointer;
    }
    #slide ol li.current {
        background-color: #DF654A;
    }
</style>

<!-- html 部分 -->
<div id="slide">
    <ul>
        <li>
            <a href="#"><img src="../image/1.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/2.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/3.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/4.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/5.jpg" alt=""></a>
        </li>
    </ul>

    <ol>
        <li class="current"></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ol>
</div>

<!-- js 部分 -->
<script src="../js/animate.js"></script>
<script>
    var slide = document.getElementById('slide');
    var ul = slide.children[0];
    var ol = slide.children[1];
    // ol 下的 li 小圆点
    var lis = ol.children;
    var imgWidth = slide.offsetWidth;
    // 给所有的小圆点注册点击事件
    for (var i = 0; i < lis.length; i++) {
        lis[i].index = i;
        lis[i].onclick = function() {
            // 小圆点高亮排他
            for (var i = 0; i < lis.length; i++) {
                lis[i].className = "";
            }
            this.className = "current";
            // 点击小圆点,让对应的图片轮播 获取ul要改变的距离
            // 负的表示ul 向左运动 此时小圆点对应的索引乘以盒子的宽度 就是ul要移动的宽度
            var target = -this.index * imgWidth;
            // ul.style.left = target + 'px';
            // 让图片像动画一样慢慢的移过去
            animate(ul, target, 50);
        }

    }
</script>

效果图:

image

从上面效果图中,我们可以看到,一个最简单的轮播图已经成型了,但是需要去用手点击,而且如果跨点数去点击,会发现图片要一张张滑过去,这里后面我们会优化。

3.2 左右焦点轮播图

左右焦点轮播图,就是在显示图片的两端添加两个按钮,一个向左,一个向右,点击的时候图片会根据点击的方向滑动。并且当鼠标悬停在显示区域的时候,两个按钮显示。鼠标离开显示区域,,两个按钮隐藏。

示例代码: [09-左右焦点轮播图.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    
    #slide {
        width: 560px;
        height: 315px;
        margin: 100px auto;
        position: relative;
        overflow: hidden;
    }
    
    #slide ul {
        width: 600%;
        position: absolute;
    }
    
    #slide ul li {
        float: left;
    }
    
    #slide ul img {
        display: block;
    }
    
    #slide #arrow {
        display: none;
    }
    
    #slide #arrow #leftArr,
    #slide #arrow #rightArr {
        width: 30px;
        height: 60px;
        background-color: rgba(255, 255, 2550, 0.3);
        position: absolute;
        top: 50%;
        margin-top: -30px;
        text-decoration: none;
        color: #fff;
        text-align: center;
        font: 700 24px/60px "宋体";
    }
    
    #slide #arrow #leftArr {
        left: 0;
    }
    
    #slide #arrow #rightArr {
        right: 0;
    }
</style>

<!-- html 部分 -->
<div id="slide">
    <ul>
        <li>
            <a href="#"><img src="../image/1.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/2.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/3.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/4.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/5.jpg" alt=""></a>
        </li>
    </ul>

    <div id="arrow">
        <a href="javascript:void(0);" id="leftArr">&lt;</a>
        <a href="javascript:void(0);" id="rightArr">&gt;</a>
    </div>
</div>

<!-- js 部分 -->
<script src="../js/animate.js"></script>
<script>
    var slide = document.getElementById('slide');
    var ul = slide.children[0];
    var lis = ul.children;
    var arrow = document.getElementById('arrow');
    var leftArr = document.getElementById("leftArr");
    var rightArr = document.getElementById("rightArr");

    var imgWidth = slide.offsetWidth;

    // 给slide注册鼠标经过事件,鼠标经过时 显示arrow
    slide.onmouseover = function() {
        arrow.style.display = "block";
    };
    // 给slide注册鼠标离开事件,鼠标离开时 隐藏arrow
    slide.onmouseout = function() {
        arrow.style.display = "none";
    };
    // 点击右箭头
    var count = 0; // 跑出去的张数
    rightArr.onclick = function() {
        // 当这个张数不等于最后一张的时候 执行动画
        if (count < lis.length - 1) {
            count++;
            var target = -count * imgWidth;
            animate(ul, target, 40);
        }
    }
    leftArr.onclick = function() {
        // 当这个张数不等于最后一张的时候 执行动画
        if (count > 0) {
            count--;
            var target = -count * imgWidth;
            animate(ul, target, 40);
        }
    }
</script>

效果图:

image

3.3 无缝轮播图

上图可以看到,当滑到最左边或者最右边的时候,再点击就没有用了,正常的轮播图肯定不是这样的,点击到最后一张后再点击肯定是接着滑动的。下面我们接着看,如何实现一个无缝轮播图

示例代码:无缝轮播(可以一直点击) [10-左右焦点轮播图-无缝滚动.html]

何谓无缝滚动?

无缝滚动就是图片能够循环切换,就算是最后一张,点击之后也会跳到第一张

原理:

  • 效果就像上面所说的一样,主要实现原理就是,在最后面一张图片,再加上一张图片,这张图片就是第一张图片
  • 当滑动到最后一张图片的时候(看下图),此时的视觉效果就是停在第一张图片上
  • 这时只需要在程序上判断,当在最后一张的时候,直接跳到第一张图片即可

image

示例代码:无缝滚动的简单原理 [10-无缝滚动原理.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    
    #slide {
        position: relative;
        width: 560px;
        height: 315px;
        border: 6px dashed #CBF078;
        margin: 100px auto;
        overflow: hidden;
    }
    
    #slide ul {
        width: 3360px;
        position: absolute;
        left: 0;
        top: 0;
    }
    
    #slide ul li {
        float: left;
    }
    
    #slide ul li img {
        display: block;
        vertical-align: top;
    }
</style>

<!-- html 部分 -->
<div id="slide">
    <ul>
        <li>
            <img src="../image/1.jpg" alt="">
        </li>
        <li>
            <img src="../image/2.jpg" alt="">
        </li>
        <li>
            <img src="../image/3.jpg" alt="">
        </li>
        <li>
            <img src="../image/4.jpg" alt="">
        </li>
        <li>
            <img src="../image/5.jpg" alt="">
        </li>
        <!-- 添加一张与第一张一模一样的图片 障眼法 -->
        <li>
            <img src="../image/1.jpg" alt="">
        </li>
    </ul>
</div>

<!-- js 部分 -->
<script>
    var slide = document.getElementById('slide');
    var ul = slide.children[0];
    setInterval(function() {
        // 每次向左移动的距离
        var step = -3;
        // 获取 ul的left的值 是个负值
        var leader = ul.offsetLeft;
        // 定义一个目标距离,这里的目标距离指的是最后一张图片距离左边的left值
        // 图片宽度560 在最后一张距离左边left的位置:-560*5 = -2800
        // 就是说当到达这张图片的时候就应该让  ul.style.left = "0px";
        var target = -2800;
        // 为什么不直接判断 leader = -2800的时候让ul.style.left = "0px";?
        // 因为每次走3步 3不能被2800整除,所以leader永远不会等于-2800的
        // 这里直接判断leader此时距左边的距离减去目标距离当这个绝对值大于等于 一步距离的绝对值3的时候让它执行往左运动
        if (Math.abs(leader - target) >= Math.abs(step)) {
            leader = leader + step;
            ul.style.left = leader + "px";
        // 当不足一步距离的时候说明就是最后一张了,就应该跳到第一张图片了
        } else {
            ul.style.left = "0px";
        }
    }, 15);
</script>

效果图:

image

左右焦点无缝轮播图: [11-左右焦点无缝轮播图.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    
    #slide {
        width: 560px;
        height: 315px;
        margin: 100px auto;
        position: relative;
        overflow: hidden;
    }
    
    #slide ul {
        width: 600%;
        position: absolute;
    }
    
    #slide ul li {
        float: left;
    }
    
    #slide ul img {
        display: block;
    }
    
    #slide #arrow {
        display: none;
    }
    
    #slide #arrow #leftArr,
    #slide #arrow #rightArr {
        width: 30px;
        height: 60px;
        background-color: rgba(255, 255, 2550, 0.3);
        position: absolute;
        top: 50%;
        margin-top: -30px;
        text-decoration: none;
        color: #fff;
        text-align: center;
        font: 700 24px/60px "宋体";
    }
    
    #slide #arrow #leftArr {
        left: 0;
    }
    
    #slide #arrow #rightArr {
        right: 0;
    }
</style>

<!-- html 部分-->
<div id="slide">
    <ul>
        <li>
            <a href="#"><img src="../image/1.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/2.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/3.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/4.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/5.jpg" alt=""></a>
        </li>
        <!-- 添加一张图片 障眼法 -->
        <li>
            <a href="#"><img src="../image/1.jpg" alt=""></a>
        </li>
    </ul>

    <div id="arrow">
        <a href="javascript:void(0);" id="leftArr">&lt;</a>
        <a href="javascript:void(0);" id="rightArr">&gt;</a>
    </div>
</div>

<!-- js 部分 -->
<script src="../js/animate.js"></script>
<script>
    var slide = document.getElementById('slide');
    var ul = slide.children[0];
    var lis = ul.children;
    var arrow = document.getElementById('arrow');
    var leftArr = document.getElementById("leftArr");
    var rightArr = document.getElementById("rightArr");

    var imgWidth = slide.offsetWidth;

    // 给slide注册鼠标经过事件,鼠标经过时 显示arrow
    slide.onmouseover = function() {
        arrow.style.display = "block";
    };
    // 给slide注册鼠标离开事件,鼠标离开时 隐藏arrow
    slide.onmouseout = function() {
        arrow.style.display = "none";
    };
    // 点击右箭头
    var count = 0; // 跑出去的张数
    rightArr.onclick = function() {
        // 当这个张数等于最后一张的时候,偷偷摸摸的把最后一张图片换成第一张
        if (count == lis.length - 1) {
            count = 0;
            ul.style.left = 0;
        }
        count++;
        var target = -count * imgWidth;
        animate(ul, target, 40);
    }
    leftArr.onclick = function() {
        // 判断是第一张的时候,偷偷摸摸的把第一张换成最后一张
        if (count == 0) {
            count = lis.length - 1;
            ul.style.left = -count * imgWidth + "px";
        }
        count--;
        var target = -count * imgWidth;
        animate(ul, target, 40);
    }
</script>

效果图:

image

3.4 完整版轮播图

前面我们已经可以通过点击对应的小点、左右焦点和无缝滚动来实现轮播图了,不过都是单独分开来的,现在我们做个整合,实现一个完整的轮播图。

功能概述:

  • 简单轮播功能

    • circle下的所有的li注册点击事件
    • 排他
    • 移动Ul
  • 左右焦点功能

    • 需要定义一个变量count来记录移动的图片的张数。
  • 点击右箭头功能

    • 如果当前图片是最后一张(假图片),需要瞬间变成真图片
    • 点击一次,需要让图片往右移动一张
    • 同步小圆点,干掉所有小圆点,复活对应count的小圆点。
    • 最后一张假图片对应的小圆点是第一个,需要做特殊处理
点击左箭头的功能和右箭头基本一致。
  • 自动轮播的功能

    • 开启定时器,每隔两秒点击一次右箭头
    • 鼠标经过盒子,停止定时器(箭头乱闪的问题解释)触发事件的一定要是外面的大盒子,不能是ul,如果给ul注册事件,就会出现乱闪的问题
    • 鼠标离开盒子,开启定时器
  • 同步功能

    • 点击小圆点时需要同步
    • 淘宝bug解决方法(当一圈过后回到第一个小圆点的时候,再点击它会发现他会再跑一圈)
  • 淘宝bug图:

淘宝bug

完整代码: [12-完整版轮播图.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    
    #slide {
        width: 560px;
        height: 315px;
        margin: 100px auto;
        position: relative;
        overflow: hidden;
    }
    
    #slide ul {
        width: 600%;
        position: absolute;
    }
    
    #slide ul li {
        float: left;
    }
    
    #slide ul img {
        display: block;
    }
    
    #slide #arrow {
        display: none;
    }
    
    #slide #arrow #leftArr,
    #slide #arrow #rightArr {
        width: 30px;
        height: 60px;
        background-color: rgba(0, 0, 0, 0.5);
        position: absolute;
        top: 50%;
        margin-top: -30px;
        text-decoration: none;
        color: #fff;
        text-align: center;
        font: 700 24px/60px "宋体";
    }
    
    #slide #arrow #leftArr {
        left: 0;
    }
    
    #slide #arrow #rightArr {
        right: 0;
    }
    
    #slide ol {
        width: 100px;
        height: 14px;
        background-color: rgba(255, 255, 255, .6);
        /* background-color: pink; */
        position: absolute;
        bottom: 14px;
        left: 50%;
        margin-left: -50px;
        border-radius: 7px;
    }
    
    #slide ol li {
        width: 10px;
        height: 10px;
        float: left;
        background-color: #fff;
        border-radius: 50%;
        margin-top: 2px;
        margin-left: 8.5px;
        cursor: pointer;
    }
    
    #slide ol li.current {
        background-color: #DF654A;
    }
</style>

<!--html 部分-->
<div id="slide">
    <ul>
        <li>
            <a href="#"><img src="../image/1.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/2.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/3.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/4.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/5.jpg" alt=""></a>
        </li>
        <li>
            <a href="#"><img src="../image/1.jpg" alt=""></a>
        </li>
    </ul>
    <!-- 左右箭头 -->
    <div id="arrow">
        <a href="javascript:void(0);" id="leftArr">&lt;</a>
        <a href="javascript:void(0);" id="rightArr">&gt;</a>
    </div>
    <!-- 小圆点 -->
    <ol id="circleOl">
        <li class="current"></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ol>
</div>


<script src="../js/animate.js"></script>
<script>
    // 自执行函数,防止页面其他定时器会受影响
    (function() {
        var slide = document.getElementById('slide');
        var imgUl = slide.children[0];
        var imgLis = imgUl.children;
        var arrow = document.getElementById('arrow');
        var leftArr = document.getElementById("leftArr");
        var rightArr = document.getElementById("rightArr");
        var circleOl = document.getElementById('circleOl');
        var circleLis = circleOl.children;
        // 获取图片的宽度
        var imgWidth = slide.offsetWidth;
        var timer = null;

        // 点击小圆点改变对应图片
        for (var i = 0; i < circleLis.length; i++) {
            circleLis[i].index = i;
            circleLis[i].onclick = function() {
                // 小圆点点击的时候高亮排他
                for (var i = 0; i < circleLis.length; i++) {
                    circleLis[i].className = "";
                }
                this.className = "current";
                // 淘宝bug:这时还需要判断一下 就是当图片在最后一张假图片的时候,
                // 再去点击第一个小圆点的时候,会出现一个bug,就是图片会轮播一圈再回到这张图片上
                if (count == imgLis.length - 1) {
                    count = 0;
                    imgUl.style.left = 0;
                }
                // 点击小圆点图片要移动
                var target = -this.index * imgWidth;
                // 如果这里不记录一下,当点击小圆点跳到某张图片的时候,再自动播放的时候,
                // 不会接着当前小圆点的位置往后播放,而是接着之前count不变的情况下 继续播放的
                count = this.index;
                animate(imgUl, target, 40);
            }
        }

        // 左右焦点轮播图
        var count = 0; // 跑出去的张数
        rightArr.onclick = function() {
            // 当这个张数等于最后一张(假图片)的时候,偷偷摸摸的把最后一张图片换成第一张
            if (count == imgLis.length - 1) {
                count = 0;
                imgUl.style.left = 0;
            }
            // 点击一次图片向右划动一次
            count++;
            var target = -count * imgWidth;
            animate(imgUl, target, 40);

            //让小圆点跟着动 只要将 count 与小圆点绑定即可
            for (var i = 0; i < circleLis.length; i++) {
                circleLis[i].className = "";
            }
            // 这里需要判断一下 因为此时最后一张是假图片 小圆点是不能正常跳转到第一个的
            // 当count == 最后一张图片的下标的时候,直接让第一个小圆点亮
            if (count == imgLis.length - 1) {
                circleLis[0].className = "current";
            } else {
                // 否则其他的下标对应的小圆点高亮
                circleLis[count].className = "current";
            }
        }
        leftArr.onclick = function() {
            // 判断是第一张的时候,偷偷摸摸的把第一张换成最后一张
            if (count == 0) {
                count = imgLis.length - 1;
                imgUl.style.left = -count * imgWidth + "px";
            }
            count--;
            var target = -count * imgWidth;
            animate(imgUl, target, 40);

            // 小圆点同步 往左的时候不会出现小圆点不同步的问题
            for (var i = 0; i < circleLis.length; i++) {
                circleLis[i].className = "";
            }
            circleLis[count].className = "current";
        }

        timer = setInterval(function() {
            rightArr.onclick();
        }, 2000);
        // 给slide注册鼠标经过事件,鼠标经过时 显示arrow
        slide.onmouseover = function() {
            arrow.style.display = "block";
            // 鼠标经过图片的时候清除定时器,停止轮播
            clearInterval(timer);
        };
        // 给slide注册鼠标离开事件,鼠标离开时 隐藏arrow
        slide.onmouseout = function() {
            arrow.style.display = "none";
            // 鼠标离开图片的时候开启定时器,自动轮播
            timer = setInterval(function() {
                rightArr.onclick();
            }, 2000);
        };

    })()

轮播图的一些功能可能有点绕,写的有可能不太看得懂,有疑惑的小伙伴直接私信我,我会一步步解释给你听的,有什么bug也可以将代码发给我。

想为后面打下扎实的基础的话,这里一定要多敲几遍,主要要搞明白的是实现的思路,以及一些小bug的解决。这对后面的学习是非常重要的。代码备注可能读起来有些拗口,不懂得小伙伴直接私信给我。

4. 缓动动画框架

4.1 缓动动画初体验

缓动动画,顾名思义,就是越来越慢的运动。

我们先来回顾一下上面匀速动画运动的原理:

/**
    step : 一步的距离
    leader :当前的距离
    我们可以看到 step在这里一直等于5  不曾改变 所以就是匀速运动
*/
var step = 5;
leader = leader + step;

现在我们再来看下缓动动画的原理:

/** 
    target: 目标距离,盒子运动到什么地方
    step  : 同样的,还是指每次运动的距离,但是这里的步数是一个变化的量了,
             我们可以看到它会随着leader的增加变得越来越小,这就是缓动动画的原理
    leader: 当前的距离
*/
var step = (target - leader)/10;
leader = leader + step;

示例代码: [13-缓动动画初体验(一).html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    
    #box {
        width: 100px;
        height: 100px;
        position: absolute;
        background: orange;
    }
</style>

<!-- html 部分-->
<input type="button" value="奔跑吧" id="btn">
<div id="box"></div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    var btn = document.getElementById('btn');
    var timer = null;

    btn.onclick = function() {
        clearInterval(timer);
        timer = setInterval(function() {
            // 定义一个目标距离
            var target = 600;
            // 获得当前盒子的位置
            var leader = box.offsetLeft;
            // 每次运动的距离
            var step = (target - leader) / 10;
            // leader = leader + step  动起来
            leader += step;
            // 将距离给盒子
            box.style.left = leader + "px";
            // 当当前距离等于目标距离的时候清除定时器
            if (leader == target) {
                clearInterval(timer);
            }
        }, 15);
    }
</script>

效果图:

image

完美了吗?并没有,这里有个小bug:

可能会有小伙伴不理解,有问题你上面直接讲一下不就得了,还特地卖关子在下面重新写一遍。我想跟大家说的一点就是,如果在上面我直接告诉你这里有个问题有个bug的话,你一眼看过,可能都不当回事,我在这里拿出来讲一下,相信这个知识点你会记得更深。

小bug:明明设置的是600,怎么会是596.4px呢?

image

原因:

  • offsetLeft获取值的时候,只会获取整数,会对小数部分会四舍五入处理,比如step = (target - leader)/10step的值出现小数的时候,leader+= step之后,offsetLeft在获取leader位置的时候就会把小数部分四舍五入,这样就会造成最后距离的误差。

解决方法:

  • step向上取整处理(Math.ceil()),保证每一次都至少跑1px的距离,只要不出现小数offsetLeft就不会出现四舍五入。

完整代码: [14-缓动动画初体验(二).html]

var box = document.getElementById('box');
var btn = document.getElementById('btn');
var timer = null;

btn.onclick = function() {
    clearInterval(timer);
    timer = setInterval(function() {
        // 定义一个目标距离
        var target = 600;
        // 获得当前盒子的位置
        var leader = box.offsetLeft;
        // 每次运动的距离
        var step = (target - leader) / 10;
        // 对step进行向上取整
        step = Math.ceil(step);
        // leader = leader + step  动起来
        leader += step;
        // 将距离给盒子
        box.style.left = leader + "px";
        // 当当前距离等于目标距离的时候清除定时器
        if (leader == target) {
            clearInterval(timer);
        }
    }, 15);
}

4.2 缓动动画函数封装

前面匀速动画那里已经讲过封装一个函数的好处与重要性,现在我们将缓动动画也封装成一个函数。

示例代码: [15-缓动动画函数封装.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
    }
    
    #box {
        width: 100px;
        height: 100px;
        background: orange;
        position: absolute;
    }
</style>

<!-- html 部分 -->
<input type="button" value="奔跑吧500" id="btn1">
<input type="button" value="奔跑吧1000" id="btn2">
<div id="box"></div>

<!-- js 部分 -->
<script>
    var btn1 = document.getElementById('btn1');
    var btn2 = document.getElementById('btn2');
    var box = document.getElementById('box');
    
    // 缓动动画函数
    /**
        element : 执行动画元素
        target  : 目标距离
        num     : 用来控制动画执行的速度 越大动画执行越慢
    */
    function slowAnimate(element, target, num) {
        // 一进来就要清除定时器,防止越点越快
        clearInterval(element.timer);
        element.timer = setInterval(function() {
            // 获得元素当前位置
            var leader = element.offsetLeft;
            // 定义每次运动的距离
            var step = (target - leader) / num;
            // step可能是小数 所以要取整
            step = Math.ceil(step);
            leader += step;
            // 设置元素的位置
            element.style.left = leader + 'px';
            // 当元素的位置 等于 目标位置的时候 清除定时器
            if (leader == target) {
                clearInterval(element.timer);
            }
        }, 15);
    }
    // 调用缓动动画函数
    btn1.onclick = function() {
        slowAnimate(box, 500, 10);
    }
    // 同样是运动500的距离,我们可以发现从500到1000,明显执行的比从0-500执行的慢
    btn2.onclick = function() {
        slowAnimate(box, 1000, 30);
    }
</script>

效果图:

image

又到了找bug的时候了:

上面的代码从0-500,从500-1000都没有问题,经过向上取整后都能到达目标距离:5001000。但是小伙伴可以看下,当从1000回到500的时候,是正好回到500的吗?答案肯定不是的,为什么呢?

image

step为正数的时候,向上取整是完全没有问题的,但是当从1000500的时候,step就是负数了,负数向上取整后就会变得更大,比如原本是-33.3,向上取整后就是-33了,-0.3就会舍去,所有就不会到500的位置。

解决方法: 判断step的正负,为正的时候,向上取整。为负的时候,向下取整。

缓动函数封装完整版: [16-缓动动画函数封装完整版.html]

function slowAnimate(element, target, num) {
    // 一进来就要清除定时器,防止越点越快
    clearInterval(element.timer);
    element.timer = setInterval(function() {
        // 获得元素当前位置
        var leader = element.offsetLeft;
        // 定义每次运动的距离
        var step = (target - leader) / num;
        //如果step是正数,对step向上取整,
        //如果step是负数,对step向下取整
        // 保证每一次最少都走1px
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        leader += step;
        // 设置元素的位置
        element.style.left = leader + 'px';
        // 当元素的位置 等于 目标位置的时候 清除定时器
        if (leader == target) {
            clearInterval(element.timer);
        }
    }, 15);
};

4.3 获取元素计算后的样式

获取元素计算后的样式指的是元素经过层叠后真正生效的样式,不管样式写在哪,计算后的样式指的就是最终的样式。

通过style只能获取到写在行内的样式,那么想要获取其他的样式怎么办呢?

  • js提供了一个方法:window.getComputedStyle(element, null)[attr];,它返回的是一个对象CSSStyleDeclaration[attr]就是这个对象里面就是计算后的所有的样式的属性名(关联数组取对象的值)。element指的是当前参数,null

这里可以不用深究-官方解释。这个方法需要window调用。

/**
    element :获取样式的当前元素
    null    :这里可以传一个伪元素,如果不是伪元素的话必须是null
    attr    :后面可以写具体的属性,比如boderRadius  就会获取这个元素的border-radius样式信息
*/
window.getComputedStyle(element,null)[attr];

image

示例代码: [17-获取元素计算后的样式.html]

<!-- 样式部分 -->
<style>
    div {
        width: 100px;
        height: 100px;
        background: pink;
    }
    #box {
        width: 200px;
    }
</style>

<!-- html 部分 -->
<div id="box" style="width:300px;"></div>

<!-- js 部分 -->
<script>
    var box = document.getElementById('box');
    console.log(window.getComputedStyle(box, null)); // 打印获得box的各种属性的样式
    // 其中行内样式权重最高,所以最后获得的宽应该是300px
    console.log(window.getComputedStyle(box, null).width); // 300px
    console.log(window.getComputedStyle(box, null).background);
    
</script>

效果图:

image

兼容性处理:

  • window.getComputedStyle(element, null)[attr];只适用于现代浏览器中
  • IE678有自己的方法:element.currentStyle[attr];
// 获取元素计算后的样式
function getStyle(element,attr){
    if(window.getComputedStyle){
        return window.getComputedStyle(element, null)[attr];
    }else{
        return element.currentStyle[attr];
    }
}

// 注意:调用函数的时候 获取的属性名是一个字符串
alert(getStyle(box, "width"));

[18-获取元素计算后的样式兼容性处理.html]

注意: 上面的封装函数中,调用的时候,属性名是一个字符串类型。

4.4 缓动动画修改多个样式

不管是上面的匀速动画函数,还是这里的缓动动画函数,都只能左右运动,但是一个真正完整的动画函数,只改变左右位置肯定是不够的,我们可能需要改变它的宽高等。在上面一节中,我们知道了如何获取到元素计算后的样式,而且只要是元素有的样式都能获取到,有了这个方法我们就可以让动画去执行更多的事情了。

1、对获取到的样式返回值进行处理:

在上面的一节中,我们可以看到,获取的返回值都是字符串格式,比如获取宽度的时候,返回的是一个"300px"的字符串,因为缓动动画函数里面是需要计算的,这里是个字符串肯定不行,所以我们需要对其进行parseInt取整处理。

[19-缓动动画修改多个样式-处理返回值.html]:

function getStyle(element, attr) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(element, null)[attr];
    } else {
        return element.currentStyle[attr];
    }
}

function animate(element, attr, target) {
    clearInterval(element.timer);
    element.timer = setInterval(function() {
        // getStyle 返回的是样式属性的值 我们用一个变量将它储存起来
        var leader = getStyle(element, attr);
    
        // 因为返回值是一个字符串,并且带有字符px,所以我们对返回值进行取整转换
        leader = parseInt(leader) || 0;  // 这里或 0的目的就是,当parseInt取整失败的话,给一个默认值0
        
        var step = (target - leader) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        leader += step;
    
        // 设置指定样式
        element.style[attr] = leader + "px";
    
        if (leader == target) {
            clearInterval(element.timer);
        }
    }, 15);
}

animate(box, "left", 800);

上面的代码我们对它的返回值进行了处理,而且还可以对它设置其他的样式,只要单位是px的属性都可以设置。但是这里每次还是只能设置一个样式,下面我们来实现修改多个样式。

注意: leader = parseInt(leader) || 0; "或"上0的目的就是:当有些属性设置的值不是数字的时候,比如:auto,这时候parseInt转换的结果是NaN。当"或"上0之后,转换失败后,leader,就会默认是0

2、遍历一个对象:

让我们来复习一下,js基础的时候,我们接触到了对象,并且知道了可以用for..in的方法来遍历对象。我们知道getComputedStyle方法,获取计算后样式的时候,返回的是一个名叫CSSStyleDeclaration的对象,这个对象里面是所有的样式属性,我们想要对这些属性进行多个操作的时候,就可以通过遍历的方法。
for(k in obj){
    // k    :就是相当于对象的键
    // obj  :就是需要遍历的对象
}

3、同时修改多个样式:

同时修改多个样式,就是将要修改的多个属性以对象的形式作为参数传进函数中。

[20-缓动动画修改多个样式.html]

var box = document.getElementById('box');
var btn = document.getElementById('btn');

// 封装一个函数,element 表示执行动画的元素  obj传的是一个对象,里面可以设置多个属性和值
function animate(element, obj) {
    clearInterval(element.timer);
    element.timer = setInterval(function() {
        // 遍历外部传进来的对象
        for (k in obj) {
            //attr   :  要做动画的样式
            //target :  目标值
            var attr = k;
            var target = obj[k];
            // 获取元素开始时计算后的样式
            var leader = getStyle(element, attr);
            leader = parseInt(leader) || 0;
            // 缓动动画函数原理
            var step = (target - leader) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
            leader += step;

            // 给元素设置以样式属性名为attr的值  
            // 这个封装的动画函数只能改值是px单位的样式
            element.style[attr] = leader + "px";
            if (leader == target) {
                clearInterval(element.timer);
            }
        }
    }, 15);
}

// 处理兼容性
function getStyle(element, attr) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(element, null)[attr];
    } else {
        return element.currentStyle[attr];
    }
}
// 调用函数 设置了五个样式属性
btn.onclick = function() {
    animate(box, {
        width: 200,
        height: 200,
        left: 300,
        top: 300,
        // bprder-radius 应该转为驼峰命名法 并且值只能是100px的格式  不能是百分比
        borderRadius: 100
    });
}

效果图:

image

通过上面封装的函数我们可以改变多个样式,但是效果图中我们可以看到一个问题,就是当到达设定值后,点击按钮还会慢慢的抖动。原因是修改多个样式的时候,所有的样式并不能都到同时达终点。

4.5 缓动动画修复定时器bug

出现这个bug的原因:在for循环中判断是否到达目标值,到达后就清除定时器,但是我们同时修改了5个样式,可能有的样式到达目标值后就清楚定时器了,但是有的样式还没到达目标值,所以就出现了上面的bug

解决方法:假设成立法

  • 假设成立
  • 想办法推翻假设
  • 如果推翻不了,说明假设成立

示例代码: [21-缓动动画修改多个样式-修复定时器bug.html]

function animate(element, obj) {
    clearInterval(element.timer);
    element.timer = setInterval(function() {
        // 1-假设都到达了终点
        var flag = true;
        for (k in obj) {
            var attr = k;
            var target = obj[k];
            var leader = getStyle(element, attr);
            leader = parseInt(leader) || 0;
            var step = (target - leader) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
            leader += step;
            element.style[attr] = leader + "px";

            // 2- 必须要等到所有的样式都到达终点才清除定时器
            //    只要有一个样式没有到达设定值,说明假设失败
            if (leader != target) {
                flag = false;
            }
        }
        // 所有的样式都到达终点后 清除定时器
        if (flag) {
            clearInterval(element.timer);
        }
    }, 15);
}

4.6 缓动动画兼容其它样式属性

经过前面几小节的学习,我们已经可以实现同时修改多个样式的缓动动画了。但是细心的小伙伴不知道有没有发现,目前只能设置跟px有关系的样式,包括设置border-radiu也不算完善。这是因为我们缓动动画封装的时后,设置的element.style[attr] = leader + "px";,所以只能实现跟px有关的样式。

设置兼容其他属性的时候,要注意两点,第一获取的时候要进行判断,设置的时候也要进行判断

1、兼容opacity属性: [22-缓动动画修改多个样式-兼容opacity.html]

function animate(element, obj) {
    clearInterval(element.timer);
    element.timer = setInterval(function() {
        var flag = true;
        for (k in obj) {
            var attr = k;
            var target = obj[k];
            // 判断获得的属性是不是“opacity”,是的话单独处理
            var leader;
            // 获得当前值
            if (attr === "opacity") {
                // 获取的时候是个小数,将它乘以100 运算时不会出现精度丢失
                leader = getStyle(element, attr) * 100 || 1;
            } else {
                leader = getStyle(element, attr);
                leader = parseInt(leader) || 0;
            }

            var step = (target - leader) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
            leader += step;
            // 赋值
            // 判断是不是opacity属性 是的话 单独赋值
            if (attr === "opacity") {
                // 因为开始的时候leader扩大了100倍 设置的时候 opacity只能是0-1
                element.style[attr] = leader / 100;
                // opacity 还需要单独处理,因为IE678 不支持opacity 
                element.style.filter = "alpha(opacity=" + leader + ")";
            } else {
                element.style[attr] = leader + "px";
            }
            if (leader != target) {
                flag = false;
            }
        }
        if (flag) {
            clearInterval(element.timer);
        }
    }, 15);
}

// 处理获取样式兼容性
function getStyle(element, attr) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(element, null)[attr];
    } else {
        return element.currentStyle[attr];
    }
}

// 调用这个函数
btn.onclick = function() {
    animate(box, {
        width: 200,
        height: 200,
        left: 300,
        top: 300,
        // 这里是按照 0-100 设置不透明度的,因为小数计算的时候会出现精度丢失
        opacity: 50
    });
}

2、兼容zIndex属性: [23-缓动动画修改多个样式-兼容zIndex.html]

zIndex这个属性不需要缓动的执行改变层级,直接获得传进来的值设置即可
// 赋值
if (attr === "opacity") {
    element.style[attr] = leader / 100;
    element.style.filter = "alpha(opacity=" + leader + ")";
// 判断设置的时候是否是zIndex属性
} else if (attr === "zIndex") {
    element.style.zIndex= leader;
} else {
    element.style[attr] = leader + "px";
}

示例代码: [24-缓动动画淡入淡出效果.html]

btn1.onclick = function() {
    animate(box, {
        opacity: 100
    })
}
btn2.onclick = function() {
    animate(box, {
        opacity: 0
    })
}

效果图:

image

4.7 缓动动画添加回调函数

程序执行完毕,再次执行的函数。

示例代码: [25-缓动动画添加回调函数.html]

var box = document.getElementById('box');
var btn = document.getElementById('btn');

function animate(element, obj, fn) {
    clearInterval(element.timer);
    element.timer = setInterval(function() {
        var flag = true;
        for (k in obj) {
            var attr = k;
            var target = obj[k];
            var leader = getStyle(element, attr);
            leader = parseInt(leader) || 0;
            var step = (target - leader) / 10;
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
            leader += step;
            element.style[attr] = leader + "px";
            if (leader != target) {
                flag = false;
            }
        }
        if (flag) {
            clearInterval(element.timer);
            // 所有程序执行完毕了,现在可以执行回调函数了
            // 只有传递了回调函数,才能执行,所以这里要判断一下
            if (fn) {
                fn();
            }
            /* fn&&fn(); */
        }
    }, 15);
}

// 处理兼容性
function getStyle(element, attr) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(element, null)[attr];
    } else {
        return element.currentStyle[attr];
    }
}
// 调用函数 
btn.onclick = function() {
    animate(box, {
        left: 600
    }, function() {
        animate(box, {
            top: 500,
            borderRadius: 50
        }, function() {
            animate(box, {
                width: 400,
                borderRadius: 50
            });
        });
    });
}

效果图:

image

5. 筋斗云案例

直接看效果图:

image

效果如上图,当我们鼠标经过某一项时,小方块会缓动移过去,当离开列表栏时,小方块会回到最初的位置。当点击某一项时小方块的初始位置就会停留在该项上。

示例代码: [26-筋斗云案例.html]

<!-- 样式部分 -->
<style>
    body {
        padding: 0;
        margin: 0;
        background: #333;
    }
    #box {
        width: 800px;
        height: 34px;
        margin: 100px auto;
        background: orange;
        position: relative;
    }
    ul {
        padding: 0 50px;
        height: 34px;
        position: relative;
    }
    #box ul li {
        float: left;
        width: 100px;
        height: 34px;
        line-height: 34px;
        text-align: center;
        list-style: none;
        font-size: 18px;
        font-family: '方正';
        color: #fff;
        cursor: pointer;
    }
    #over {
        position: absolute;
        top: -3px;
        left: 51px;
        width: 100px;
        height: 38px;
        background: orangered;
    }
</style>

<!-- html 部分 -->
<div id='box'>
    <span id='over'></span>
    <ul id='nav'>
        <li>首页</li>
        <li>社区服务</li>
        <li>智慧超市</li>
        <li>便民</li>
        <li>圈子</li>
        <li>活动</li>
        <li>聚优惠</li>
    </ul>
</div>

<!-- js 部分 -->
<script>
    var over = document.getElementById('over');
    var nav = document.getElementById('nav');
    var lis = nav.children;

    for (var i = 0; i < lis.length; i++) {
        lis[i].onmouseover = function() {
                // 鼠标经过时移动的距离就是它距离左边的距离
                slowAnimate(over, this.offsetLeft);
            }
            // 设定默认位置,因为第一个选项距离左边为51px距离所以,默认值设置为51
        var staticLeft = 51;
        lis[i].onmouseout = function() {
            // 鼠标离开的时候,要让它回到默认位置
            slowAnimate(over, staticLeft);
        }

        lis[i].onclick = function() {
            // 当点击某一选项的时候,将默认位置设置为此时的位置
            staticLeft = this.offsetLeft;
        }
    }
    // 缓动动画
    function slowAnimate(element, target, num) {
        clearInterval(element.timer);
        element.timer = setInterval(function() {
            var leader = element.offsetLeft;
            // num 不传的话,默认是10
            var step = (target - leader) / (num || 10);
            step = step > 0 ? Math.ceil(step) : Math.floor(step);
            leader += step;
            element.style.left = leader + 'px';
    
            if (leader == target) {
                clearInterval(element.timer);
            }
        }, 15);
    }
</script>

6. 右下角关闭广告案例

在网页中经常会出现广告,我们举个例子让关闭广告的时候有一个动画效果。

实现原理:

  • 图片其实被切成了两个部分,看到的效果是一张图片,其实是两张。
  • 点击关闭按钮的时候,调用缓动动画函数,将下半部分的盒子高度等于0,所以会出现一个向下的效果
  • 在刚刚的动画函数的回调函数里面继续调用缓动动画,将整个大盒子的宽度等于0,所以出现一个向右的效果

示例代码: [27-右下角关闭广告案例.html]

<!-- 样式部分 -->
<style>
    #box {
        width: 213px;
        position: fixed;
        bottom: 0;
        right: 0;
        overflow: hidden;
    }
    
    #close {
        position: absolute;
        top: 0;
        right: 0;
        width: 30px;
        height: 30px;
        cursor: pointer;
        color: #FFFFFF;
        text-align: center;
    }
    
    .img {
        display: block;
        width: 212px;
        z-index: 99;
    }
</style>

<!-- html 部分 -->
<div id="box">
    <div id="hd">
        <span id="close"> x </span>
        <img src="../image/关闭广告/banna_up.png" class="img" alt="" />
    </div>
    <div id="bt">
        <img src="../image/关闭广告/banner_down.png" class="img" alt="" />
    </div>
</div>

<!-- js 部分 -->
<script src="../js/slow-animate-styles.js"></script>
<script>
    var close = document.getElementById('close');
    var box = document.getElementById('box');
    var bt = document.getElementById('bt');

    close.onclick = function() {
        slowAnimateStyles(bt, {
            height: 0
        }, function() {
            slowAnimateStyles(box, {
                width: 0
            });
        });
    }
</script>

效果图:

image

7. 手风琴案例

手风琴效果在网页中用的也特别的多,下面我们会介绍两种实现的方法,当然个人比较偏好第二种。

1、浮动版手风琴

实现原理:

  • ul,li进行布局,li左浮动,并且设置等分的宽度;
  • 给每个li注册鼠标经过事件,当鼠标经过的时候利用排他原理,将所有的li宽度设置成最小宽度,将当前经过的li宽度设置一个最大宽度;
  • 然后再去设置鼠标离开事件,当鼠标离开时让所有的li再恢复到等分的宽度。

示例代码: [28-手风琴-浮动版.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    #box {
        width: 900px;
        height: 441px;
        margin: 100px auto;
        overflow: hidden;
        border-radius: 30px;
    }
    ul {
        /* ul的宽要比外面的盒子大一点,否则在添加动画效果的时候,最后一个li会出现闪动 */
        width: 120%;
        height: 100%;
        overflow: hidden;
    }
    li {
        width: 180px;
        height: 100%;
        float: left;
    }
</style>

<!-- html 部分 -->
<div id="box">
    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</div>

<!-- js 部分 -->
<script src="../js/slow-animate-styles.js"></script>
<script>
    var box = document.getElementById('box');
    var lis = box.getElementsByTagName("li");

    for (var i = 0; i < lis.length; i++) {
        // 动态创建img标签
        var img = document.createElement("img");
        img.src = "../image/手风琴/" + (i + 1) + ".png";
        lis[i].appendChild(img);
        // 给所有li注册鼠标经过事件,让当前的li宽度变成 500,其余的li宽度变成100
        lis[i].onmouseover = function() {
            for (var i = 0; i < lis.length; i++) {
                // 先让所有的li宽度变成100
                slowAnimateStyles(lis[i], {
                    width: 100
                });
                // 鼠标当前经过的宽度为500
                slowAnimateStyles(this, {
                    width: 500
                })
            }
        };
        // 当鼠标离开的时候,所以的li 宽度恢复到180px
        lis[i].onmouseout = function() {
            for (var i = 0; i < lis.length; i++) {
                slowAnimateStyles(lis[i], {
                    width: 180
                })
            }
        }
    }
</script>

效果图:

image

2、定位版手风琴

实现原理:

  • 给外部大盒子设置一个与图片大小一致的宽高,并且设置相对定位
  • 还是采用ul,li结构,li设置宽高,与图片大小一致,设置绝对定
  • 动态的给li添加背景图片,因为li绝对定位的原因,此时所有的li都叠在一起
  • 动态的给每个li设置left值(left*i),这时候li就会依次排开
  • 大盒子还要设置一个overflow-hidden属性,将多余的隐藏掉
  • 给每个li注册鼠标鼠标经过事件,然后根据下面推算出的规律(当前鼠标经过的索引index,他之前包括他自己的left值都是,设定的最小值乘以对应的索引。而他后面的会将设定的最小值乘以对应的索引后再加上450,这里的450不是一个固定值,根据规律找出来的)进行判断,设置各自的left值;
  • 鼠标离开的时候再让所有的盒子恢复到一开始的位置,每个li显示等分的宽度

大盒子没有overflow-hidden的时候:

image

画个图,理解一下:

image

找规律:

结合上面的图片,我们可以找到一个规律
  • 当鼠标在第1个li上的时候,li下标index为0:

    • index:0 left:0
    • index:1 left:500px
    • index:2 left:550px
    • index:3 left:600px
    • index:4 left:650px
  • 当鼠标在第2个li上的时候,li下标index为1:

    • index:0 left:0
    • index:1 left:50px
    • index:2 left:550px
    • index:3 left:600px
    • index:4 left:650px
  • 当鼠标在第3个li上的时候,li下标index为2:

    • index:0 left:0
    • index:1 left:50px
    • index:2 left:100px
    • index:3 left:600px
    • index:4 left:650px

看出规律了吗?

  • 当对应li的下标<=鼠标悬停的的下标上的时候left值 是50*i
  • 当对应li的下标>鼠标悬停的的下标上的时候left值 是50*i + ,450(450不是固定的值,是经过计算出来的)

示例代码: 29-手风琴-定位版.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    
    #box {
        width: 700px;
        height: 440px;
        margin: 100px auto;
        position: relative;
        overflow: hidden;
        box-sizing: border-box;
        border-radius: 30px;
    }
    
    li {
        width: 700px;
        height: 440px;
        position: absolute;
        /* background: yellow; */
    }
</style>

<!-- html 部分 -->
<div id="box">
    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</div>

<!-- js 部分 -->
<script src="../js/slow-animate-styles.js"></script>
<script>
    var box = document.getElementById('box');
    var lis = box.getElementsByTagName('li');


    for (var i = 0; i < lis.length; i++) {
        lis[i].index = i;
        // 动态添加li的背景图片 因为i下标从0开始,但是图片序号是从1开始 所以jia1
        lis[i].style.backgroundImage = "url(../image/手风琴/" + (i + 1) + ".png)";
        // 现在都叠在一起,设置left 让他们分开来 700/5 ==> 140px
        lis[i].style.left = 140 * i + "px";

        // 注册鼠标经过事件,让当前的显示宽度为500,其余的为50
        lis[i].onmouseover = function() {
            for (var i = 0; i < lis.length; i++) {
                // 判断当i小于等于当前鼠标停留的下标的时候,给li的left设置 50*i
                if (i <= this.index) {
                    slowAnimateStyles(lis[i], {
                        left: 50 * i
                    });
                    // 当i大于当前鼠标停留的索引的时候,给后边的li的left设置 50*i + 450 
                } else {
                    slowAnimateStyles(lis[i], {
                        left: 50 * i + 450
                    });
                }
            }
        }

        // 注册鼠标离开事件,让所有的li都恢复到最初的样式
        lis[i].onmouseout = function() {
            for (var i = 0; i < lis.length; i++) {
                slowAnimateStyles(lis[i], {
                    left: 140 * i
                });
            }
        }
    }
</script>

效果图:

image

8.旋转木马案例

旋转木马也叫旋转轮播图,在效果上它就是旋转版的轮播图,但是在实现原理上却一点一不一样

旋转木马原理:

  • 利用ulli方式将图片包裹在li里,并且对每个li的大小、层级、不透明度以及定位的位置设置好
  • 样式上可能比较繁琐,我们将上面的每个参数再以对象的方式存到数组datas
  • 之前封装过一个缓动动画函数,可以改变层级和不透明度,这里正好用得到
  • 其实抛开上面样式上的细节,旋转木马最核心的就是运用到几个数组常用的方法 popunshiftshiftpush
  • 点击右按钮的时候,将datas里的最后一项利用pop删除掉,并且返回这个删除的数据,再将这个数据unshift到数组的最前面。重新遍历数组,执行一遍动画
  • 点击左箭头的时候,将datas里的最前面一项利用shift删除掉,并且返回这个删除的数据,再将这个数据push到数组的最后面。重新遍历数组,执行一遍动画
  • 再给按钮添加一个节流阀,没加之前不停地点击按钮,图片就会不停切换,加上之后,点一次执行完才可以再次点击。

示例代码: [30-旋转木马轮播图案例.html]

<!-- 样式部分 -->
<style>
    * {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    body {
        background: #666;
    }
    .wrap {
        width: 1200px;
        margin: 200px auto;
    }
    .slide {
        height: 340px;
        position: relative;
    }
    .slide li {
        position: absolute;
        left: 300px;
        top: 0;
    }
    img {
        width: 100%;
    }
    .arrow {
        opacity: 0;
        position: relative;
        z-index: 99;
        top: 50%;
    }
    .arrow #left,
    .arrow #right {
        width: 40px;
        height: 90px;
        position: absolute;
        top: 50%;
        margin-top: -45px;
        background: url(../image/旋转木马/left.png);
        background-size: cover;
        z-index: 99;
    }
    .arrow #right {
        right: 0;
        background: url(../image/旋转木马/right.png);
        background-size: cover;
    }
</style>

<!-- html 部分 -->
<div class="wrap" id="wrap">
    <div class="slide" id="slide">
        <ul>
            <li><img src="../image/1.jpg" alt=""></li>
            <li><img src="../image/2.jpg" alt=""></li>
            <li><img src="../image/3.jpg" alt=""></li>
            <li><img src="../image/4.jpg" alt=""></li>
            <li><img src="../image/5.jpg" alt=""></li>
        </ul>
        <div class="arrow" id="arrow">
            <a href="javascript:;"><span id="left"></span></a>
            <a href="javascript:;"><span id="right"></span></a>
        </div>
    </div>
</div>

<!-- js 部分 -->
<script src="../js/slow-animate-styles.js">
</script>
<script>
    // 将其余四张位置与透明度等信息,存放在一个数组中
    var datas = [{
            "width": 300,
            "top": -20,
            "left": 150,
            "opacity": 20,
            "zIndex": 2
        }, //0
        {
            "width": 500,
            "top": 30,
            "left": 50,
            "opacity": 80,
            "zIndex": 3
        }, //1
        {
            "width": 600,
            "top": 100,
            "left": 300,
            "opacity": 100,
            "zIndex": 4
        }, //2
        {
            "width": 500,
            "top": 30,
            "left": 650,
            "opacity": 80,
            "zIndex": 3
        }, //3
        {
            "width": 300,
            "top": -20,
            "left": 750,
            "opacity": 20,
            "zIndex": 2
        } //4
    ];

    var slide = document.getElementById('slide');
    var lis = slide.getElementsByTagName('li');
    var arrow = document.getElementById('arrow');
    var left = document.getElementById('left');
    var right = document.getElementById('right');

    // 定义一个节流阀
    var flag = true;

    // 一开始页面刷新的时候,将datas里的数据 动态添加进去
    for (var i = 0; i < lis.length; i++) {
        slowAnimateStyles(lis[i], datas[i]);
    };

    // 鼠标经过的时候 箭头显示
    slide.onmouseover = function() {
        slowAnimateStyles(arrow, {
            opacity: 100
        })
    };

    // 鼠标离开的时候 箭头隐藏
    slide.onmouseout = function() {
        slowAnimateStyles(arrow, {
            opacity: 0
        })
    };

    // 点击右箭头的时候
    // 利用数组的pop 和 unshift方法对数组datas进行操作
    // pop 会删除数组的最后一项,并且返回这一项。 unshift 会在数组的最前添加
    right.onclick = function() {
        // 只有节流阀为true的时候 点击才会执行里面的代码
        if (flag) {
            // 电击后一进来就将节流阀关上,再次点击的时候就不会进来
            flag = false;
            datas.unshift(datas.pop());
            for (var i = 0; i < lis.length; i++) {
                // 点击一次就要动画渲染一次,datas[i]  其实是一个对象
                /*
                    {
                        "width": 300,
                        "top": -20,
                        "left": 150,
                        "opacity": 20,
                        "zIndex": 2
                    }
                */
                slowAnimateStyles(lis[i], datas[i], function() {
                    // 当动画执行完,也就是回调函数触发的时候,再将节流阀打开,这样就可以继续点击了
                    flag = true;
                });
            }
        }
    }

    // 点击左箭头
    // 利用数组的 shift 和 push方法对数组datas进行操作
    // shift 会删除数组的第一项,并且返回这一项。 push 会在数组的最后添加
    left.onclick = function() {
        if (flag) {
            flag = false;
            datas.push(datas.shift());
            for (var i = 0; i < lis.length; i++) {
                slowAnimateStyles(lis[i], datas[i], function() {
                    flag = true;
                });
            }
        }

    }
</script>

效果图:

image

上一篇:JavaScript 基础知识 - BOM篇
下一篇:JavaScript 进阶知识 - 特效篇(二)


深海丶Deepsea
3.9k 声望1.4k 粉丝

Trust yourself,You know more than you think you do.