JavaScript三部曲 - 网页特效篇

JavaScript - 网页特效篇

网页特效篇,也是JavaScript三部曲中的最后一篇,在上两篇我们分别学习了JavaScript的基础语法和JavaScript操作DOM与BOM。根据上两篇的学习也能完成很多场景的开发,在这一篇中我们将学习offset、client、scroll系列属性的作用与使用、封装简单动画函数、网页轮番图等,完成更高级的开发。

1、元素偏移量 offset 系列

1.1、offset 概述

offset翻译过来就是偏移量,我们使用 offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。

offset总结下来就是:

  • 获得元素距离带有定位父元素的位置
  • 获得元素自身的大小(宽度高度)
  • 注意:返回的数值都不带单位

offset系列常用属性:

offset系列属性作用
element.offsetParent返回作为该元素带有定位的父级元素,如果父级都没有定位则返回body
element.offsetTop返回元素相对带有定位父元素上方的偏移
element.offsetLeft返回元素相对带有定位父元素左边框的偏移
element.offsetWidth返回自身包括 padding、边框、内容区的宽度,返回数值不带单位
element.offsetHeight返回自身包括padding、边框、内容区的高度,返回数值不带单位

如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: relative;
            width: 200px;
            height: 200px;
            background-color: chartreuse;
            margin-top: 100px;
            margin-left: 50px;
        }
        .box .son {
            position: absolute;
            width: 50px;
            height: 50px;
            top: 20px;
            left: 100px;
            margin-left: -25px;
            background-color: chocolate;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="son">

        </div>
    </div>
    <script>
        // offset 系列
        // 1、可以得到元素的偏移、位置、返回的不带有单位的数值
        var box = document.querySelector(".box");
        console.log(box.offsetTop); // 100
        console.log(box.offsetLeft); // 58

        var son = document.querySelector(".box .son");
        console.log(son.offsetTop); // 20
        console.log(son.offsetLeft); // 75

        // 2、可以得到元素的大小、宽度和高度
        console.log(box.offsetWidth); // 200
        console.log(son.offsetHeight); // 50

        // 3、返回带有定位的父亲,否则返回的是body
        console.log(son.offsetParent);
        console.log(son.parentNode); // 返回最近一级的父节点,不管父节点有没有定位

    </script>
</body>
</html>

1.2、offset 与 style 区别

offsetstyle
offset 可以得到任意样式表中的样式值style只能得到行内样式表中的样式值
offset 系列获得的数值是没有单位的style.width获得的是带有单位的字符串
offsetWidth 包含 padding + border + widthstyle.width获得不包含padding和border的值
offsetWidth 等属性是只读属性,只能获取不能赋值style.width是可读写属性,可以获取也可以赋值
所以我们想要获取元素大小位置,用offset更合适所以,我们想要给元素更改值,则需要用style改变

案例1:获取鼠标在盒子内的坐标

思路:

  1. 我们在盒子内点击,想要得到鼠标距离盒子左右的距离
  2. 首先得到鼠标在页面中的坐标(event.pageXevent.pageY
  3. 其次得到盒子在页面中的距离(element.offsetLeftelement.offsetTop
  4. 用鼠标距离页面的坐标减去盒子在页面中的距离,得到鼠标在盒子内的坐标
  5. 如果想要移动鼠标,就要获取最新的坐标,使用鼠标移动事件mousemove

在这里插入图片描述
代码实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin-top: 100px;
            margin-left: 100px;
            background-color: cyan;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        var box = document.querySelector(".box");
        box.addEventListener("mousemove", function(event) {
            var x = event.pageX - box.offsetLeft;
            var y = event.pageY - box.offsetTop;
            box.innerText = "x=" + x + ", y=" + y;
            // console.log("x = " + x + ", y = " + y);
        });
    </script>
</body>
</html>

案例2:仿照京东放大镜效果
在这里插入图片描述
思路分析:

  1. 整个案例可以分为三个功能模块
  2. 鼠标经过小图片盒子,黄色的遮罩层和大图片盒子显示,离开隐藏2个盒子功能
  3. 黄色的遮挡层跟随鼠标功能
  4. 移动黄色遮挡层,大图片跟随移动

代码实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: relative;
            width: 450px;
            height: 450px;
            margin-top: 20px;
            margin-left: 50px;
        }
        .box .pre_img {
            width: 100%;
            height: 100%;
            border: 1px solid #eee;
        }
        .box .mask {
            position: absolute;
            display: none;
            top: 0;
            left: 0;
            width: 300px;
            height: 300px;
            cursor: move;
            /* background-color: rgb(254,238,167); */
            background-color: rgba(254, 238, 167, 0.5);
        }
        .box .zoom_img {
            position: absolute;
            display: none;
            top: 0;
            left: 450px;
            width: 500px;
            height: 500px;
            border: 1px solid #eee;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="pre_img">
            <img src="https://img12.360buyimg.com/n1/s450x450_jfs/t1/179275/27/217/45273/607fbd91Ec4a05572/06c8a5ab803317f8.jpg" alt="">
        </div>
        <!-- 遮罩层 -->
        <div class="mask"></div>
        <!-- 大图 -->
        <div class="zoom_img">
            <img src="https://img14.360buyimg.com/n0/jfs/t1/179275/27/217/45273/607fbd91Ec4a05572/06c8a5ab803317f8.jpg" alt="">
        </div>
    </div>
    <script>
        /*window.addEventListener("load", function() {

        });*/
        var box = document.querySelector(".box");
        var pre_img = document.querySelector(".box .pre_img");
        var mask = document.querySelector(".box .mask");
        var zoom_img = document.querySelector(".box .zoom_img");

        // 1、鼠标经过小图片盒子,黄色的遮罩层和大图片盒子显示,离开隐藏2个盒子功能
        pre_img.addEventListener("mouseover", function() {
            mask.style.display = "block";
            zoom_img.style.display = "block";
        });
        // 2、鼠标移动时,黄色盒子跟随鼠标进行移动
        box.addEventListener("mousemove", function(event) {
            var x = event.pageX - this.offsetLeft;
            var y = event.pageY - this.offsetTop;
            // console.log(x,y);
            var maskX = x - mask.offsetWidth / 2;
            var maskY = y - mask.offsetHeight / 2;

            // 黄色盒子不能脱离盒子,所以移动的方向需要做一个约束
            if (maskX <= 0) {
                maskX = 0;
            }
            if (maskX > (this.offsetWidth - mask.offsetWidth)) {
                maskX = this.offsetWidth - mask.offsetWidth;
            } 
            if (maskY <= 0) {
                maskY = 0;
            }
            if (maskY > (this.offsetHeight - mask.offsetHeight)) {
                maskY = this.offsetHeight - mask.offsetHeight;
            }
            mask.style.left = maskX + "px";
            mask.style.top = maskY + "px";

            // 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离  / 遮挡层的最大移动距离
            var maskMax = this.offsetWidth - mask.offsetWidth;
            var bigImg = document.querySelector(".box .zoom_img img");
            // console.log(bigImg);
            // 大图最大移动距离
            var bigImgMaskMax = bigImg.offsetWidth - zoom_img.offsetWidth;
            // 大图片的移动距离
            var bigImgMaskX = maskX * bigImgMaskMax / maskMax; 
            var bigImgMaskY = maskY * bigImgMaskMax / maskMax; 
            
            bigImg.style.position = "absolute";
            bigImg.style.left = -bigImgMaskX + "px";
            bigImg.style.top = -bigImgMaskY + "px";

        });

        mask.addEventListener("mouseout", function() {
            mask.style.display = "none";
            zoom_img.style.display = "none";
        });
    </script>
</body>
</html>

2、元素可视区 client 系列

2.1、client常用属性

client翻译过来就是客户端,我们使用client系列的相关属性来获取元素可视区的相关信息。通过client系列的相关属性可以动态的得到该元素的边框大小、元素大小等。

client系列属性作用
element.clientTop返回元素上边框的大小
element.clientLeft返回元素左边框的大小
element.clientWidth返回自身包括padding、内容区的宽度、不含边框,返回数值不带单位
element.clientHeight放回自身包括padding。内容区的高度、不含边框,返回数值不带单位

如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: aqua;
            border: 1px solid #eee;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        var box = document.querySelector(".box");
        // 不包含边框、padding等
        console.log(box.clientWidth); // 200 
        console.log(box.offsetWidth); // 202
    </script>
</body>
</html>

2.2、立即执行函数

在淘宝网中响应的js文件里,我们常常会看到如下写法的代码:

(function(m, p, a, e, r, o, t) {
	...
}
)(window, document, Math.random, navigator, location, encodeURIComponent, decodeURIComponent);

// 立即执行函数写法
(function() {})()

这种写法的主要作用就是:创建一个独立的作用域

使用示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        // 1、传统函数声明与调用
        function eat() {
            console.log("吃饭第一!");
        }
        eat();

        // 2、立即执行函数:不需要调用,立马能够自己执行的函数
        (function(name) {
            console.log("hello " + name);
        })("alex");
        // 3、立即执行函数第二种写法
        /*
        (function() {} ());
        */
       // 4、最大的作用就是创建了独立的作用域。里面所有的变量都是局部变量,不会有命名冲突的情况。
    </script>
</body>
</html>

淘宝flexible.js源码分析:

(function flexible(window, document) {
    // 获取的html 的根元素
    var docEl = document.documentElement
        // dpr 物理像素比
    var dpr = window.devicePixelRatio || 1
 
    // adjust body font size  设置我们body 的字体大小
    function setBodyFontSize() {
        // 如果页面中有body 这个元素 就设置body的字体大小
        if (document.body) {
            document.body.style.fontSize = (12 * dpr) + 'px'
        } else {
            // 如果页面中没有body 这个元素,则等着 我们页面主要的DOM元素加载完毕再去设置body的字体大小
            document.addEventListener('DOMContentLoaded', setBodyFontSize)
        }
    }
    setBodyFontSize();
 
    // set 1rem = viewWidth / 10    设置我们html 元素的文字大小
    function setRemUnit() {
        var rem = docEl.clientWidth / 10
        docEl.style.fontSize = rem + 'px'
    }
 
    setRemUnit()
 
    // reset rem unit on page resize  当我们页面尺寸大小发生变化的时候,要重新设置下rem 的大小
    window.addEventListener('resize', setRemUnit)
        // pageshow 是我们重新加载页面触发的事件
    window.addEventListener('pageshow', function(e) {
        // e.persisted 返回的是true 就是说如果这个页面是从缓存取过来的页面,也需要从新计算一下rem 的大小
        if (e.persisted) {
            setRemUnit()
        }
    })
 
    // detect 0.5px supports  有些移动端的浏览器不支持0.5像素的写法
    if (dpr >= 2) {
        var fakeBody = document.createElement('body')
        var testElement = document.createElement('div')
        testElement.style.border = '.5px solid transparent'
        fakeBody.appendChild(testElement)
        docEl.appendChild(fakeBody)
        if (testElement.offsetHeight === 1) {
            docEl.classList.add('hairlines')
        }
        docEl.removeChild(fakeBody)
    }
}(window, document))

3、元素滚动scroll系列

3.1、元素 scroll 系列属性

scroll翻译过来就是滚动的,我们使用scroll系列的相关属性可以动态的得到该元素的大小、滚动距离等。

scroll系列属性作用
element.scrollTop返回被卷去的上侧距离,返回数值不带单位
element.scrollLeft返回被卷去的左侧距离,返回数值不带单位
element.scrollWidth返回自身实际的宽度,不含边框,返回数值不带单位
element.scrollHeight返回自身实际的高度,不含边框,返回数值不带单位

scroll系列与client系列的区别(当盒子内容超出盒子大小时即可看出区别):
在这里插入图片描述
代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: aqua;
            border: 1px solid #eee;
            padding: 2px;
            overflow: auto;
        }
    </style>
</head>
<body>
    <div class="box">我是内容</div>
    <script>
        // scroll 系列
        var box = document.querySelector(".box");
        console.log(box.scrollWidth); // 204
    </script>
</body>
</html>

3.2、页面被卷去的头部

如果浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条。当滚动条向下滚动时,页面上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发onscroll事件。

代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: aqua;
            border: 1px solid #eee;
            padding: 2px;
            overflow: auto;
        }
    </style>
</head>
<body>
    <div class="box">
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
        我是内容
    </div>
    <script>
        // scroll 系列
        var box = document.querySelector(".box");
        console.log(box.scrollWidth); // 204
        // 1、scroll滚动事件当我们滚动条发生变化会触发的事件
        box.addEventListener("scroll", function() {
            console.log(box.scrollTop);
        });
    </script>
</body>
</html>

案例:仿淘宝固定右侧侧边栏
在这里插入图片描述
功能分析:

1、原先侧边栏是绝对定位

2、当页面滚动到一定位置,侧边栏改为固定定位

3、页面继续滚动,会让返回顶部显示出来

思路分析:

  1. 需要用到页面滚动事件scroll因为是页面滚动,所以事件源是document
  2. 滚动到某个位置,就是判断页面被卷去的上部值
  3. 页面被卷去的头部:可以通过window.pageYOffset 获得,如果是被卷去的左侧window.pageXOffset
  4. 注意:元素被卷去的头部是element.scrollTop,如果是页面被卷去的头部则是window.pageYOffset

代码实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .tab_bar {
            position: relative;
        }
        .tab_bar .back {
            position: absolute;
            display: none;
            top: 70px;
            right: 100px;
            width: 40px;
            height: 100px;
            background-color: rgba(127, 255, 212, 0.4);
            text-align: center;
            padding-top: 50px;
        }
        .top {
            width: 1200px;
            height: 50px;
            margin: 0 auto;
            background-color: red;
        }
        .banner {
            width: 1200px;
            height: 230px;
            margin: 0 auto;
            background-color: aqua;
        }
        .content {
            width: 1200px;
            height: 1000px;
            margin: 0 auto;
            background-color: aquamarine;
        }
    </style>
</head>
<body>
    <div class="tab_bar">
        <div class="back">返回顶部</div>
    </div>
    <div class="top">
        top
    </div>
    <div class="banner">
        banner
    </div>
    <div class="content">
        content
    </div>
    <script>
        var tabBar = document.querySelector(".tab_bar");
        var back = document.querySelector(".tab_bar .back");
        var top = document.querySelector(".top");
        var banner = document.querySelector(".banner");
        // console.log(top);
        document.addEventListener("scroll", function() {
            // console.log("hello world");
            console.log(banner.offsetTop);
            // window.pageYOffset 页面被卷去的头部
            if (window.pageYOffset >= banner.offsetTop) {
                back.style.display = "block";
                // 固定定位
                back.style.position = "fixed";
            } else {
                back.style.display = "none";
                back.style.position = "absolute";
            }
        });
    </script>
</body>
</html>

3.3、页面被卷去的头部兼容性解决方案

需要注意的是,页面被卷去的头部,有兼容性问题,因此被卷去的头部通常有如下几种写法:

  1. 声明了DTD(<!DOCTYPE html>),使用document.documentElement.scrollTop
  2. 未声明DTD,使用document.body.scrollTop
  3. 新方法window.pageYOffsetwindow.pageXOffset,IE9开始支持
function getScroll() {
	return {
		left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
		top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
	};
}
// 使用的时候 getScroll().left 或者是 getScroll().top

3.4、三大系列总结

三大系列作用
element.offsetWidth返回自身包括padding、边框、内容区的宽度,返回数值不带单位
element.clientWidth返回自身包括padding。内容区的宽度,不含边框,返回数值不带单位
element.scrollWidth返回自身实际的宽度,不含边框,返回数值不带单位

在这里插入图片描述
他们的主要用法:

  • offset系列经常用于获得元素位置offsetLeft、offsetTop
  • client系列经常用于获取元素大小clientWidth、clientHeight
  • scroll系列经常用于获取元素滚动距离scrollTop、scrollLeft

4、JavaScript动画

4.1、动画实现原理

核心原理:通过定时器setInterval()不断移动盒子位置。

  1. 获得盒子当前位置
  2. 让盒子在当前位置加上1个移动距离
  3. 利用定时器不断重复这个操作
  4. 加一个结束定时器的条件
  5. 注意此元素需要添加定位,才能使用element.syle.left

如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: absolute;
            width: 200px;
            height: 200px;
            top: 0;
            left: 0;
            background-color: greenyellow;
        }
    </style>
