2021-09-18 Day21-JS 第十一天 PC端网页特效

一、PC 端网页特效

1. 元素偏移量 offset 系列

1.1 offset 概述

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

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

在这里插入图片描述

offset 系列常用属性:

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

1.2 offset 与 style 区别

offset:

  • offset 可以得到任意样式表中的样式值
  • offset 系列获得的数值是没有单位的
  • offsetWidth 包含padding+border+width
  • offsetWidth 等属性是只读属性,只能获取不能赋值
  • 所以,想要获取元素大小位置,用 offset 更合适

style:

  • style 只能得到行内样式表中的样式值
  • style.width 获得的是带有单位的字符串
  • style.width 获得不包含padding和border的值
  • style.width 是可读写属性,可以获取也可以赋值
  • 所以,想要给元素更改值,则需要用 style 改变

案例:模态框拖拽

需求:

弹出框,也称为模态框。

  1. 点击弹出层, 会弹出模态框, 并且显示灰色半透明的遮挡层。
  2. 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
  3. 鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
  4. 鼠标松开,可以停止拖动模态框移动。

分析:

  1. 点击弹出层, 模态框和遮挡层就会显示出来 display:block;
  2. 点击关闭按钮,模态框和遮挡层就会隐藏起来 display:none;
  3. 在页面中拖拽的原理: 鼠标按下并且移动, 之后松开鼠标
  4. 触发事件是鼠标按下 mousedown, 鼠标移动mousemove 鼠标松开 mouseup
  5. 拖拽过程: 鼠标移动过程中,获得最新的值赋值给模态框的left和top值, 这样模态框可以跟着鼠标走了
  6. 鼠标按下触发的事件源是 最上面一行,就是 id 为 title
  7. 鼠标的坐标 减去 鼠标在盒子内的坐标, 才是模态框真正的位置。
  8. 鼠标按下,我们要得到鼠标在盒子的坐标。
  9. 鼠标移动,就让模态框的坐标 设置为 : 鼠标坐标 减去盒子坐标即可,注意移动事件写到按下事件里面。
  10. 鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除

代码:

<!DOCTYPE html>
<html lang="zh-CN">

<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>模态框拖拽</title>
    <style>
        .login-header {
            width: 100%;
            text-align: center;
            height: 30px;
            font-size: 24px;
            line-height: 30px;
        }
        
        ul,
        li,
        ol,
        dl,
        dt,
        dd,
        div,
        p,
        span,
        h1,
        h2,
        h3,
        h4,
        h5,
        h6,
        a {
            padding: 0px;
            margin: 0px;
        }
        
        .login {
            display: none;
            width: 512px;
            height: 280px;
            position: fixed;
            border: #ebebeb solid 1px;
            left: 50%;
            top: 50%;
            background: #ffffff;
            box-shadow: 0px 0px 20px #ddd;
            z-index: 9999;
            transform: translate(-50%, -50%);
        }
        
        .login-title {
            width: 100%;
            margin: 10px 0px 0px 0px;
            text-align: center;
            line-height: 40px;
            height: 40px;
            font-size: 18px;
            position: relative;
            cursor: move;
        }
        
        .login-input-content {
            margin-top: 20px;
        }
        
        .login-button {
            width: 50%;
            margin: 30px auto 0px auto;
            line-height: 40px;
            font-size: 14px;
            border: #ebebeb 1px solid;
            text-align: center;
        }
        
        .login-bg {
            display: none;
            width: 100%;
            height: 100%;
            position: fixed;
            top: 0px;
            left: 0px;
            background: rgba(0, 0, 0, .3);
        }
        
        a {
            text-decoration: none;
            color: #000000;
        }
        
        .login-button a {
            display: block;
        }
        
        .login-input input.list-input {
            float: left;
            line-height: 35px;
            height: 35px;
            width: 350px;
            border: #ebebeb 1px solid;
            text-indent: 5px;
        }
        
        .login-input {
            overflow: hidden;
            margin: 0px 0px 20px 0px;
        }
        
        .login-input label {
            float: left;
            width: 90px;
            padding-right: 10px;
            text-align: right;
            line-height: 35px;
            height: 35px;
            font-size: 14px;
        }
        
        .login-title span {
            position: absolute;
            font-size: 12px;
            right: -20px;
            top: -30px;
            background: #ffffff;
            border: #ebebeb solid 1px;
            width: 40px;
            height: 40px;
            border-radius: 20px;
        }
    </style>
</head>

