[TOC]

1. 滚动事件和加载事件

1.1 滚动事件

  • 当页面进行滚动时触发的事件
  • 为什么要学?
    • 很多网页需要检测用户把页面滚动到某个区域后做一些处理, 比如固定导航栏,比如返回顶部
  • 事件名:scroll
  • 监听整个页面滚动:
    • image.png
    • 给 window 或 document 添加 scroll 事件
  • 监听某个元素的内部滚动直接给某个元素加即可

    1.2 加载事件

  • 加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件

  • 为什么要学?
    • 有些时候需要等页面资源全部处理完了做一些事情
    • 老代码喜欢把 script 写在 head 中,这时候直接找 dom 元素找不到
  • 事件名:load
  • 监听页面所有资源加载完毕:
    • 给 window 添加 load 事件
    • image.png
  • 注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件

  • 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载
  • 事件名:DOMContentLoaded
  • 监听页面DOM加载完毕:
    • 给 document 添加 DOMContentLoaded 事件
    • image.png

      2. 元素大小和位置

      2.1 scroll 家族

      2.1.1 元素 scroll 系列属性

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

使用场景:
我们想要页面滚动一段距离,比如100px,就让某些元素显示隐藏,那我们怎么知道,页面滚动了100像素呢?
就可以使用scroll 来检测页面滚动的距离
QQ截图20210113092212.png
QQ截图20210113092321.png

  • 获取宽高:
    • 获取元素的内容总宽高(不包含滚动条)返回值不带单位
    • scrollWidth 和 scrollHeight
  • 获取位置:
    • 获取元素内容往左、往上滚出去看不到的距离
    • scrollLeft 和 scrollTop
    • 这两个属性是可以修改的
    • image.png
  • 开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素。检测页面滚动的头部距离(被卷去的头部)用 document.documentElement.scrollTop
    • image.png
    • image.png

      2.1.2 页面被卷去的头部

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

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