</head>
<body>
    <div class="box">

    </div>
    <script>
        var box = document.querySelector(".box");
        var num = 1;
        var timer = setInterval(function() {
            box.style.left = num++ + "px";
            if(num === 500) {
                window.clearInterval(timer);
            }
        }, 10);
    </script>
</body>
</html>

4.2、动画函数简单封装

注意函数需要传递2个参数,动画对象移动的距离

如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: absolute;
            width: 200px;
            height: 200px;
            top: 0;
            left: 0;
            background-color: greenyellow;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <script>
        var box = document.querySelector(".box");
        
        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target) {
            var timer = setInterval(function() {
                if(object.offsetLeft >= target) {
                    // 停止动画
                    clearInterval(timer);
                }
                object.style.left = object.offsetLeft + 1 + "px";
            },30);
        }
		// 调用动画函数
        animate(box, 500);
    </script>
</body>
</html>

我们发现以上封装的动画函数,定时器的声明都是timer,并且是以var关键字声明的。如果我们有多个目标对象调用此动画函数时,就会在内存中创建多个以timer命名的定时器,在清除定时器时就会产生歧义。所有我们需要给不同元素记录不同定时器。

动画函数给不同元素记录不同定时器

如果多个元素都使用这个动画函数,每次都要var 声明定时器。我们可以给不同的元素使用不同的定时器(自己专门用自己的定时器)。