<body>
    <div class="login-header"><a id="link" href="javascript:;">点击,弹出登录框</a></div>
    <div id="login" class="login">
        <div id="title" class="login-title">登录会员
            <span><a id="closeBtn" href="javascript:void(0);" class="close-login">关闭</a></span>
        </div>
        <div class="login-input-content">
            <div class="login-input">
                <label>用户名:</label>
                <input type="text" placeholder="请输入用户名" name="info[username]" id="username" class="list-input">
            </div>
            <div class="login-input">
                <label>登录密码:</label>
                <input type="password" placeholder="请输入登录密码" name="info[password]" id="password" class="list-input">
            </div>
        </div>
        <div id="loginBtn" class="login-button"><a href="javascript:void(0);" id="login-button-submit">登录会员</a></div>
    </div>
    <!-- 遮盖层 -->
    <div id="bg" class="login-bg"></div>
    <script>
        // 点击弹出层, 会弹出模态框, 并且显示灰色半透明的遮挡层。
        // 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
        // 鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
        // 鼠标松开,可以停止拖动模态框移动。

        // 1. 获取元素
        let login = document.querySelector('.login');
        let mask = document.querySelector('.login-bg');
        let link = document.querySelector('#link');
        let closeBtn = document.querySelector('#closeBtn');
        let title = document.querySelector('#title');
        // 2. 点击 link 让mask 和 login 显示出来
        link.addEventListener('click', function() {
            mask.style.display = 'block';
            login.style.display = 'block';
        });
        // 3. 点击 closeBtn 让 mask 和 login 隐藏起来
        closeBtn.addEventListener('click', function() {
            mask.style.display = 'none';
            login.style.display = 'none';
        });
        // 4. 开始拖拽
        // (1) 当鼠标按下,就获得鼠标在盒子内的坐标
        title.addEventListener('mousedown', function(e) {
            let x = e.pageX - login.offsetLeft;
            let y = e.pageY - login.offsetTop;
            // (2) 当鼠标移动 把鼠标在页面中的坐标 减去 鼠标在盒子内的坐标就是模态框的 left 和 top 值
            document.addEventListener('mousemove', move)

            function move(e) {
                login.style.left = e.pageX - x + 'px';
                login.style.top = e.pageY - y + 'px';
            }
            // (3) 当鼠标弹起
            document.addEventListener('mouseup', function() {
                document.removeEventListener('mousemove', move)
            })
        })
    </script>
</body>

</html>

案例:仿京东放大镜

分析:

    1. 整个案例可以分为三个功能模块

    2. 鼠标经过小图片盒子, 黄色的遮挡层 和 大图片盒子显示,离开隐藏2个盒子功能

    3. 黄色的遮挡层跟随鼠标功能。

    4. 移动黄色遮挡层,大图片跟随移动功能。

    1. 鼠标经过小图片盒子, 黄色的遮挡层 和 大图片盒子显示,离开隐藏2个盒子功能

    2. 就是显示与隐藏

    1. 黄色的遮挡层跟随鼠标功能。

    2. 把鼠标坐标给遮挡层不合适。因为遮挡层坐标以父盒子为准。

    3. 首先是获得鼠标在盒子的坐标。

    4. 之后把数值给遮挡层做为left 和top值。

    5. 此时用到鼠标移动事件,但是还是在小图片盒子内移动。

    6. 发现,遮挡层位置不对,需要再减去盒子自身高度和宽度的一半。

    7. 遮挡层不能超出小图片盒子范围。

    8. 如果小于零,就把坐标设置为0

    9. 如果大于遮挡层最大的移动距离,就把坐标设置为最大的移动距离

    10. 遮挡层的最大移动距离: 小图片盒子宽度 减去 遮挡层盒子宽度

    1. 移动黄色遮挡层,大图片跟随移动功能。

    2. 求大图片的移动距离公式

      大图片移动距离 = 遮挡层移动距离*大图片最大移动距离 / 遮挡层最大移动距离

代码:

window.addEventListener('load', function() {
    let preview_img = document.querySelector('.preview_img');
    let mask = document.querySelector('.mask');
    let big = document.querySelector('.big');
    // 1. 当鼠标经过 preview_img 就显示和隐藏 mask 和 big 盒子
    preview_img.addEventListener('mouseover', function() {
        mask.style.display = 'block';
        big.style.display = 'block';
    })
    preview_img.addEventListener('mouseout', function() {
            mask.style.display = 'none';
            big.style.display = 'none';
        })
        // 2.鼠标移动时,让 maks 盒子跟着移动
    preview_img.addEventListener('mousemove', function(e) {
        // (1) 计算鼠标在盒子内的坐标
        let x = e.pageX - this.offsetLeft;
        let y = e.pageY - this.offsetTop;
        // console.log(x, y);
        // (2) 鼠标在 mask 盒子的中间, 减去盒子高度 200 的一半是 100, 就是 mask的 left 和 top 值了
        // (3) mask 的移动距离
        let maskX = x - mask.offsetWidth / 2;
        let maskY = y - mask.offsetWidth / 2;
        // (4) mask 移动距离
        //遮挡层最大移动距离
        let maskMax = preview_img.offsetWidth - mask.offsetWidth;
        if (maskX <= 0) {
            maskX = 0;
        } else if (maskX >= maskMax) {
            maskX = maskMax;
        }
        if (maskY <= 0) {
            maskY = 0;
        } else if (maskY >= maskMax) {
            maskY = maskMax;
        }
        mask.style.left = maskX + 'px';
        mask.style.top = maskY + 'px';
        //大图片移动距离 = 遮挡层移动距离*大图片最大移动距离 / 遮挡层最大移动距离
        let bigImg = document.querySelector('.bigImg');
        //大图片最大移动距离
        let bigMax = bigImg.offsetWidth - big.offsetWidth;
        //大图片移动距离 X Y 
        let bigX = maskX * bigMax / maskMax;
        let bigY = maskY * bigMax / maskMax;
        bigImg.style.left = -bigX + 'px';
        bigImg.style.top = -bigY + 'px';
    })
})