1.原先侧边栏是绝对定位
2. 当页面滚动到一定位置,侧边栏改为固定定位
3. 页面继续滚动,会让 返回顶部显示出来
右侧侧边栏.gif
案例分析

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

    ```html
    <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. 获取元素
      var sliderbar = document.querySelector('.slider-bar');
      var banner = document.querySelector('.banner');
      // banner.offestTop 就是被卷去头部的大小 一定要写到滚动的外面
      var bannerTop = banner.offsetTop
      // 当我们侧边栏固定定位之后应该变化的数值
      var sliderbarTop = sliderbar.offsetTop - bannerTop;
      // 获取main 主体元素
      var main = document.querySelector('.main');
      var goBack = document.querySelector('.goBack');
      var mainTop = main.offsetTop;
      // 2. 页面滚动事件 scroll
      document.addEventListener('scroll', function() {
        // console.log(11);
        // window.pageYOffset 页面被卷去的头部
        // console.log(window.pageYOffset);
        // 3 .当我们页面被卷去的头部大于等于了 172 此时 侧边栏就要改为固定定位
        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>
    

    2.1.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
    

    新案例:页面滚动显示返回顶部按钮

    需求:当页面滚动500像素,就显示返回顶部按钮,否则隐藏, 同时点击按钮,则返回顶部
    分析:
    ①:用到页面滚动事件
    ②:检测页面滚动大于等于500像素,则显示按钮
    ③:点击按钮,则让页面的 scrollTop 重置为 0

    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
    
      .content {
        width: 1000px;
        height: 3000px;
        background-color: pink;
        margin: 0 auto;
      }
    
      .backtop {
        display: none;
        width: 50px;
        left: 50%;
        margin: 0 0 0 505px;
        position: fixed;
        bottom: 60px;
        z-index: 100;
      }
    
      .backtop a {
        height: 50px;
        width: 50px;
        background: url(./images/bg2.png) 0 -600px no-repeat;
        opacity: 0.35;
        overflow: hidden;
        display: block;
        text-indent: -999em;
        cursor: pointer;
      }
    </style>
    
    <div class="content"></div>
    <div class="backtop">
      <img src="./images/close2.png" alt="">
      <a href="javascript:;"></a>
    </div>
    <script>
      // 1. 获取元素
      let backtop = document.querySelector('.backtop')
      // 一. 页面滚动事件
      window.addEventListener('scroll', function () {
        // 2. 页面检测滚动的距离
        // console.log(document.documentElement.scrollTop)
        let num = document.documentElement.scrollTop
        // 3. 进行判断显示和隐藏
        if (num >= 500) {
          //显示那个元素
          backtop.style.display = 'block'
        } else {
          // 否则隐藏元素
          backtop.style.display = 'none'
    
        }
      })
    
      // 二、点击链接返回顶部 backtop.children[1]
      backtop.children[1].addEventListener('click', function () {
        // 返回顶部
        // scrollTop 可读写
        document.documentElement.scrollTop = 0
      })
    </script>
    

    2.2 offset 家族

    2.2.1 offset 概述

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

    • 获取宽高:
      • 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
      • offsetWidth 和 offsetHeight
    • 获取位置:
      • 获取元素距离自己定位父级元素的左、上距离,如果都没有以文档左上角为准
      • offsetLeft 和 offsetTop 注意是只读属性
      • 注意: 返回的数值都不带单位

    image.png
    offset 系列常用属性:
    QQ截图20210112181936.png
    使用场景:
    前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。简单说,就是通过js的方式,得到元素在页面中的位置。这样我们可以做,页面滚动到这个位置,就可以返回顶部的小盒子显示…
    image.png

    2.2.2 offset 与 style 区别

    offset:

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

    style

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

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

    <style>
      .box {
        width: 300px;
        height: 300px;
        background-color: pink;
        margin: 200px;
      }
    </style>
    
    <div class="box"></div>
    <script>
      // 我们在盒子内点击, 想要得到鼠标距离盒子左右的距离。
      // 首先得到鼠标在页面中的坐标( e.pageX, e.pageY)
      // 其次得到盒子在页面中的距离(box.offsetLeft, box.offsetTop)
      // 用鼠标距离页面的坐标减去盒子在页面中的距离, 得到 鼠标在盒子内的坐标
      var box = document.querySelector('.box');
      box.addEventListener('mousemove', function(e) {
        // console.log(e.pageX);
        // console.log(e.pageY);
        // console.log(box.offsetLeft);
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        this.innerHTML = 'x坐标是' + x + ' y坐标是' + y;
      })
    </script>
    

    案例:模态框拖拽

    弹出框,我们也称为模态框。

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

    模态框拖拽.gif

    案例分析

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

      ```html
      <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. 获取元素
        var login = document.querySelector('.login');
        var mask = document.querySelector('.login-bg');
        var link = document.querySelector('#link');
        var closeBtn = document.querySelector('#closeBtn');
        var 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) {
          var x = e.pageX - login.offsetLeft;
          var 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>
      


      案例:仿京东放大镜

      案例分析:

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

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

      6. 就是显示与隐藏

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

      8. 把鼠标坐标给遮挡层不合适。因为遮挡层坐标以父盒子为准。
      9. 首先是获得鼠标在盒子的坐标。
      10. 之后把数值给遮挡层做为left 和top值。
      11. 此时用到鼠标移动事件,但是还是在小图片盒子内移动。
      12. 发现,遮挡层位置不对,需要再减去盒子自身高度和宽度的一半。
      13. 遮挡层不能超出小图片盒子范围。
      14. 如果小于零,就把坐标设置为0
      15. 如果大于遮挡层最大的移动距离,就把坐标设置为最大的移动距离
      16. 遮挡层的最大移动距离: 小图片盒子宽度 减去 遮挡层盒子宽度

      QQ截图20210117233426.png
      QQ截图20210117233459.png

      新案例:仿京东固定导航栏案例

      需求:当页面滚动到秒杀模块,导航栏自动滑入,否则滑出
      分析:
      ①:用到页面滚动事件
      ②:检测页面滚动大于等于 秒杀模块 的位置 则滑入,否则滑出
      仿京东固定头部.gif

      <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      
      .content {
        overflow: hidden;
        width: 1000px;
        height: 3000px;
        background-color: pink;
        margin: 0 auto;
      }
      
      .backtop {
        display: none;
        width: 50px;
        left: 50%;
        margin: 0 0 0 505px;
        position: fixed;
        bottom: 60px;
        z-index: 100;
      }
      
      .backtop a {
        height: 50px;
        width: 50px;
        background: url(./images/bg2.png) 0 -600px no-repeat;
        opacity: 0.35;
        overflow: hidden;
        display: block;
        text-indent: -999em;
        cursor: pointer;
      }
      
      .header {
        position: fixed;
        top: -80px;
        left: 0;
        width: 100%;
        height: 80px;
        background-color: purple;
        text-align: center;
        color: #fff;
        line-height: 80px;
        font-size: 30px;
        transition: all .3s;
      }
      
      .sk {
        width: 300px;
        height: 300px;
        background-color: skyblue;
        margin-top: 600px;
      }
      </style>
      
      <div class="header">我是顶部导航栏</div>
      <div class="content">
        <div class="sk">秒杀模块</div>
      </div>
      <div class="backtop">
        <img src="./images/close2.png" alt="">
        <a href="javascript:;"></a>
      </div>
      <script>
        let sk = document.querySelector('.sk')
        let header = document.querySelector('.header')
        // 1. 页面滚动事件
        window.addEventListener('scroll', function () {
          // console.log(11)
          // 要检测滚动的距离
          // console.log(document.documentElement.scrollTop)
          // console.log(sk.offsetTop)
          // 2. 要检测滚动的距离 >= 秒杀模块的offsetTop 则滑入
          if (document.documentElement.scrollTop >= sk.offsetTop) {
            // alert('改吃饭了')
            header.style.top = '0'
          } else {
            header.style.top = '-80px'
          }
        })
      </script>
      

      新案例:电梯导航案例

      需求:点击可以页面调到指定效果
      分析:
      ①:点击当前小导航,当前添加 active,其余移除 active
      ②:得到对应内容的 offsetTop 值
      ③:让页面的 scrollTop 走到对应内容的 offsetTop
      电梯导航.gif

      <style>
        * {
          margin: 0;
          padding: 0;
        }
      
        body {
          height: 3000px;
        }
      
        .aside {
          position: fixed;
          left: 0;
          top: 50%;
          transform: translateY(-50%);
        }
      
        .item {
          height: 40px;
          line-height: 40px;
          text-align: center;
          padding: 0 10px;
          cursor: pointer;
        }
      
        .active {
          background-color: red;
          color: #fff;
        }
      
        .content {
          width: 660px;
          margin: 400px auto;
        }
      
        .neirong {
          height: 300px;
          margin-bottom: 20px;
          color: #fff;
        }
      
        .content1 {
          background-color: red;
        }
      
        .content2 {
          background-color: blue;
        }
      
        .content3 {
          background-color: orange;
        }
      
        .content4 {
          background-color: yellowgreen;
        }
      </style>
      
      <div class="aside">
        <div class="item active">男装/女装</div>
        <div class="item">儿童服装/游乐园</div>
        <div class="item">电子产品</div>
        <div class="item">电影/美食</div>
      </div>
      
      <div class="content">
        <div class="neirong content1">男装/女装</div>
        <div class="neirong content2">儿童服装/游乐园</div>
        <div class="neirong content3">电子产品</div>
        <div class="neirong content4">电影/美食</div>
      </div>
      
      <script>
        // 1. 获元取素  
        let items = document.querySelectorAll('.item')
        // 内容的盒子获取
        let neirongs = document.querySelectorAll('.neirong')
        // 2. 左侧aside 模块 点击谁,谁高亮
        for (let i = 0; i < items.length; i++) {
          items[i].addEventListener('click', function () {
            // 找到上一个active 移除类
            document.querySelector('.aside .active').classList.remove('active')
            // 点击谁谁添加类
            this.classList.add('active')
            // 3. 右侧内容跟随走动  让页面滚动到对应的offsetTop值位置
            // console.log(neirongs[i].offsetTop) 不用给单位
            document.documentElement.scrollTop = neirongs[i].offsetTop
          })
        }
      </script>
      

      2.3 client 家族

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

      • 获取宽高:
        • 获取元素的可见部分宽高(不包含边框,滚动条等)
        • clientWidth 和 clientHeight
      • 获取位置:
        • 获取左边框和上边框宽度
        • clientLeft 和 clientTop 注意是只读属性

      QQ截图20210112234233.png
      QQ截图20210112234300.png

      • 会在窗口尺寸改变的时候触发事件:

        • resize
        • image.png
          <script>
          // 当窗口变化的时候触发的事件
          window.addEventListener('resize', function () {
          // console.log(111)
          let w = document.documentElement.clientWidth
          // console.log(document.documentElement.clientWidth)
          if (w >= 1000) {
          document.body.style.backgroundColor = 'pink'
          } else if (w > 540) {
          document.body.style.backgroundColor = 'hotpink'
          } else {
          document.body.style.backgroundColor = 'deeppink'
          }
          })
          </script>
          <script src="../素材/flexible.js"></script>
          
      • 检测屏幕宽度:

        • image.png

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

      立即执行函数 (function() {})() 或者 (function(){}())
      主要作用: 创建一个独立的作用域。 避免了命名冲突问题

      <script>
        /h/ 1.立即执行函数: 不需要调用,立马能够自己执行的函数
        function fn() {
          console.log(1);
        }
        fn();
        // 2. 写法 也可以传递参数进来
        // 1.(function() {})()    或者  2. (function(){}());
        (function(a, b) {
          console.log(a + b);
          var num = 10;
        })(1, 2); // 第二个小括号可以看做是调用函数
        (function sum(a, b) {
          console.log(a + b);
          var num = 10; // 局部变量
        }(2, 3));
        // 3. 立即执行函数最大的作用就是 独立创建了一个作用域, 里面所有的变量都是局部变量 不会有命名冲突的情况
      </script>
      

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

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

      但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。
      所以此时后退按钮不能刷新页面。
      此时可以使用 pageshow事件来触发。,这个事件在页面显示时触发,无论页面是否来自缓存。在重新加载页面中,pageshow会在load事件触发后触发;根据事件对象中的persisted来判断是否是缓存中的页面触发的pageshow事件,注意这个事件给window添加。

      <script src="flexible.js"></script>
      
      <script>
        console.log(window.devicePixelRatio);
        window.addEventListener('pageshow', function() {
          alert(11);
        })
      </script>
      <a href="http://www.itcast.cn">链接</a>
      
      (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))
      

      2.4 三大系列总结

      QQ截图20210113100243.png
      他们主要用法:

      1. offset系列 经常用于获得元素位置 offsetLeft offsetTop ,盒子元素的大小=含padding+边框+内容
      2. client 经常用于获取元素大小 clientWidth clientHeight ,不含边框,不含滚动条,含padding+内容
      3. scroll 经常用于获取滚动距离 scrollTop scrollLeft ,不含边框,不含滚动条,含padding+内容
      4. 注意页面滚动的距离通过 window.pageXOffset(只读) 或者 document.documentElement.scrollTop(可读写) 获得

      image.png

      scrollTop, offsetTop, pageYOffset, scrollY 的区别

      scrollTop(可读写)

      DOM对象的scrollTop用于获取或者设置一个元素里滚动的距离(垂直)。例如:document.documentElement.scrollTop可以获取当前页面的滚动高度,也可以获取某个DOM元素的滚动距离,例如:document.querySelector(‘.content’).scrollTop,前提是.content元素存在,并且可以滚动。
      另外,scrollTop还可以把滚动条移到指定位置,例如:

      // 回到顶部
      document.documentElement.scrollTop = 0;
      

      offsetTop

      DOM对象的offsetTop与scrollTop完全不同,它是上边框相对于父元素上边框的距离,一般是固定的,不随滚动变化。

      pageYOffset(只读) 和 scrollY(可读写)

      这两个都能返回视窗滚动过的距离,相对来说,pageYOffset兼容性更好,一般我们只用 pageYOffset就行。但两者都不兼容IE9以下,另外 scrollY 可以赋值,让视窗滚动到指定位置。pageYOffser和scrollY都与scrollTop不同,pageYOffset和scrollY都只存在window对象里。
      看下面等式:

      document.documentElement.scrollTop === window.pageYOffset === scrollY
      

      5. mouseenter 和mouseover的区别

      mouseenter 鼠标事件

      • 当鼠标移动到元素上时就会触发 mouseenter 事件
      • 类似 mouseover,它们两者之间的差别是
      • mouseover 鼠标经过自身盒子会触发,经过子盒子还会触发。 mouseenter 只会经过自身盒子触发
      • 之所以这样,就是因为mouseenter不会冒泡
      • 跟mouseenter搭配 鼠标离开 mouseleave 同样不会冒泡
        <div class="father">
        <div class="son"></div>
        </div>
        <script>
        var father = document.querySelector('.father');
        var son = document.querySelector('.son');
        father.addEventListener('mouseenter', function() {
          console.log(11);
        })
        </script>
        

        6. 动画函数封装

        6.1 动画实现原理

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

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

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

      6.2 动画函数简单封装

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

      <style>
        div {
          position: absolute;
          left: 0;
          width: 100px;
          height: 100px;
          background-color: pink;
        }
      
        span {
          position: absolute;
          left: 0;
          top: 200px;
          display: block;
          width: 150px;
          height: 150px;
          background-color: purple;
        }
      </style>
      
      <div></div>
      <span>夏雨荷</span>
      <script>
        // 简单动画函数封装obj目标对象 target 目标位置
        function animate(obj, target) {
          var timer = setInterval(function() {
            if (obj.offsetLeft >= target) {
              // 停止动画 本质是停止定时器
              clearInterval(timer);
            }
            obj.style.left = obj.offsetLeft + 1 + 'px';
      
          }, 30);
        }
      
        var div = document.querySelector('div');
        var span = document.querySelector('span');
        // 调用函数
        animate(div, 300);
        animate(span, 200);
      </script>
      

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

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

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

      <button>点击夏雨荷才走</button>
      <div></div>
      <span>夏雨荷</span>
      <script>
        // var obj = {};
        // obj.name = 'andy';
        // 简单动画函数封装obj目标对象 target 目标位置
        // 给不同的元素指定了不同的定时器
        function animate(obj, target) {
          // 当我们不断的点击按钮,这个元素的速度会越来越快,因为开启了太多的定时器
          // 解决方案就是 让我们元素只有一个定时器执行
          // 先清除以前的定时器,只保留当前的一个定时器执行
          clearInterval(obj.timer);
          obj.timer = setInterval(function() {
            if (obj.offsetLeft >= target) {
              // 停止动画 本质是停止定时器
              clearInterval(obj.timer);
            }
            obj.style.left = obj.offsetLeft + 1 + 'px';
      
          }, 30);
        }
      
        var div = document.querySelector('div');
        var span = document.querySelector('span');
        var btn = document.querySelector('button');
        // 调用函数
        animate(div, 300);
        btn.addEventListener('click', function() {
          animate(span, 200);
        })
      </script>
      

      6.4 缓动效果原理

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

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

        <button>点击夏雨荷才走</button>
        <span>夏雨荷</span>
        <script>
        // 缓动动画函数封装obj目标对象 target 目标位置
        // 思路:
        // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
        // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
        // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
        function animate(obj, target) {
         // 先清除以前的定时器,只保留当前的一个定时器执行
         clearInterval(obj.timer);
         obj.timer = setInterval(function() {
           // 步长值写到定时器的里面
           var step = (target - obj.offsetLeft) / 10;
           if (obj.offsetLeft == target) {
             // 停止动画 本质是停止定时器
             clearInterval(obj.timer);
           }
           // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
           obj.style.left = obj.offsetLeft + step + 'px';
        
         }, 15);
        }
        var span = document.querySelector('span');
        var btn = document.querySelector('button');
        
        btn.addEventListener('click', function() {
         // 调用函数
         animate(span, 500);
        })
        // 匀速动画 就是 盒子是当前的位置 +  固定的值 10 
        // 缓动动画就是  盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
        </script>
        

        6.5 动画函数多个目标值之间移动

        可以让动画函数从 800 移动到 500。
        当我们点击按钮时候,判断步长是正值还是负值

      5. 如果是正值,则步长 往大了取整

      6. 如果是负值,则步长 向小了取整

        <button class="btn500">点击夏雨荷到500</button>
        <button class="btn800">点击夏雨荷到800</button>
        <span>夏雨荷</span>
        <script>
        // 缓动动画函数封装obj目标对象 target 目标位置
        // 思路:
        // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
        // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
        // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
        function animate(obj, target) {
         // 先清除以前的定时器,只保留当前的一个定时器执行
         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);
           }
           // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
           obj.style.left = obj.offsetLeft + step + 'px';
         }, 15);
        }
        var span = document.querySelector('span');
        var btn500 = document.querySelector('.btn500');
        var btn800 = document.querySelector('.btn800');
        
        btn500.addEventListener('click', function() {
         // 调用函数
         animate(span, 500);
        })
        btn800.addEventListener('click', function() {
         // 调用函数
         animate(span, 800);
        })
        // 匀速动画 就是 盒子是当前的位置 +  固定的值 10 
        // 缓动动画就是  盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
        </script>
        

        6.6 动画函数添加回调函数

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

        <button class="btn500">点击夏雨荷到500</button>
        <button class="btn800">点击夏雨荷到800</button>
        <span>夏雨荷</span>
        <script>
        // 缓动动画函数封装obj目标对象 target 目标位置
        // 思路:
        // 1. 让盒子每次移动的距离慢慢变小, 速度就会慢慢落下来。
        // 2. 核心算法:(目标值 - 现在的位置) / 10 做为每次移动的距离 步长
        // 3. 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
        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();
             }
           }
           // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
           obj.style.left = obj.offsetLeft + step + 'px';
        
         }, 15);
        }
        var span = document.querySelector('span');
        var btn500 = document.querySelector('.btn500');
        var btn800 = document.querySelector('.btn800');
        
        btn500.addEventListener('click', function() {
         // 调用函数
         animate(span, 500);
        })
        btn800.addEventListener('click', function() {
         // 调用函数
         animate(span, 800, function() {
           // alert('你好吗');
           span.style.backgroundColor = 'red';
         });
        })
        // 匀速动画 就是 盒子是当前的位置 +  固定的值 10 
        // 缓动动画就是  盒子当前的位置 + 变化的值(目标值 - 现在的位置) / 10)
        </script>
        

        6.7 动画函数封装到单独JS文件里面

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

      7. 单独新建一个JS文件。

      8. HTML文件引入 JS 文件。
      // animate.js
      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';
          }, 15);
      }
      
      // index.html
      <style>
        .sliderbar {
          position: fixed;
          right: 50PX;
          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>
      
      
      
      <div class="sliderbar">
        <span>←</span>
        <div class="con">问题反馈</div>
      </div>
      
      <script>
        // 1. 获取元素
        var sliderbar = document.querySelector('.sliderbar');
        var con = document.querySelector('.con');
        // 当我们鼠标经过 sliderbar 就会让 con这个盒子滑动到左侧
        // 当我们鼠标离开 sliderbar 就会让 con这个盒子滑动到右侧
        sliderbar.addEventListener('mouseenter', function() {
          // animate(obj, target, callback);
          animate(con, -160, function() {
            // 当我们动画执行完毕,就把 ← 改为 →
            sliderbar.children[0].innerHTML = '→';
          });
      
        })
        sliderbar.addEventListener('mouseleave', function() {
          // animate(obj, target, callback);
          animate(con, 0, function() {
            sliderbar.children[0].innerHTML = '←';
          });
        })
      </script>
      

      7. 常见网页特效案例

      新案例:手风琴效果

      手风琴.gif

      <style>
        ul {
          list-style: none;
        }
      
        * {
          margin: 0;
          padding: 0;
        }
      
        div {
          width: 1200px;
          height: 400px;
          margin: 50px auto;
          border: 1px solid red;
          overflow: hidden;
        }
      
        div li {
          width: 240px;
          height: 400px;
          float: left;
          transition: all 500ms;
        }
      
        div ul {
          width: 1200px;
        }
      </style>
      
      <body>
        <div id="box">
          <ul>
            <li>
              <a href="#">
                <img src="./images/1.jpg" alt="">
              </a>
            </li>
            <li>
              <a href="#">
                <img src="./images/2.jpg" alt="">
              </a>
            </li>
            <li>
              <a href="#">
                <img src="./images/3.jpg" alt="">
              </a>
            </li>
            <li>
              <a href="#">
                <img src="./images/4.jpg" alt="">
              </a>
            </li>
            <li>
              <a href="#">
                <img src="./images/5.jpg" alt="">
              </a>
            </li>
          </ul>
        </div>
      </body>
      <script>
        // 1. li 默认有个宽度是 240像素  
        // 2. 当我们鼠标经过, 当前的小li 宽度变大 800px 其余的小li 变为 100px 
        // 3. 鼠标离开事件, 所有的小li 都要复原 宽度为 240px
        // (1) 获取元素
        let lis = document.querySelectorAll('li')
        // (2) 绑定鼠标经过和离开事件
        for (let i = 0; i < lis.length; i++) {
          // (3) 鼠标经过
          lis[i].addEventListener('mouseenter', function () {
            // 排他思想  干掉所有人 100px,复活我自己 800px
            for (let j = 0; j < lis.length; j++) {
              lis[j].style.width = '100px'
            }
            this.style.width = '800px'
          })
          // (4) 鼠标离开
          lis[i].addEventListener('mouseleave', function () {
            for (let j = 0; j < lis.length; j++) {
              lis[j].style.width = '240px'
            }
          })
        }
      </script>
      

      案例:网页轮播图

      轮播图也称为焦点图,是网页中比较常见的网页特效。
      image.png
      分析:

      • 需求①:小图标鼠标经过事件
        • 鼠标经过小图片,当前高亮,其余兄弟变淡,添加类
      • 需求② :大图片跟随变化
        • 对应的大图片跟着显示,如果想要过渡效果,可以使用opacity效果,可以利用CSS淡入淡出的效果,还是添加类
      • 需求③:右侧按钮播放效果
        • 点击右侧按钮,可以自动播放下一张图片 需要一个变化量 index 不断自增
        • 然后播放下一张图片
        • 如果到了最后一张,必须要还原为第1张图片
        • 教你一招: 索引号 = 索引号 % 数组长度 (放到播放前面)
      • 需求④:解决一个BUG
        • 点击右侧按钮可以实现播放下一张,但是鼠标经过前面的,播放就会乱序
        • 解决方案: 让变化量(索引号) 重新赋值为 当前鼠标经过的索引号
      • 需求⑤:左侧按钮播放效果
        • 点击左侧按钮,可以自动播放上一张图片
        • 需要一个变化量 index 不断自减
        • 然后播放上一张图片
        • 如果到了第一张,必须要从最后一张播放
        • 教你一招: 索引号 = (数组长度 + 索引号) % 数组长度
      • 需求⑥:
        • 因为左侧按钮和右侧按钮里面有大量相同的操作,可以抽取封装一个函数 common
      • 需求⑦:开启定时器
        • 其实定时器自动播放,就相当于点击了右侧按钮,此时只需要, right.click()
      • 需求⑧:

        • 鼠标经过停止定时器 (清除定时器)
        • 鼠标离开开启定时器 (开启定时器) ```css

          ```html
          <div class="main">
          <div class="slides">
            <ul>
              <li class="active"><a href="#"><img src="./assets/b_01.jpg" alt="第1张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_02.jpg" alt="第2张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_03.jpg" alt="第3张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_04.jpg" alt="第4张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_05.jpg" alt="第5张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_06.jpg" alt="第6张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_07.jpg" alt="第7张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_08.jpg" alt="第8张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_09.jpg" alt="第9张图的描述信息"></a></li>
              <li><a href="#"><img src="./assets/b_10.jpg" alt="第9张图的描述信息"></a></li>
            </ul>
          
            <div class="extra">
              <h3>第1张图的描述信息</h3>
              <a class="prev" href="javascript:;"></a>
              <a class="next" href="javascript:;"></a>
            </div>
          </div>
          <div class="indicator">
            <ul>
              <li class="active">
                <img src="assets/s_01.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_02.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_03.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_04.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_05.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_06.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_07.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_08.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_09.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
              <li>
                <img src="assets/s_10.jpg">
                <span class="mask"></span>
                <span class="border"></span>
              </li>
            </ul>
          </div>
          </div>
          

          ```javascript ```

          7.1 节流阀

          防止轮播图按钮连续点击造成播放过快。
          节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
          核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
          开始设置一个变量 var flag = true;
          If(flag) {flag = false; do something} 关闭水龙头
          利用回调函数 动画执行完毕, flag = true 打开水龙头

          案例二:返回顶部
          滚动窗口至文档中的特定位置。
          window.scroll(x, y)
          注意,里面的x和y 不跟单位,直接写数字

          案例分析:

          1. 带有动画的返回顶部
          2. 此时可以继续使用我们封装的动画函数
          3. 只需要把所有的left 相关的值 改为 跟 页面垂直滚动距离相关就可以了
          4. 页面滚动了多少,可以通过 window.pageYOffset 得到
          5. 最后是页面滚动,使用 window.scroll(x,y)

          案例三:筋头云案例
          鼠标经过某个小li, 筋斗云跟这到当前小li位置
          鼠标离开这个小li, 筋斗云复原为原来的位置
          鼠标点击了某个小li, 筋斗云就会留在点击这个小li 的位置

          案例分析:

          1. 利用动画函数做动画效果
          2. 原先筋斗云的起始位置是0
          3. 鼠标经过某个小li, 把当前小li 的 offsetLeft 位置 做为目标值即可
          4. 鼠标离开某个小li, 就把目标值设为 0
          5. 如果点击了某个小li, 就把li当前的位置存储起来,做为筋斗云的起始位置