核心原理:利用JavaScript是一门动态语言,可以很方便的给当前对象添加属性。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: absolute;
            width: 200px;
            height: 200px;
            background-color: greenyellow;
        }
        .test {
            position: absolute;
            width: 200px;
            height: 200px;
            top: 100px;
            background-color: hotpink;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <div class="test"></div>
    <script>
        var box = document.querySelector(".box");
        var test = document.querySelector(".test");

        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(object.offsetLeft >= target) {
                    // 停止动画
                    clearInterval(object.timer);
                }
                object.style.left = object.offsetLeft + 1 + "px";
            },30);
        }

        animate(box, 500);
        animate(test, 300);
    </script>
</body>
</html>

4.3、缓动动画

缓动动画:就是让元素运动速度有所变化,最常见的是让速度慢慢停下来。

思路:

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
  2. 核心算法:(目标值 - 现在的位置) / 10,作为每次移动的距离步长。
  3. 停止的条件是:让当前盒子位置 等于目标位置就停止定时器。

代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: absolute;
            width: 200px;
            height: 200px;
            background-color: greenyellow;
        }
        .test {
            position: absolute;
            width: 200px;
            height: 200px;
            top: 100px;
            background-color: hotpink;
        }
    </style>
</head>
<body>
    <button class="start">开始</button>
    <div class="box"></div>
    <div class="test"></div>
    <script>
        var box = document.querySelector(".box");
        var test = document.querySelector(".test");
        var start = document.querySelector(".start");

        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(object.offsetLeft == target) {
                    // 停止动画
                    clearInterval(object.timer);
                }
                // 实现缓动动画
                // 注意:js中小数运算,会出现问题。所以为避免出现运算问题,我们需要把步长转成整数(向上取整)
                var step = (target - object.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step); 
                object.style.left = object.offsetLeft + step + "px";
            },15);
        }
        /*
        匀速动画:盒子是当前位置 + 固定值
        缓动动画:盒子当前位置 + 变化的值(目标值 - 现在的位置 / 10)
        */
        animate(test, 300);
        start.addEventListener("click", function() {
            animate(box, 500);
        });
    </script>