2. 元素可视区 client 系列

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

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

在这里插入图片描述

案例: 淘宝 flexible.js 源码 分析

立即执行函数 (function() {})() 或者 (function(){}())

主要作用: 创建一个独立的作用域。 避免了命名冲突问题

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

下面三种情况都会刷新页面都会触发 load 事件。

  1. a标签的超链接
  2. F5或者刷新按钮(强制刷新)
  3. 前进后退按钮

但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。

所以此时后退按钮不能刷新页面。

此时可以使用 pageshow事件来触发。,这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加。

3. 元素滚动 scroll 系列

3.1 元素scroll 系列属性

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

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

在这里插入图片描述

3.2 页面被卷去的头部

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

案例:仿淘宝固定右侧侧边栏

需求:

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

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

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

分析:

  1. 需要用到页面滚动事件 scroll 因为是页面滚动,所以事件源是 document
  2. 滚动到某个位置,就是判断页面被卷去的上部值。
  3. 页面被卷去的头部:可以通过window.pageYOffset 获得 如果是被卷去的左侧 window.pageXOffset
  4. 注意,元素被卷去的头部是 element.scrollTop , 如果是页面被卷去的头部 则是 window.pageYOffset
  5. 其实这个值 可以通过盒子的 offsetTop 可以得到,如果大于等于这个值,就可以让盒子固定定位了

代码:

<body>
    <div class="slider-bar">
        <span class="goBack">返回顶部</span>
    </div>
    <div class="header w">头部区域</div>
    <div class="banner w">banner区域</div>
    <div class="main w">主体部分</div>
    <script>
        //1. 获取元素
        let sliderBar = document.querySelector('.slider-bar');
        let banner = document.querySelector('.banner');
        // banner.offsetTop  banner盒子被卷去头部的大小
        let bannerTop = banner.offsetTop;
        // 当我们侧边栏固定定位之后应该变化的数值
        let sliderBarTop = sliderBar.offsetTop - bannerTop;
        // 获取main 主体元素
        let main = document.querySelector('.main');
        let goBack = document.querySelector('.goBack');
        //main.offsetTop main盒子被卷去的头部大小
        let mainTop = main.offsetTop;
        // 2. 页面滚动事件 scroll
        document.addEventListener('scroll', function() {
            // console.log(window.pageYOffset);
            // window.pageYOffset 页面被卷去的头部
            // 3.当页面被卷去的头部大于等于170时,侧边栏就改为固定定位
            if (window.pageYOffset >= bannerTop) {
                sliderBar.style.position = 'fixed';
                sliderBar.style.top = sliderBarTop + 'px';
            } else {
                sliderBar.style.position = 'absolute';
                sliderBar.style.top = '300px';
            }
            // 4. 当我们页面滚动到main盒子,就显示 goback模块
            if (window.pageYOffset >= mainTop) {
                goBack.style.display = 'block';
            } else {
                goBack.style.display = 'none';
            }
        })
    </script>

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

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

  1. 声明了 DTD,使用 document.documentElement.scrollTop
  2. 未声明 DTD,使用 document.body.scrollTop
  3. 新方法 window.pageYOffset 和 window.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

3. 4 三大系列总结

三大邪猎大小对比作用
element.offsetWidth返回自身包括padding、边框、内容区的宽度,返回数值不带单位
element.clientWdith返回自身包括padding、内容区的宽度,不含边框,返回数值不带单位
element.scrollWidth返回自身实际的宽度,不含边框,返回数值不带单位

主要用法:

  1. offset系列 经常用于获得元素位置 offsetLeft offsetTop
  2. client 经常用于获取元素大小 clientWidth clientHeight
  3. scroll 经常用于获取滚动距离 scrollTop scrollLeft
  4. 注意页面滚动的距离通过 window.pageXOffset 获得

mouseenter 和 mouseover 的区别 (面试题)

mouseenter 鼠标事件

当鼠标移动到元素上时就会触发 mouseenter 事件

类似 mouseover,它们两者之间的差别是:

  • mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。 mouseenter 只会经过自身盒子触发,之所以这样,就是因为mouseenter不会冒泡

  • 跟mouseenter搭配 鼠标离开 mouseleave 同样不会冒泡

4. 动画函数封装

4.1 动画实现原理

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

实现步骤:

  1. 获得盒子当前位置

  2. 让盒子在当前位置加上1个移动距离

  3. 利用定时器不断重复这个操作

  4. 加一个结束定时器的条件

  5. 注意此元素需要添加定位,才能使用element.style.left