</body>
</html>

4.4、动画函数添加回调函数

回调函数原理:函数可以作为一个参数。将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,在执行传进去的这个函数,这个过程就叫做回调

回调函数书写的位置:定时器结束的位置。

如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: absolute;
            width: 200px;
            height: 200px;
            background-color: greenyellow;
        }
        .test {
            position: absolute;
            width: 200px;
            height: 200px;
            top: 100px;
            background-color: hotpink;
        }
    </style>
</head>
<body>
    <button class="start">开始</button>
    <div class="box"></div>
    <div class="test"></div>
    <script>
        var box = document.querySelector(".box");
        var test = document.querySelector(".test");
        var start = document.querySelector(".start");

        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target, callback) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(object.offsetLeft == target) {
                    // 停止动画
                    clearInterval(object.timer);
                    // 回调函数
                    if (callback) {
                        callback();
                    }
                }
                // 实现缓动动画
                // 注意:js中小数运算,会出现问题。所以为避免出现运算问题,我们需要把步长转成整数(向上取整)
                var step = (target - object.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step); 
                object.style.left = object.offsetLeft + step + "px";
            }, 15);
        }
        /*
        匀速动画:盒子是当前位置 + 固定值
        缓动动画:盒子当前位置 + 变化的值(目标值 - 现在的位置 / 10)
        */
        animate(test, 300);
        start.addEventListener("click", function() {
            animate(box, 500, function() {
                console.log("Hello World");
                box.style.backgroundColor = "red";
            });
        });

    </script>
</body>
</html>

案例:
在这里插入图片描述
代码实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            position: relative;
            width: 50px;
            height: 50px;
            margin: 100px auto;
            text-align: center;
            line-height: 50px;
            color: red;
            background-color: greenyellow;
        }
        .box .con {
            position: absolute;
            display: none;
            top: 0;
            width: 100px;
            height: 50px;
            color: red;
            background-color: greenyellow;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="side_bar">
            <--
        </div>
        <div class="con">问题反馈</div>
    </div>
    <script>
        var box = document.querySelector(".box");
        var con = document.querySelector(".box .con");
        var side_bar = document.querySelector(".box .side_bar");

        // 封装好的动画函数
        function animate(object, target, callback) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(object.offsetLeft == target) {
                    // 停止动画
                    clearInterval(object.timer);
                    // 回调函数
                    if (callback) {
                        callback();
                    }
                }
                // 实现缓动动画
                // 注意:js中小数运算,会出现问题。所以为避免出现运算问题,我们需要把步长转成整数(向上取整)
                var step = (target - object.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step); 
                object.style.left = object.offsetLeft + step + "px";
            }, 15);
        }

        box.addEventListener("mouseover", function() {
            // console.log("123");
            con.style.display = "block";
            animate(con, -100, function() {
                side_bar.innerText = "-->"
            });
        });

        box.addEventListener("mouseout", function() {
            animate(con, 50, function() {
                con.style.display = "none";
                side_bar.innerText = "<--"
            });
        });
    </script>
</body>
</html>

5、常见的网页特效案例

5.1、网页轮番图

轮番图也称为焦点图,是网页中比价常见的网页特效。

轮番图功能分析:

  1. 鼠标经过轮番图模块,左右按钮显示,离开隐藏左右按钮
  2. 点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理
  3. 图片播放的同时,下面小圆圈模块跟随一起变化
  4. 点击小圆圈,可以播放响应图片
  5. 鼠标不经过轮番图也会自动播放图片,鼠标停在图片时,轮番图停止。