<body>
    <div></div>
    <script>
        // 1. 获得盒子当前位置
        // 2. 让盒子在当前位置加上1个移动距离
        // 3. 利用定时器不断重复这个操作
        // 4. 加一个结束定时器的条件
        // 5. 注意此元素需要添加定位,才能使用element.style.left
        let div = document.querySelector('div');
        let timer = setInterval(function() {
            div.style.left = div.offsetLeft + 1 + 'px';
            if (div.offsetLeft >= 300) {
                //停止动画 本质就是停止定时器
                clearInterval(timer);
            }
        }, 30)
    </script>
</body>

4.2 动画函数简单封装

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

<body>
    <div>吕布</div>
    <span>貂蝉</span>
    <script>
        // 简单动画封装 obj目标对象, target 目标位置
        // 给不同的元素指定了不同的定时器
        var div = document.querySelector('div');
        var span = document.querySelector('span');

        function animate(obj, target) {
            var timer = setInterval(function() {
                obj.style.left = obj.offsetLeft + 1 + 'px';
                if (obj.offsetLeft >= target) {
                    clearInterval(timer);
                }
            }, 100)
        }
        animate(div, 300);
        animate(span, 200);
    </script>
</body>

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

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

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

<body>
    <button>点击按钮貂蝉动</button>
    <div>吕布</div>
    <span>貂蝉</span>
    <script>
        let div = document.querySelector('div');
        let span = document.querySelector('span');
		let btn = document.querySelector('button');
        function animate(obj, target) {
            // 当不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
            // 解决方案:让元素只有一个定时器执行
            // 先清除以前的定时器,只保留当前的一个定时器执行	
            celarInterval(obj.timer);
             obj.timer = setInterval(function() {
                obj.style.left = obj.offsetLeft + 1 + 'px';
                if (obj.offsetLeft >= target) {
                    clearInterval(obj.timer);
                }
            }, 100)
        }
        animate(div, 300);
		btn.addEventListener('click',function() {
            animate(span, 200);
        })
    </script>
</body>

4.4 缓动效果原理

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

思路:

  1. 让盒子每次移动的距离慢慢变小,速度就会慢慢降下来。
  2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
  3. 停止的任务是:让当前盒子位置等于目标位置就停止定时器
  4. 注意步长值需要取整
代码实现
    <style>
        div {
            position: relative;
            left: 0;
            top: 0;
            width: 300px;
            height: 300px;
            background-color: skyblue;
        }
    </style>
<body>
    <button>动画缓动</button>
    <div>貂蝉</div>
    <script>
        let btn = document.querySelector('button');
        let div = document.querySelector('div');

        function animate(obj, target) {
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(obj.timer);
            obj.timer = setInterval(function() {
                // 步长值写到定时器的里面
                let step = (target - obj.offsetLeft) / 10;
                // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
                obj.style.left = obj.offsetLeft + step + 'px';
                if (obj.offsetLeft == target) {
                    // 停止动画 本质是停止定时器
                    clearInterval(obj.timer);
                }
            }, 30);
        }
        btn.addEventListener('click', function() {
            // 调用函数
            animate(div, 1000);
        });
    </script>
</body>

4. 5 动画函数多个目标之间移动

可以让动画函数从800 移动到 500。

当点击按钮的时候,判断步长是正值还是负值

  1. 如果是正值,则步长 往大了取整
  2. 如果是负值,则步长 往小了取整
代码实现
<head>
    <title>缓动动画</title>
    <style>
        div {
            position: absolute;
            left: 0;
            top: 150px;
            width: 300px;
            height: 300px;
            background-color: skyblue;
        }
    </style>
</head>

<body>
    <button class="btn500">貂蝉到500</button>
    <button class="btn800">貂蝉到800</button>
    <div>貂蝉</div>
    <script>
        // 缓动动画函数封装obj目标对象 target 目标位置
        // 思路:
        // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
        // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
        // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
        let btn500 = document.querySelector('.btn500');
        let btn800 = document.querySelector('.btn800');
        let div = document.querySelector('div');

        function animate(obj, target) {
            // 先清除以前的定时器,只保留当前的一个定时器执行
            clearInterval(obj.timer);
            obj.timer = setInterval(function() {
                // 步长值写到定时器的里面
                let step = (target - obj.offsetLeft) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                if (obj.offsetLeft == target) {
                    // 停止动画 本质是停止定时器
                    clearInterval(obj.timer);
                }
                // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
                obj.style.left = obj.offsetLeft + step + 'px';
            }, 15);
        }
        btn500.addEventListener('click', function() {
            // 调用函数
            animate(div, 500);
        });
        btn800.addEventListener('click', function() {
            // 调用函数
            animate(div, 800);
        });
        // 匀速动画 就是 盒子是当前的位置 +  固定的值 10 
        // 缓动动画就是  盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
    </script>
</body>

4.6 动画函数添加回调函数

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

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

代码实现
<head>
   <title>缓动动画之回调函数</title>
   <style>
       div {
           position: absolute;
           left: 0;
           top: 150px;
           width: 300px;
           height: 300px;
           background-color: skyblue;
       }
   </style>
</head>