轮播图案例:
在这里插入图片描述
代码实现:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        ul {
            margin: 0px;
            padding: 0px;
        }
        .box {
            position: relative;
            width: 520px;
            height: 280px;
            /* background-color: aqua; */
            margin: 100px auto;
            border-radius: 15px;
            overflow: hidden;
        }
        .box .slider_control_prev,.slider_control_next {
            position: absolute;
            display: none;
            top: 50%;
            margin-top: -14px;
            background-color: rgba(0, 0, 0, 0.5);
            cursor: pointer;
            border: none;
        }
        .box .slider_control_prev {
            width: 22px;
            height: 28px;
            border-top-right-radius: 20px;
            border-bottom-right-radius: 20px;
        }
        .box .slider_control_prev i {
            display: inline-block;
            width: 8px;
            height: 8px;
            border-top: 2px solid #fff;
            border-left: 2px solid #fff;
            transform: rotate(-45deg);
        }
        .box .slider_control_next {
            right: 0;
            width: 22px;
            height: 28px;
            border-top-left-radius: 20px;
            border-bottom-left-radius: 20px;
        }
        .box .slider_control_next i {
            display: inline-block;
            width: 8px;
            height: 8px;
            border-top: 2px solid #fff;
            border-right: 2px solid #fff;
            transform: rotate(45deg);
        }
        .box .slider_indicators {
            position: absolute;
            left: 50%;
            bottom: 15px;
            height: 12px;
            margin-left: -50px;
            border-radius: 10px;
            background-color: rgba(255,255,255,.3);
            list-style: none;
        }
        .box .slider_indicators li {
            float: left;
            width: 8px;
            height: 8px;
            cursor: pointer;
            border-radius: 8px;
            margin: 2px 5px;
            background-color: #fff;
        }
        .box .mod {
            position: absolute;
            z-index: -1;
            width: 3500px;
        }
        .box .mod li {
            /* position: absolute; */
            float: left;
            list-style: none;
        }
        .box .slider_indicators .active {
            background-color: #ff5000;
        }
    </style>
</head>
<body>
    <div class="box">
        <!-- 左侧按钮 -->
        <button class="slider_control_prev">
            <i></i>
        </button>
        <!-- 右侧按钮 -->
        <button class="slider_control_next">
            <i></i>
        </button>
        <!-- 滚动圆圈 -->
        <ul class="slider_indicators">
            <!-- <li class="active"></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li> -->
        </ul>
        <!-- 核心滚动区域 -->
        <ul class="mod">
            <li>
                <!-- 图片地址从淘宝网获取即可 -->
                <a href="#">
                    <img src="https://aecpm.alicdn.com/simba/img/TB1JNHwKFXXXXafXVXXSutbFXXX.jpg" alt="">
                </a>
            </li>
            <li>
                <a href="#">
                    <img src="https://aecpm.alicdn.com/simba/img/TB1XotJXQfb_uJkSnhJSuvdDVXa.jpg" alt="">
                </a>
            </li>
            <li>
                <a href="#">
                    <img src="https://aecpm.alicdn.com/simba/img/TB183NQapLM8KJjSZFBSutJHVXa.jpg" alt="">
                </a>
            </li>
            <li>
                <a href="#">
                    <img src="https://img.alicdn.com/imgextra/i4/6000000001266/O1CN01r9ist01LDs6BeBe1j_!!6000000001266-0-octopus.jpg" alt="">
                </a>
            </li>
            <li>
                <a href="#">
                    <img src="https://img.alicdn.com/imgextra/i2/2206686532409/O1CN01RYuJcN1TfMndJT0B2_!!2206686532409-0-lubanimage.jpg" alt="">
                </a>
            </li>
        </ul>
    </div>
    <script>
        window.addEventListener("load", function() {
            // 1、获取元素
            var box = document.querySelector(".box");
            var prev = document.querySelector(".box .slider_control_prev");
            var next = document.querySelector(".box .slider_control_next");
            var sliderIndicators = document.querySelector(".box .slider_indicators");
            var mod = document.querySelector(".box .mod");

            var imgWidth = box.offsetWidth; // 轮播图片的宽度
            var circle = 0; // 辅助变量,用于控制左右侧按钮点击时,小圆圈样式跟随改变
            // mouseover => 存在穿透 mouseenter => 不存在穿透(只对绑定的元素有效)
            // 2、绑定鼠标事件
            box.addEventListener("mouseover", function() {
                prev.style.display = "block";
                next.style.display = "block";
                window.clearInterval(timer);
                timer = null; // 清除变量
            });
            box.addEventListener("mouseout", function() {
                prev.style.display = "none";
                next.style.display = "none";
                timer = window.setInterval(function() {
                    next.click();
                },2000);
            });
            // 3、根据轮播图片动态生成定位小圆圈
            var ul = document.querySelector(".box .mod");
            // console.dir(ul);
            // console.dir(ul.children);
            for (var i = 0; i < ul.children.length; i++) {
                // 创建 li 标签
                var li = document.createElement("li");
                // 记录小圆圈的索引号(自定义属性)
                li.setAttribute("index", i);
                // 点击小圆圈实现被点击元素选中状态切换效果
                li.addEventListener("click", function() {

                    for (var n = 0;n < sliderIndicators.children.length; n++) {
                        if (sliderIndicators.children[n].getAttribute("class")) {
                            sliderIndicators.children[n].setAttribute("class", "");
                            break;
                        }
                    }
                    this.className = "active";
                    // 点击小圆点实现图片切换
                    /*
                    注意是 ul 移动,而不是小 li
                    滚动图片的核心算法:点击某个小圆圈,就让图片滚动,小圆圈的 [索引号]*[图片的宽度] = [ul的移动距离]
                    */
                    // var imgWidth = box.offsetWidth;
                    var index = this.getAttribute("index");
                    // console.log(imgWidth);
                    animate(mod, -imgWidth * index);
                    num = index; // 当我们点击了某个小 li,就要把这个li的索引号给 num
                    circle = index;
                });

                sliderIndicators.appendChild(li);
            }
            // 给第一个小 li 添加一个类名
            sliderIndicators.children[0].className = "active";

            // 4、点击左右侧按钮实现图片的切换
            // 实现图片无缝滚动(原理如下)
            var first = mod.children[0].cloneNode(true);
            mod.appendChild(first);

            // 声明一个变量num,点击一次,自增1,让这个变量乘以图片的宽度,就是ul的滚动距离
            var num = 0;
            prev.addEventListener("click", function() {
                // console.log(num);
                if (num === 0) {
                    num = mod.children.length -1;
                    mod.style.left = -num * imgWidth;
                }
                num--;
                animate(mod, -num * imgWidth);
                // 5、控制小圆圈播放
                circle--;
                // 如果circle < 0 说明是第一张图片,则小圆圈改为第5个小圆圈
                if (circle < 0) {
                    circle = sliderIndicators.children.length - 1;
                }
                // 排他思想
                excludeActive(sliderIndicators, circle);
            });
            next.addEventListener("click", function() {
                num++;
                // console.log(num);
                if (num === mod.children.length - 1) {
                    mod.style.left = 0;
                    num = 0;
                }
                animate(mod, -num * imgWidth);
                // 5、控制小圆圈播放
                circle++;
                if (circle === sliderIndicators.children.length) {
                    circle = 0;
                }

                // 排他思想
                excludeActive(sliderIndicators, circle);
                /*for (var i = 0; i < sliderIndicators.children.length; i++) {
                    sliderIndicators.children[i].setAttribute("class", "");
                }
                sliderIndicators.children[circle].setAttribute("class", "active");*/
            });
            /*
            图片无缝滚动原理:
                1.把 ul第一个 li 复制一份,放到 ul 的最后面
                2.当图片滚动到克隆的最后一张图片时,让 ul 快速的、不做动画的跳到最左侧:left = 0;
                3.最后num = 0重新滚动即可
            */

            // 6、自动播放功能
            /*
                1.自动播放轮播图,实际就是类似于点击了右侧按钮。
                2.此时我们使用手动调用右侧按钮点击事件: next.click();
            */
            var timer = window.setInterval(function() {
                // 手动调用点击事件
                next.click();
            },2000);
        });
        function excludeActive(parentNode, circle) {
            for (var i = 0; i < parentNode.children.length; i++) {
                parentNode.children[i].setAttribute("class", "");
            }
            parentNode.children[circle].setAttribute("class", "active");
        }
        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target, callback) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(object.offsetLeft == target) {
                    // 停止动画
                    clearInterval(object.timer);
                    // 回调函数
                    if (callback) {
                        callback();
                    }
                    // callback && callback();
                }
                // 实现缓动动画
                // 注意:js中小数运算,会出现问题。所以为避免出现运算问题,我们需要把步长转成整数(向上取整)
                var step = (target - object.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step); 
                object.style.left = object.offsetLeft + step + "px";
            }, 15);
        }
    </script>