<body>
   <button class="btn500">貂蝉到500</button>
   <button class="btn800">貂蝉到800</button>
   <div>貂蝉</div>
   <script>
       let btn500 = document.querySelector('.btn500');
       let btn800 = document.querySelector('.btn800');
       let div = document.querySelector('div');

       function animate(obj, target, callback) {
           // 先清除以前的定时器,只保留当前的一个定时器执行
           clearInterval(obj.timer);
           obj.timer = setInterval(function() {
               // 步长值写到定时器的里面
               let step = (target - obj.offsetLeft) / 10;
               step = step > 0 ? Math.ceil(step) : Math.floor(step);
               if (obj.offsetLeft == target) {
                   // 停止动画 本质是停止定时器
                   clearInterval(obj.timer);
                   if (callback) {
                       callback();
                   }
               }
               // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
               obj.style.left = obj.offsetLeft + step + 'px';
           }, 15);
       }
       btn500.addEventListener('click', function() {
           // 调用函数
           animate(div, 500);
       });
       btn800.addEventListener('click', function() {
           // 调用函数
           animate(div, 800, function() {
               div.style.backgroundColor = 'red';
           });
       });
   </script>
</body>

4.7 动画函数封装到 JS 文件里

如果以后经常使用这个动画函数,可以单独封装到一个JS文件里面,使用的时候引用这个JS文件即可。

  1. 单独新建一个JS文件。
  2. HTML文件引入 JS 文件。
代码实现
<head>
    <title>引用动画函数</title>
    <style>
        .sliderbar {
            position: fixed;
            right: 0;
            bottom: 100px;
            width: 40px;
            height: 40px;
            text-align: center;
            line-height: 40px;
            cursor: pointer;
            color: #fff;
        }
        
        .con {
            position: absolute;
            left: 0;
            top: 0;
            width: 200px;
            height: 40px;
            background-color: purple;
            z-index: -1;
        }
    </style>
    <script src="animate.js"></script>
</head>

<body>
    <div class="sliderbar">
        <span></span>
        <div class="con">问题反馈</div>
    </div>
    <script>
        // 当鼠标经过 sliderbar 就会让 con这个盒子滑动到左侧
        // 当鼠标离开 sliderbar 就会让 con这个盒子滑动到右侧
        let sliderbar = document.querySelector('.sliderbar');
        let con = document.querySelector('.con');
        // let span = document.querySelector('span');
        sliderbar.addEventListener('mouseenter', function() {
            animate(con, -160, function() {
                sliderbar.children[0].innerHTML = '→';
                // span.innerHTML = '→';
            })
        })
        sliderbar.addEventListener('mouseleave', function() {
            animate(con, 0, function() {
                sliderbar.children[0].innerHTML = '←';
                // span.innerHTML = '←';
            })

        })
    </script>
</body>

5. 常见网页特效案例

5.1 案例:网页轮播图

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

功能需求:
  1. 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
  2. 点击右侧按钮一次,图片往左播放一张,以此类推, 左侧按钮同理。
  3. 图片播放的同时,下面小圆圈模块跟随一起变化。
  4. 点击小圆圈,可以播放相应图片。
  5. 鼠标不经过轮播图, 轮播图也会自动播放图片。
  6. 鼠标经过,轮播图模块, 自动播放停止。
案例分析:
第一大步
  1. 因为js较多,可以单独新建js文件夹,再新建js文件, 引入页面中。
  2. 此时需要添加 load 事件。
  3. 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
  4. 显示隐藏 display 按钮。
第二大步
  1. 动态生成小圆圈
  2. 核心思路:小圆圈的个数要跟图片张数一致
  3. 所以首先先得到ul里面图片的张数(图片放入li里面,所以就是li的个数)
  4. 利用循环动态生成小圆圈(这个小圆圈要放入ol里面)
  5. 创建节点 createElement(‘li’)
  6. 插入节点 ol. appendChild(li)
  7. 第一个小圆圈需要添加 current 类
第三大步
  1. 小圆圈的排他思想
  2. 点击当前小圆圈,就添加current类
  3. 其余的小圆圈就移除这个current类
  4. 注意: 在刚才生成小圆圈的同时,就可以直接绑定这个点击事件了。
第四大步
  1. 点击小圆圈滚动图片
  2. 此时用到animate动画函数,将js文件引入(注意,因为index.js 依赖 animate.js 所以,animate.js 要写到 index.js 上面)
  3. 使用动画函数的前提,该元素必须有定位
  4. 注意是ul 移动 而不是小li
  5. 滚动图片的核心算法: 点击某个小圆圈 , 就让图片滚动 小圆圈的索引号乘以图片的宽度做为ul移动距离
  6. 此时需要知道小圆圈的索引号, 我们可以在生成小圆圈的时候,给它设置一个自定义属性,点击的时候获取这个自定义属性即可。
第五大步
  1. 点击右侧按钮一次,就让图片滚动一张。
  2. 声明一个变量num, 点击一次,自增1, 让这个变量乘以图片宽度,就是 ul 的滚动距离。
  3. 图片无缝滚动原理
    • 把ul 第一个li 复制一份,放到ul 的最后面
    • 当图片滚动到克隆的最后一张图片时, 让ul 快速的、不做动画的跳到最左侧: left 为0
    • 同时num 赋值为0,可以从新开始滚动图片了
  4. 克隆第一张图片
  5. 克隆ul 第一个li cloneNode() 加true 深克隆 复制里面的子节点 false 浅克隆
  6. 添加到 ul 最后面 appendChild
第六大步
  1. 点击右侧按钮, 小圆圈跟随变化
  2. 最简单的做法是再声明一个变量circle,每次点击自增1,注意,左侧按钮也需要这个变量,因此要声明全局变量。
  3. 但是图片有5张,我们小圆圈只有4个少一个,必须加一个判断条件
  4. 如果circle == 4 就 从新复原为 0
第七大步
  1. 自动播放功能
  2. 添加一个定时器
  3. 自动播放轮播图,实际就类似于点击了右侧按钮
  4. 此时我们使用手动调用右侧按钮点击事件 arrow_r.click()
  5. 鼠标经过focus 就停止定时器
  6. 鼠标离开focus 就开启定时器
代码实现:

html文件

<!DOCTYPE html>
<html lang="zh-CN">

<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>淘宝轮播图案例</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        
        a {
            color: #666;
            text-decoration: none;
        }
        
        a:hover {
            color: #e33333;
        }
        
        li {
            list-style: none;
        }
        
        .focus {
            position: relative;
            width: 721px;
            height: 455px;
            background-color: purple;
            margin: 200px auto;
            overflow: hidden;
        }
        
        .focus ul {
            position: absolute;
            top: 0;
            left: 0;
            width: 600%;
        }
        
        .focus ul li {
            float: left;
        }
        
        .arrow-l,
        .arrow-r {
            display: block;
            position: absolute;
            top: 50%;
            margin-top: -20px;
            width: 24px;
            height: 40px;
            background: rgba(0, 0, 0, .3);
            text-align: center;
            line-height: 40px;
            color: #fff;
            font-size: 18px;
            z-index: 2;
        }
        
        .arrow-r {
            right: 0;
        }
        
        .circle {
            position: absolute;
            bottom: 10px;
            left: 50px;
        }
        
        .circle li {
            float: left;
            width: 8px;
            height: 8px;
            /*background-color: #fff;*/
            border: 2px solid rgba(255, 255, 255, 0.5);
            margin: 0 3px;
            border-radius: 50%;
            /*鼠标经过显示小手*/
            cursor: pointer;
        }
        
        .current {
            background-color: #fff;
        }
    </style>
    <script src="animate.js"></script>
    <script src="index.js"></script>
</head>

<body>
    <div class="focus">
        <!-- 左侧按钮 -->
        <a href="javascript:;" class="arrow-l">&lt;</a>
        <!-- 右侧按钮 -->
        <a href="javascript:;" class="arrow-r">&gt;</a>
        <!-- 核心的滚动区域 -->
        <ul>
            <li>
                <a href="#"><img src="images/focus.jpg" alt=""></a>
            </li>
            <li>
                <a href="#"><img src="images/focus1.jpg" alt=""></a>
            </li>
            <li>
                <a href="#"><img src="images/focus2.jpg" alt=""></a>
            </li>
            <li>
                <a href="#"><img src="images/focus3.jpg" alt=""></a>
            </li>
        </ul>
        <!-- 小圆圈 -->
        <ol class="circle">
        </ol>
    </div>
</body>

</html>

javascript文件