</body>
</html>

5.2、节流阀

节流阀可以解决的问题:防止轮播图按钮连续点击造成播放过快。

节流阀目的:当上一个函数内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。

核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。

var flag = true;
if (flag) {
	flag = false;
	// 执行目标代码
}
// 利用回调函数,动画执行完毕,flag = true

如以上轮播图案例代码改进:

var flag = true; // 节流阀辅助变量
next.addEventListener("click", function() {
    if (flag) {
        flag = false; // 关闭节流阀
        num++;
        // console.log(num);
        if (num === mod.children.length - 1) {
            mod.style.left = 0;
            num = 0;
        }
        animate(mod, -num * imgWidth, () => {
            flag = true; // 开启节流阀
        });
        // 5、控制小圆圈播放
        circle++;
        if (circle === sliderIndicators.children.length) {
            circle = 0;
        }

        // 排他思想
        excludeActive(sliderIndicators, circle);
    }
});

5.3、带有动画的返回顶部

滚动窗口至文档中的特定位置。

window.scroll(x, y);

代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .center {
            width: 1200px;
            height: 1500px;
            margin: 0 auto;
            background-color: aqua;
        }
        .main {
            background-color: red;
        }
        .top {
            position: fixed; /* 固定定位 */
            display: none;
            right: 70px;
            bottom: 15px;
            width: 30px;
            height: 90px;
            text-align: center;
            cursor: pointer;
            background-color: royalblue;
        }
    </style>
</head>
<body>
    <div class="center"></div>
    <div class="center main"></div>
    <div class="top">返回顶部</div>
    <script>
        window.addEventListener("load", function() {
            var top = document.querySelector(".top");
            // console.log(top);
            window.addEventListener("scroll", function() {
                console.log(window.pageYOffset);
                if (window.pageYOffset > 200) {
                    top.style.display = "block";
                } else {
                    top.style.display = "none";
                }
            });

            top.addEventListener("click", function() {
                // alert(123);
                // 当我们点击了返回顶部模块,就让窗口滚动的页面的最上方 window.scroll(x, y) x y 不带单位
                animate(window, 0);
            });
        });
        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target, callback) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(window.pageYOffset == target) {
                    // 停止动画
                    clearInterval(object.timer);
                    // 回调函数
                    if (callback) {
                        callback();
                    }
                    // callback && callback();
                }
                // 实现缓动动画
                // 注意:js中小数运算,会出现问题。所以为避免出现运算问题,我们需要把步长转成整数(向上取整)
                var step = (target - window.pageYOffset) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step); 
                // object.style.left = window.pageYOffset + step + "px";
                window.scroll(0, window.pageYOffset + step);
            }, 15);
        }
    </script>
</body>
</html>

5.4、导航栏特效(筋斗云)

功能分析:

  1. 鼠标经过某个小li,筋斗云跟着当前小li位置。
  2. 鼠标离开这个小li,筋斗云复原为原来的位置。
  3. 鼠标点击了某个小li,筋斗云就会留在点击这个小li的位置。

案例效果图:
在这里插入图片描述
代码实现:

<!DOCTYPE html>
<html>

<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        * {
            margin: 0;
            padding: 0
        }
        
        ul {
            list-style: none;
        }
        
        body {
            background-color: black;
        }
        
        .c-nav {
            width: 900px;
            height: 42px;
            background: #fff url(images/rss.png) no-repeat right center;
            margin: 100px auto;
            border-radius: 5px;
            position: relative;
        }
        
        .c-nav ul {
            position: absolute;
        }
        
        .c-nav li {
            float: left;
            width: 83px;
            text-align: center;
            line-height: 42px;
        }
        
        .c-nav li a {
            color: #333;
            text-decoration: none;
            display: inline-block;
            height: 42px;
        }
        
        .c-nav li a:hover {
            color: white;
        }
        
        .c-nav li.current a {
            color: #0dff1d;
        }
        
        .cloud {
            position: absolute;
            left: 0;
            top: 0;
            width: 83px;
            height: 42px;
            background: url(images/cloud.gif) no-repeat;
        }
    </style>
    <script>
        window.addEventListener('load', function() {
            // 1. 获取元素
            var cloud = document.querySelector('.cloud');
            var c_nav = document.querySelector('.c-nav');
            var lis = c_nav.querySelectorAll('li');
            // 2. 给所有的小li绑定事件 
            // 这个current 做为筋斗云的起始位置
            var current = 0;
            for (var i = 0; i < lis.length; i++) {
                // (1) 鼠标经过把当前小li 的位置做为目标值
                lis[i].addEventListener('mouseenter', function() {
                    animate(cloud, this.offsetLeft);
                });
                // (2) 鼠标离开就回到起始的位置 
                lis[i].addEventListener('mouseleave', function() {
                    animate(cloud, current);
                });
                // (3) 当我们鼠标点击,就把当前位置做为目标值
                lis[i].addEventListener('click', function() {
                    current = this.offsetLeft;
                });
            }
        });
        // 简单动画封装 object => 目标对象,target => 目标位置
        function animate(object, target, callback) {
            // 给不同的元素指定了不同的定时器
            clearInterval(object.timer); //  保证元素只有一个定时器
            object.timer = setInterval(function() {
                if(object.offsetLeft == target) {
                    // 停止动画
                    clearInterval(object.timer);
                    // 回调函数
                    if (callback) {
                        callback();
                    }
                    // callback && callback();
                }
                // 实现缓动动画
                // 注意:js中小数运算,会出现问题。所以为避免出现运算问题,我们需要把步长转成整数(向上取整)
                var step = (target - object.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step); 
                object.style.left = object.offsetLeft + step + "px";
            }, 15);
        }
    </script>
</head>

<body>
    <div id="c_nav" class="c-nav">
        <span class="cloud"></span>
        <ul>
            <li class="current"><a href="#">首页新闻</a></li>
            <li><a href="#">师资力量</a></li>
            <li><a href="#">活动策划</a></li>
            <li><a href="#">企业文化</a></li>
            <li><a href="#">招聘信息</a></li>
            <li><a href="#">公司简介</a></li>
            <li><a href="#">我是佩奇</a></li>
            <li><a href="#">啥是佩奇</a></li>
        </ul>
    </div>
</body>

</html>

End

Thank you for watching

End

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lambda.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值