window.addEventListener('load', function() {
    // 1. 获取元素
    let arrow_l = document.querySelector('.arrow-l');
    let arrow_r = document.querySelector('.arrow-r');
    let focus = document.querySelector('.focus');
    let focusWidth = focus.offsetWidth;
    // 2. 鼠标经过 focus 就显示左右按钮 离开就隐藏
    focus.addEventListener('mouseenter', function() {
        arrow_l.style.display = 'block';
        arrow_r.style.display = 'block';
        clearInterval(timer);
        timer = null; //清除定时器变量
    });
    focus.addEventListener('mouseleave', function() {
        arrow_l.style.display = 'none';
        arrow_r.style.display = 'none';
        timer = setInterval(function() {
            arrow_r.click();
        }, 2000);
    });
    // 3. 动态生成小圆圈 有几张图就生成几个小圆圈
    let ul = focus.querySelector('ul');
    let ol = focus.querySelector('.circle');
    for (let i = 0; i < ul.children.length; i++) {
        //创建一个li
        let li = document.createElement('li');
        // 5. 记录当前小圆圈的索引号,通过自定义属性
        li.setAttribute('index', i);
        //把li 插入到 ul中去
        ol.appendChild(li);
        // 4. 小圆圈的排他思想 可以直接在生成小圆圈的同时直接绑定点击事件
        li.addEventListener('click', function() {
            // 干掉所有人 把所有的li 清除掉 current 类
            for (let i = 0; i < ol.children.length; i++) {
                ol.children[i].className = '';
            }
            // 留下自己 当前的li 设置 current 类
            this.className = 'current';
            // 5. 点击li 移动图片, 移动的是ul
            // ul 的移动距离是 小圆圈的索引号*图片的宽度 注意是负值(往左走)
            // 点击了某个li 就拿到当前li 的索引号
            let index = this.getAttribute('index');
            // 当某个li被点击了,就要把这个li的索引号给num
            num = index;
            // 当某个li被点击了,就要把这个li的索引号给circle
            circle = index;
            // num = circle = index;
            animate(ul, -index * focusWidth);
        })
    };
    // 把ol里第一个li添加current类
    ol.children[0].className = 'current';
    // 6. 克隆第一张图片
    let first = ul.children[0].cloneNode(true);
    ul.appendChild(first);
    // 7. 点击右侧按钮,图片移动一张
    let num = 0;
    let circle = 0;
    arrow_r.addEventListener('click', function() {
        // 如果走到了最后复制的一张图片,此时ul 要快速复原 left = 0;
        if (num == ul.children.length - 1) {
            ul.style.left = 0;
            num = 0;
        };
        num++;
        animate(ul, -num * focusWidth);
        // 8. 点击右侧按钮,小圆圈跟随一起变化 可以再声明一个变量控制小圆圈的播放
        circle++;
        // 如果circle == 4,说明走到最后克隆的图片了,需要复原
        // if (circle == ol.children.length) {
        //     circle = 0;
        // }
        circle = circle == ol.children.length ? 0 : circle;
        circleChange();
    });
    // 9. 左侧按钮
    arrow_l.addEventListener('click', function() {
        if (num == 0) {
            num = ul.children.length - 1;
            ul.style.left = -num * focusWidth + 'px';
        };
        num--;
        animate(ul, -num * focusWidth);
        circle--;
        // 如果circle < 0,说明第一张图片,则小圆圈要改为第4个小圆圈(3),
        if (circle < 0) {
            circle = ol.children.length - 1;
        }
        circleChange();
    });
    //小圆圈的排他思想
    function circleChange() {
        //清除其他小圆圈的current 类
        for (let i = 0; i < ol.children.length; i++) {
            ol.children[i].className = '';
        };
        // 留下当前小圆圈的current类
        ol.children[circle].className = 'current';
    };
    // 10. 自动播放轮播图 定时器
    let timer = this.setInterval(function() {
        arrow_r.click();
    }, 2000);
});
function animate(obj, target, callback) {
    // console.log(callback);  callback = function() {}  调用的时候 callback()

    // 先清除以前的定时器,只保留当前的一个定时器执行
    clearInterval(obj.timer);
    obj.timer = setInterval(function() {
        // 步长值写到定时器的里面
        // 把我们步长值改为整数 不要出现小数的问题
        // var step = Math.ceil((target - obj.offsetLeft) / 10);
        var step = (target - obj.offsetLeft) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (obj.offsetLeft == target) {
            // 停止动画 本质是停止定时器
            clearInterval(obj.timer);
            // 回调函数写到定时器结束里面
            // if (callback) {
            //     // 调用函数
            //     callback();
            // }
            callback && callback();
        }
        // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
        obj.style.left = obj.offsetLeft + step + 'px';

    }, 5);
}

5.2 节流阀

防止轮播图按钮连续点击造成播放过快。

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

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

开始设置一个变量 var flag = true;

If(flag) {flag = false; do something} 关闭水龙头

利用回调函数 动画执行完毕, flag = true 打开水龙头

  arrow_l.addEventListener('click', function() {
        if (flage) {
            flage = false;
            if (num == 0) {
                num = ul.children.length - 1;
                ul.style.left = -num * focusWidth + 'px';
            };
            num--;
            animate(ul, -num * focusWidth, function() {
                flage = true;
            });
            circle--;
            if (circle < 0) {
                circle = ol.children.length - 1;
            }
            circleChange();
        }
    });

5.3 案例:返回顶部

需求:

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

window.scroll(x, y)

注意,里面的x和y 不跟单位,直接写数字

分析:
  1. 带有动画的返回顶部
  2. 此时可以继续使用我们封装的动画函数
  3. 只需要把所有的left 相关的值 改为 跟 页面垂直滚动距离相关就可以了
  4. 页面滚动了多少,可以通过 window.pageYOffset 得到
  5. 最后是页面滚动,使用 window.scroll(x,y)
代码实现:
<!DOCTYPE html>
<html lang="zh-CN">

<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>案例:返回顶部</title>
    <style>
        .slider-bar {
            position: absolute;
            left: 50%;
            top: 300px;
            margin-left: 600px;
            width: 45px;
            height: 130px;
            background-color: pink;
        }
        
        .w {
            width: 1200px;
            margin: 10px auto;
        }
        
        .header {
            height: 150px;
            background-color: purple;
        }
        
        .banner {
            height: 250px;
            background-color: skyblue;
        }
        
        .main {
            height: 1000px;
            background-color: yellowgreen;
        }
        
        span {
            display: none;
            position: absolute;
            bottom: 0;
        }
    </style>
</head>

<body>
    <div class="slider-bar">
        <span class="goBack">返回顶部</span>
    </div>
    <div class="header w">头部区域</div>
    <div class="banner w">banner区域</div>
    <div class="main w">主体部分</div>
    <script>
        //1. 获取元素
        let sliderBar = document.querySelector('.slider-bar');
        let banner = document.querySelector('.banner');
        // banner.offsetTop  banner盒子被卷去头部的大小
        let bannerTop = banner.offsetTop;
        // 当我们侧边栏固定定位之后应该变化的数值
        let sliderBarTop = sliderBar.offsetTop - bannerTop;
        // 获取main 主体元素
        let main = document.querySelector('.main');
        let goBack = document.querySelector('.goBack');
        //main.offsetTop main盒子被卷去的头部大小
        let mainTop = main.offsetTop;
        // 2. 页面滚动事件 scroll
        document.addEventListener('scroll', function() {
                // console.log(window.pageYOffset);
                // window.pageYOffset 页面被卷去的头部
                // 3.当页面被卷去的头部大于等于170时,侧边栏就改为固定定位
                if (window.pageYOffset >= bannerTop) {
                    sliderBar.style.position = 'fixed';
                    sliderBar.style.top = sliderBarTop + 'px';
                } else {
                    sliderBar.style.position = 'absolute';
                    sliderBar.style.top = '300px';
                }
                // 4. 当我们页面滚动到main盒子,就显示 goback模块
                if (window.pageYOffset >= mainTop) {
                    goBack.style.display = 'block';
                } else {
                    goBack.style.display = 'none';
                }
            })
            //当点击了goback 就返回顶部
        goBack.addEventListener('click', function() {
            //滚动的对象是窗口,所以是window
            animate(window, 0);
        });

        function animate(obj, target, callback) {
            clearInterval(obj.timer);
            obj.timer = setInterval(function() {
                let step = (target - window.pageYOffset) / 10;
                step = step > 0 ? Math.ceil(step) : Math.floor(step);
                if (window.pageYOffset == target) {
                    clearInterval(obj.timer);
                    // if (callback) {
                    //     callback();
                    // }
                    callback && callback();
                }
                let wpy = window.pageYOffset + step;
                window.scroll(0, wpy);
            }, 15);
        }
    </script>
</body>

</html>

5.4 案例:筋斗云案例

需求:
  1. 鼠标经过某个小li, 筋斗云跟这到当前小li位置
  2. 鼠标离开这个小li, 筋斗云复原为原来的位置
  3. 鼠标点击了某个小li, 筋斗云就会留在点击这个小li 的位置
分析:
  1. 利用动画函数做动画效果
  2. 原先筋斗云的起始位置是0
  3. 鼠标经过某个小li, 把当前小li 的 offsetLeft 位置 做为目标值即可
  4. 鼠标离开某个小li, 就把目标值设为 0
  5. 如果点击了某个小li, 就把li当前的位置存储起来,做为筋斗云的起始位置
代码实现:

动画效果JS

function animate(obj, target, callback) {
    clearInterval(obj.timer);
    obj.timer = setInterval(function() {
        let step = (target - obj.offsetLeft) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step);
        if (obj.offsetLeft == target) {
            clearInterval(obj.timer);
            // if (callback) {
            //     callback();
            // }
            callback && callback();
        }
        obj.style.left = obj.offsetLeft + step + 'px';
    }, 15);
}
<!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;
        }
        
        .cloud {
            position: absolute;
            left: 0;
            top: 0;
            width: 83px;
            height: 42px;
            background: url(images/cloud.gif) no-repeat;
        }
    </style>
    <script src="animate.js"></script>
    <script>
        window.addEventListener('load', function() {
            // 获取元素
            let c_nav = document.querySelector('.c-nav');
            let cloud = document.querySelector('.cloud');
            let lis = c_nav.querySelectorAll('li');
            // 给所以li绑定事件
            let current = 0; // current 作为 cloud 的起始位置 
            for (let i = 0; i < lis.length; i++) {
                // (1) 鼠标经过 cloud移动到目标位置
                lis[i].addEventListener('mouseenter', function() {
                    animate(cloud, this.offsetLeft);
                });
                // (2) 鼠标离开 cloud 返回起始的位置
                lis[i].addEventListener('mouseleave', function() {
                    animate(cloud, current);
                });
                // (3) 鼠标点击某个li后,就把当前的位置作为cloud的起始位置
                lis[i].addEventListener('click', function() {
                    current = this.offsetLeft;
                })
            }
        })
    </script>
</head>
<body>
    <div id="c_nav" class="c-nav">
        <span class="cloud"></span>
        <ul>
            <li><a href="javascript:;">首页新闻</a></li>
            <li><a href="javascript:;">师资力量</a></li>
            <li><a href="javascript:;">活动策划</a></li>
            <li><a href="javascript:;">企业文化</a></li>
            <li><a href="javascript:;">招聘信息</a></li>
            <li><a href="javascript:;">公司简介</a></li>
            <li><a href="javascript:;">我是佩奇</a></li>
            <li><a href="javascript:;">啥是佩奇</a></li>
        </ul>
    </div>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值