1. BOM

Browser Object Model,浏览器对象模型,提供与浏览器窗口进行交互的对象,核心对象是 window。

  • BOM 缺乏标准,兼容性较差;DOM(W3C)、JavaScript 语法(ECMA);
  • BOM 比 DOM 更大,它包含了 document、location、navigation、screen、history。

    2. window 对象

  • 它是浏览器的顶级对象,具有双重角色:

    • 既是JS访问浏览器窗口的一个接口;
    • 也是一个全局对象。定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。
  • 注意,window 对象有一个特殊属性 window.name,它是有意义的,所以不要给变量起名为 name。

    3. window 对象的常用事件

    ① 页面加载事件

    **window.onload = function(){ }** 或者 **window.addEventListener('load' , function(){ })**
    【说明】当页面内容完全加载完成后(包括图像、其余的JS、CSS等)才会触发该事件,随后调用处理函数。
    【注意】

  • window.onload 传统注册方式只能写一次,如果有多个,则会以最后一个为准;

  • 使用 window.addEventListener 新标准方式则不会有上述限制;
  • 点击 a 标签的超链接、点击浏览器的前进后退按钮、刷新页面,都能触发 load 事件。

【相似事件】DOM 加载事件 **document.addEventListener('DOMContentLoaded' , function(){ })**
【说明】当 DOM 加载完成后(即标签元素加载完成,不包括图像、CSS等)就会触发该事件,随后调用处理函数。
【注意】

  • 兼容性:IE 9+
  • 事件顺序:先 DOM 加载事件,后页面加载事件
  • 使用场合:当页面图片较多时使用,保证页面的结构和交互效果不受图片加载速度的影响。

    ② 调整窗口大小事件

    **window.onresize = function(){ }** 或者 **window.addEventListener('resize' , function(){ })**
    【说明】当窗口的大小被调整时(像素级别),会触发该事件,随后调用处理函数。
    【注意】

  • 只要窗口的宽度或高度发生像素变化,就会触发该事件;

  • 利用该事件,配合属性 window.innerWidth ( 当前屏幕的宽度,单位px ),可以完成响应式布局。

    4. window 对象的常用方法

    ① 四种定时器方法

    【方法1】 **window.setTimeout( function(){ } [, 延迟的毫秒数] );**
    【说明】该方法用于设置一个定时器,当定时器到期后就会执行回调函数( callback ),window 可省略。
    【注意】

  • 延迟时间的单位是毫秒,单位不需要写出来,如果省略时间,则默认延迟时间为0,即立刻异步执行回调函数;

  • 处理函数可直接在 function 里,也可以写函数名
  • 页面中可以同时存在多个定时器,那么我们可以给它们添加标识符(起名),方法如下:
    • var timer1 = setTimeout(callback, 3000);
    • var timer2 = setTimeout(callback, 6000);

【方法2】 **window.clearTimeout(timeoutID);**
【说明】该方法可取消通过 setTimeout() 来建立的定时器,timeoutID 是定时器的标识符。

  1. var btn = document.querySelector('button');
  2. var timer = setTimeout(callback , 5000);
  3. // 定时器 timer 到期前点击 button 就可以取消,要是到期后再点击就没意义了
  4. btn.addEventListener('click' , function(){
  5. clearTimeout(timer);
  6. })

【方法3】 **window.setInterval( function(){ } [, 间隔的毫秒数] );**
【说明】该方法可重复执行回调函数,每隔一定时间,就会去执行一次。
【注意】同 setTimeout(),window 可省略。

【方法4】 **window.clearInterval(timeoutID);**
【说明】该方法可取消通过 setInterval() 来建立的定时器,timeoutID 是定时器的标识符。

【案例1】简易倒计时器(重点必会)
【代码较长,请查看“倒计时器-简易案例.html”文件,已做好注释】
【案例2】按钮倒计时器
【代码较长,请查看“倒计时器-按钮案例.html”文件,已做好注释】

② this 的指向问题

【说明】一般情况下,this 的最终指向是它的调用者,当然程序员也能手动更改它的指向。
【具体请查看“this指向问题.html”文件,已做好注释】

5. JavaScript 执行机制

① JavaScript 语言的一大特点就是单线程,即JS同一时间只能做一件事。单线程就意味着所有任务都需要排队,前一个任务结束后才能执行后一个任务。这就容易造成页面渲染不连贯,使得页面加载出现不连贯的感觉。
② 为了解决这个问题,JavaScript 提供了异步操作,并提出“同步”和“异步”这两个概念:
【同步】前一个任务结束后再执行后一个任务,即任务执行顺序与排列顺序是一致的,称之为“同步”
【异步】若某个任务需要花费大量时间,那就先不处理这个任务,而执行下一个任务,称之为“异步”
③ 执行机制详解

  • JS 引擎在编译代码时,会创建两个队列:同步任务队列 和 异步任务队列(又称:事件任务队列);
  • 接着,JS 引擎会把代码中所有的任务按照制定好的规则分配到两种队列里;
  • 等 JavaScript 执行线程开始后,按照先进先出的原则,先执行同步任务队列里的所有任务,再执行异步任务队列里的所有任务,这就是 JavaScript 的执行机制。

【任务分配规则】
【说明】异步任务队列,又称事件任务队列。一般来说,异步任务队列会存放以下三种类型的回调函数:

  • DOM 事件的回调函数,比如 click、resize、focus、blur 等;
  • 资源加载事件的回调函数,比如 load、error 等;
  • 定时器方法的回调函数,包括 setTimeout、setInterval 等。

    1. // 有如下代码:
    2. console.log(1);
    3. setTimeout( function(){
    4. console.log(3);
    5. },5000);
    6. console.log(2);
    7. // 被分配到同步任务队列里的任务是:
    8. //【1】console.log(1);
    9. //【2】setTimeout(function , 5000); 注:这里开始计时,但不需要等待5秒,计时开始后即可往下执行任务【3】
    10. //【3】console.log(2);
    11. // 被分配到异步任务队列里的任务是:
    12. //【4】function(){console.log(3);}
    13. // 输出结果:1 2 3
    14. /*
    15. 需要注意的是,当5秒计时结束后,任务【4】才会被分配到异步任务队列里
    16. 但是,只有同步任务队列里所有任务执行完成后,才能执行异步任务队列里的任务
    17. 也就是说,如果同步任务需要处理5秒以上,那么即使5秒倒计时结束了,也无法执行异步任务
    18. */

    【事件循环 ( Event Loop ) 】JavaScript 执行线程会不断的重复获取任务、执行任务、再获取、再执行,于是我们称之为事件循环。

    1. // 有如下代码:
    2. console.log(1);
    3. document.onclick = function(){
    4. console.log('click');
    5. }
    6. console.log(2);
    7. // 被分配到同步任务队列里的任务是:
    8. //【1】console.log(1);
    9. //【2】document.onclick = function;
    10. //【3】console.log(2);
    11. // 被分配到异步任务队列里的任务是:
    12. //【4】function(){console.log('click');}
    13. /*
    14. 需要注意的是,只有用户点击了页面后,任务【4】才会被分配到异步任务队列里,而且该任务可以重复多次出现(用户点击后就会出现)
    15. 所以 JS 执行线程需要不断地监视异步任务队列,只要检测到任务出现,它就会获取并执行之,这就是所谓的事件循环(Event Loop)
    16. */

    【参考资料】https://blog.csdn.net/weixin_43789897/article/details/85210069

    6. location 对象

    ① 概念

  • window 对象给我们提供了一个 location 属性,用于获取或设置窗体的URL,并且可以用于解析URL。

  • 因为这个属性返回的是一个对象,所以我们将这个属性也称为 location 对象。

    ② 统一资源定位符(Uniform Resource Loactor,URL)

    【语法格式】protocol://host[:port]/path/[?query]#fragment
    【用词说明】

  • protocol 通信协议,常见的有 http、ftp、maito 等

  • host 主机(域名),例如 www.bilibili.com
  • port 端口号,可选,省略时使用方案的默认端口,例如 http 协议的默认端口为80
  • path 路径,由多个 ‘ / ‘ 符号隔开的字符串,一般用于表示主机上的一个目录或文件地址
  • query 参数,可选,以键值对的形式呈现,通过 ‘ & ‘ 符号连接多个键值对
  • fragment 片段,即 ‘ # ‘ 后面的内容,常见于链接锚点

    ③ location 对象的常用属性

  • location.href 获取或者设置整个URL(直接输出则是当前页面的URL,重新赋值则能跳转到新页面)

  • location.host 返回主机(域名),例如 www.itheima.com
  • location.port 返回端口号,如果未写返回空字符串
  • location.pathname 返回路径,主机(域名)后面的内容
  • location.search 返回参数,? 后面的内容(包括 ? )
  • location.hash 返回片段,# 后面的内容(包括 #)

【例1】5秒钟之后自动跳转至新页面

  1. <body>
  2. <div></div>
  3. </body>
  4. <script>
  5. var div = document.querySelector('div');
  6. var time = 5;
  7. setInterval(function(){
  8. if (time==0) {
  9. location.href = 'http://www.bilibili.com';
  10. } else {
  11. div.innerHTML = '您将在 ' + time + ' 秒钟之后跳转到首页';
  12. time--;
  13. }
  14. }, 1000);
  15. </script>

【例2】两页面之间的参数传递(参考资料:https://blog.csdn.net/wzwenhuan/article/details/7803510

【login.html】
<body>
  <!-- 默认采用 get 请求方式,数据以 name=value 的键值对方式存放在URL中 -->
  <form action="index.html">    <!-- action 属性指定了处理这些表单数据的页面 -->
    用户名:<input type="text" name="uname">
    <input type="submit" value="登录">   <!-- 没有 name 属性,不会生成键值对 -->
  </form>
</body>
【index.html】
<body>
  <div></div>
</body>
<script>
  console.log(location.search);   // 假设获取到的参数是 '?uname=andy'
  // 1. 先去掉问号 '?' ,利用字符串方法 substr( '起始的位置' [, 截取几个字符] );
  var params = location.search.substr(1);
  console.log(params);   // uname=andy
  // 2. 把字符串分割为数组,利用字符串方法 split('=');
  var arr = params.split('=');
  console.log(arr);   // ["uname", "ANDY"]
  // 3. 把数据写入 div 标签内
  var div = document.querySelector('div');
  div.innerHTML = arr[1] + '欢迎您';
</script>

④ location 对象的常用方法

  • location.assign('URL'); 跟 href 一样,可以进行页面跳转,记录历史,能进行页面后退
  • location.replace('URL'); 替换当前页面,但是不记录历史,所以不能进行页面后退
  • location.reload(); 重新加载页面,相当于刷新按钮或者F5,如果参数为true,进行强制刷新ctrl+F5

    7. navigator 对象

    【说明】

  • 该对象包含有关浏览器的信息,它有很多属性,我们最常用的是 userAgent。

  • 这个属性可以返回由客户端发送至服务器的 user-agent 头部的值。
  • 该属性值包含了客户端的浏览器、浏览器内核以及操作系统的版本号。
  • 我们可以通过获取这个属性的值来判断客户端是 PC 还是智能移动设备,从而让用户访问正确的页面。

    8. history 对象

    ① window 对象提供的 history 对象能与浏览器历史记录进行交互,该对象包含用户(在浏览器窗口中)访问过的URL。
    ② history 对象常用方法

  • history.back(); 后退功能

  • history.forward(); 前进功能
  • history.go(参数); 前进后退功能,参数如果是1,前进1个页面;如果是-1,后退1个页面

PC 端网页特效

1. 元素偏移量 offset 系列

① offset 基础

【概述】offset 即偏移量,使用 offset 系列相关属性可以 [ 动态地 ] 得到该元素的位置(偏移)、大小等。
【注意】

  • 可获得子元素距离父元素的位置,但要求父元素带有定位,返回值不带单位;
  • 可获得元素自身的宽度和高度,返回值不带单位。

    ② offset 常用属性

  • element.offsetLeft 返回元素相对于带有定位的父元素左边框的偏移,如果父级都没有定位,则以 body 为准(返回值不带单位)

  • element.offsetTop 返回元素相对于带有定位的父元素上边框的偏移,如果父级都没有定位,则以 body 为准(返回值不带单位)
  • element.offsetWidth 返回自身包括 padding、边框、内容区的宽度(返回值不带单位)
  • element.offsetHeight 返回自身包括 padding、边框、内容区的高度(返回值不带单位)
  • element.offsetParent 返回带有定位的父级元素节点,如果父级都没有定位,则返回 body

    ③ 各种区别

    【element.parentNode 和 element.offsetParent 的区别】

  • parentNode 返回的是最近一级的父级元素节点,不局限于有没有定位;

  • offsetParent 返回的是带有定位的父级元素节点,不局限于最近一级。

【offset 和 style 的区别】

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

【案例】获取鼠标在盒子内的坐标
【原理】
● 先通过鼠标事件对象 ( e.pageX / e.pageY ) 获取鼠标在页面上的坐标;
● 再通过元素偏移量 ( offsetLeft / offsetTop ) 获取盒子再页面上的偏移;
● 最后把坐标值减去偏移量就能得到鼠标在盒子内的坐标(盒子是坐标系)。

<body>
  <div class="box"></div>
</body>
<script>
  var box = document.querySelector('.box');
  box.addEventListener('mousemove' , function(e){
    var x = e.pageX - this.offsetLeft;
    var y = e.pageY - this.offsetTop;
    this.innerHTML = '鼠标的x坐标是' + x + ' y坐标是' + y;
  })
</script>

【案例】弹出式模态框(可拖拽)
【分析】
● 点击弹出层,模态框和遮罩层就会显示出来 dispaly:block; 点击关闭按钮隐藏模态框和遮罩层 dispaly:none
● 在页面中拖拽元素时,鼠标操作的两个步骤:先按下(down)并移动(move)鼠标,再松开(mouseup)鼠标
● 实现拖拽效果:鼠标移动的过程中,获取其最新的坐标值,经过处理后赋值给模态框的 left 和 top 即可
● 处理方式:利用鼠标在页面上的坐标值减去鼠标在模态框内的坐标值,就能得到模态框在页面上的最新坐标
● 允许鼠标按下触发拖拽的事件源是模态框最上面那一行,id 是 title
【代码较长,请查看“弹出式模态框案例.html”文件,已做好注释】

【案例】商品图片放大镜(仅 JavaScript 代码)
【分析】
● 鼠标经过小图片盒子时,黄色遮罩层和大图片盒子显示;鼠标离开小图片盒子后它们消失;
● 在小图片盒子内,黄色遮罩层随鼠标一起移动,但不得超出小图片盒子的范围;
● 在大图片盒子内,显示的是小图片在黄色遮罩层范围下的放大版;
● 黄色遮罩层移动的同时,大图片盒子内的图片一起移动。

window.addEventListener('load' , function(){
  var preview_img = document.querySelector('.preview_img');   // 小图片盒子
  var mask = document.querySelector('.mask');   // 黄色遮罩层(绝对定位在小图片盒子内部)
  var 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. 鼠标移动的时候,让黄色遮罩层盒子跟着鼠标来走(黄色盒子的坐标就是鼠标在小图片盒子内的坐标)
  preview_img.addEventListener('mousemove', function(e){
    // (1) 先计算出鼠标在小图片盒子内的坐标
    var x = e.pageX - this.offsetLeft;
    var y = e.pageY - this.offsetTop;
    // (2) 让鼠标对齐黄色遮罩层盒子的中间点,即让黄色盒子移动自己宽高的一半的距离
    var maskX = x - mask.offsetWidth / 2;
    var maskY = y - mask.offsetHeight / 2;
    // (3) 限制黄色遮罩层盒子的位置,不让它跑到小图片盒子外,这就需要计算它允许的最大移动距离
    // 遮罩层最小移动距离是0,最大移动距离是 小图片盒子宽度(高度) 减去 遮罩层盒子宽度(高度)
    var maskXMax = preview_img.offsetWidth - mask.offsetWidth;
    var maskYMax = preview_img.offsetHeight - mask.offsetHeight;
    if (maskX <= 0) {
      maskX = 0;
    } else if (maskX >= maskXMax) {
      maskX = maskXMax;
    }
    if (maskY <= 0) {
      maskY = 0;
    } else if (maskY >= maskYMax) {
      maskY = maskYMax;
    }
    // 最终黄色遮罩层的移动距离
    mask.style.left = maskX + 'px';
    mask.style.top = maskY + 'px';

    // 3. 计算大图片移动的距离,因为移动距离比例要一致,所以可知:
    // 大图片的移动距离与其最大移动距离的比值 = 遮罩层的移动距离与其最大移动距离的比值,得出公式:
    // 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离
    var bigIMG = document.querySelector('.bigImg');    // 获取大图片元素节点
    // 大图片最大移动距离
    var bigXMax = bigIMG.offsetWidth - big.offsetWidth;
    var bigYMax = bigIMG.offsetHeight - big.offsetHeight;
    // 大图片的移动距离 X Y
    var bigX = maskX * bigXMax / maskXMax;
    var bigY = maskY * bigYMax / maskYMax;
    // 遮罩层往右走,但大图是往左走的,所以要加 - 号
    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、内容区的高度 ( 不含边框,返回值不带单位 )

【client 和 offset 的区别】

  • client 返回的元素宽高不包含边框宽度,但 offset 返回的元素宽高包含;
  • client 可以单独返回元素的边框宽度,但 offset 不可以。

    3. 元素滚动 scroll 系列

    ① scroll 基础

    【概述】使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。

    ② scroll 常用属性(返回值不带单位)

  • element.scrollTop 返回被卷去的上侧距离(内容区域有滚动条的时候使用)

  • element.scrollLeft 返回被卷去的左侧距离(内容区域有滚动条的时候使用)
  • element.scrollwidth 返回自身实际的内容宽度,包含 padding,不含边框
  • element.scrollHeight 返回自身实际的内容高度,包含 padding,不含边框

【scroll 和 client 的区别】client 返回的元素宽高仅仅是元素可视区域的宽高,不包括滚动区域的宽高

③ scroll 滚动事件

【说明】当元素的滚动条发生变化时(被用户拖动)触发的事件

div.addEventListener('scroll' , function(){
  console.log( div.scrollTop );   // 输出被卷去的内容顶部距离元素顶部的距离
  console.log( div.scrollLeft );   // 输出被卷去的内容左侧距离元素左侧的距离
})

【案例】仿淘宝固定侧边栏
【功能】

  • 原先侧边栏是绝对定位
  • 当页面往下滚动一定距离,侧边栏改为固定定位,固定在网页右侧
  • 页面再滚动一定距离后,侧边栏会出现“返回顶部”的功能按钮
  • 点击“返回顶部”按钮,页面以动画的方式网上滚动至页面顶部(涉及 JavaScript 动画)

【分析】

  • 需要用到页面滚动事件scroll,因为是页面滚动,所以事件源是 document
  • 判断滚动到某个位置,就是判断页面被卷去的那一部分的高度值大小
  • 页面被卷去的头部高度值可通过 window.pageYOffset 获得
  • 注意,element.scrollTop 获取的是元素被卷去的头部高度值,并不是页面,所以没用到 scrollTop

【代码较长,请查看“仿淘宝固定侧边栏案例.html”文件,已做好注释】

【offset、client、scroll 三大系列总结】

  • offset 系列常用于获取元素的位置(offsetLeft、offsetTop)
  • client 系列常用于获取元素的大小(clientWidth、clientHeight)
  • scroll 系列常用于获取元素的内容滚动距离(scrollTop、scrollLeft)
  • 特别注意:获取页面的滚动距离是通过 window.pageXOffset / pageYOffset 来获取的

    4. JavaScript 动画函数封装

    ① 动画实现原理

    【核心原理】通过定时器 setInterval( ) 不断移动盒子的位置
    【步骤分解】

  • 获取盒子当前的位置,利用 element.offsetLeft 即可,但这个属性是只读属性,不可写入;

  • 让盒子在当前位置加上一个移动距离,注意此盒子需要添加定位才能使用 element.style.left;
  • 利用定时器 setInterval() 不断重复这个操作;
  • 最后还需设置一个定时器结束 clearInterval() 的条件。

    var div = document.querySelector('div');
    var timer = setInterval(function(){
    if ( div.offsetLeft == 200 ) {
      clearInterval( timer );
    }
    div.style.left = div.offsetLeft + 1 +'px';
    }, 30);
    

    ② 动画函数简单封装

    【传参】注意动画函数需要传递最少两个参数,动画对象(谁要动起来)和移动距离(什么时候结束动画)

    // 封装动画函数
    function simpleAnimate(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');
    simpleAnimate(div , 400);
    

    【问题出现】如果多个元素都使用这个动画函数,每次都要 var 声明定时器,这会浪费大量内存。
    【解决方法】利用 JavaScript 是一门动态语言的特性,我们可以很方便地给当前对象添加属性。

    // 封装动画函数
    function simpleAnimate(obj , target){
    // 给不同的元素指定了不同的定时器,既不容易混淆,也不需要开辟新空间浪费内存
    obj.timer = setInterval(function(){
      if ( obj.offsetLeft == target ) {
        clearInterval( obj.timer );
      }
      obj.style.left = obj.offsetLeft + 1 +'px';
    }, 30);
    }
    // 调用动画函数
    var div = document.querySelector('div');
    simpleAnimate(div , 400);
    

    【问题又出现】

  • 如果给元素添加一个按钮,点击才执行动画函数。此时若用户不断点击按钮,元素移动速度会明显加快。

  • 因为用户多次点击按钮,会给同一个元素开启多个定时器,它们会同时工作,速度当然会变快。

【解决方法】限制元素的定时器个数为一个即可。

// 封装动画函数
function simpleAnimate(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');
simpleAnimate(div , 400);

③ 缓动动画

【效果】让元素运动速度有所变化,常见的是让速度慢慢降为0。
【原理】

  • 让盒子每次移动的距离慢慢变小,速度就会慢慢降为0;
  • 核心算法:( 目标位置 - 当前位置 ) / 10 作为每次移动的距离,即步长;
  • 停止条件:盒子到达目标位置时就停止计时器;
  • 特别注意:步长值需要取整,不然元素永远无法到达目标位置
    // 封装缓动动画函数
    function simpleAnimate(obj , target){
    clearInterval( obj.timer );
    obj.timer = setInterval(function(){
      // 每次移动前先计算步长
      var step = ( target - obj.offsetLeft ) / 10;
      // 如果步长为正数,向上取整;如果步长为负数,向下取整
      step = step > 0 ? Math.ceil( step ) : Math.floor( step );
      if ( obj.offsetLeft == target ) {
        clearInterval( obj.timer );
      }
      obj.style.left = obj.offsetLeft + step +'px';
    }, 30);
    }
    // 调用缓动动画函数
    var div = document.querySelector('div');
    simpleAnimate(div , 400);
    

    ④ 给动画函数添加回调函数

    【回调函数原理】函数可以作为一个参数,传到另一个函数里面,当那个函数执行完之后再执行传进去的函数,这个过程就叫做回调。
    // 封装缓动动画函数,callback 是回调函数
    function simpleAnimate(obj , target , callback){
    clearInterval( obj.timer );
    obj.timer = setInterval(function(){
      var step = ( target - obj.offsetLeft ) / 10;
      step = step > 0 ? Math.ceil( step ) : Math.floor( step );
      if ( obj.offsetLeft == target ) {
        clearInterval( obj.timer );
        // 因为要等计时器结束后再执行回调函数,所以回调函数的调用写在这里
        // 短路运算原理,先判断参数callback是否为空,不为空才进行下一步执行函数
        callback && callback();
      }
      obj.style.left = obj.offsetLeft + step +'px';
    }, 30);
    }
    // 调用缓动动画函数
    var div = document.querySelector('div');
    simpleAnimate( div , 400 , function(){
    div.style.backgroundColor = 'red';
    alert('动画结束啦!');
    });
    

【案例】网页轮播图(重点掌握)
【功能需求】

  • 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮
  • 点击右侧按钮一次,图片往左播放—张,以此类推,左侧按钮同理
  • 图片播放的同时,下面小圆圈模块跟随一起变化
  • 点击小圆圈,可以播放相应图片
  • 鼠标不经过轮插图,轮播图也会自动播放图片
  • 鼠标经过,轮播图模块,自动捅放停止

【节流阀】防止轮播图按钮连续点击而造成动画播放过快

  • 核心原理:利用回调函数,在里面添加一个变量,用于锁住或解锁缓动动画函数

【代码较长,请查看“网页轮播图.js”文件,已做好注释】

移动端网页特效

① 触屏事件 touch 系列

touch 对象代表一个触摸点,可响应用户手指或触控笔对屏幕的操作

② 常见的触摸屏事件

  • touchstart 手指 [ 触摸 ] 到一个 DOM 元素时触发
  • touchmove 手指在一个 DOM 元素上 [ 滑动 ] 时触发
  • touchend 手指从一个 DOM 元素上 [ 移开 ] 时触发

    ③ 触摸事件对象 TouchEvent

    【概述】

  • TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件。

  • 这类事件用于描述一个或多个触点,使开发者可以检测触点的移动、增加或减少等触点变化。

【常见对象列表】

  • e.touches 正在触摸 [ 屏幕 ] 的所有手指的一个列表
  • e.targetTouches 正在触摸 [ 当前 DOM 元素 ] 上的手指的一个列表(常用)
  • e.changedTouches 状态发生了改变的手指列表(从无到有,从有到无的改变)

【区别】

  • 当所有手指离开屏幕后,touches 和 targetTouches 列表就无法获取任何手指信息了,但 changedTouches 可以获取到,因为这些手指的状态发生了改变。

    ④ 拖动元素

    【概述】

  • 利用 touchstart、touchmove、touchend 可以实现拖动元素;

  • 但是拖动元素需要当前手指的坐标值,我们可以使用 e.targetTouches[0] 里面的 pageX 和 pageY;
  • 移动端拖动的原理:计算出手指的移动距离,那么,盒子原来的位置 + 手指移动的距离 = 盒子最终的位置;
  • 手指移动的距离:手指滑动中的位置减去手指刚开始触摸的位置。

【步骤】

  • 触摸元素 touchstart:获取手指初始坐标,同时获得盒子原来的位置
  • 移动手指 touchmove:计算手指的滑动距离,并且移动盒子
  • 离开手指 touchend:无任何操作
  • 注意:手指移动也会触发滚动屏幕事件,所以这里要阻止默认的屏幕滚动 e.preventDefault();

    <style>
    div {
      position: absolute;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
    </style>
    <body>
    <div></div>
    </body>
    <script>
    var div = document.querySelector('div');
    // 声明全局变量 手指初始坐标
    var startX = 0;
    var startY = 0;
    // 声明全局变量 元素初始坐标
    var x = 0;
    var y = 0;
    div.addEventListener('touchstart' , function(e){
      // 获取手指的初始坐标
      startX = e.targetTouches[0].pageX;
      startY = e.targetTouches[0].pageY;
      // 获取元素的初始坐标
      x = this.offsetLeft;
      y = this.offsetTop;
    });
    div.addEventListener('touchmove' , function(e){
      // 阻止默认的屏幕滚动事件
      e.preventDefault();
      // 计算手指的滑动距离:手指移动之后的坐标减去手指的初始坐标
      var moveX = e.targetTouches[0].pageX - startX;
      var moveY = e.targetTouches[0].pageY - startY;
      // 移动元素:元素最终的坐标 = 元素初始的坐标 + 手指移动的距离
      this.style.left = x + moveX + 'px';
      this.style.top = y + moveY + 'px';
    });
    </script>
    

    ⑤ 移动端轮播图

    【功能需求】

  • 功能基本与 PC 端一致

  • 额外添加手指可拖动轮播图的功能

【功能实现】

  • 移动端可以不用考虑兼容性问题,故图片移动效果可使用 CSS3 的 translateX 来制作
  • 修改元素类名: classList 返回元素所有类名 / classList.remove(‘类名’) 去掉某个类名 / classList.add(‘类名’) 追加一个类名 / classList.toggle(‘类名’) 类名有无的切换

【代码较长,请查看“移动端网页轮播图案例.js”文件,已做好注释】

⑥ 移动端 click 事件延时问题

【问题说明】移动端 click 事件会有300ms的延时,原因是移动端屏幕双击默认会缩放页面。
【解决方案】

  • 禁用缩放:禁用浏览器默认的双击缩放行为即可去掉300ms的点击延迟
    • 代码:<meta name="viewport" content="user-scalable=no">
  • 利用 touch 事件自行封装一个事件解决300ms延迟,原理就是:
    • 第一步:当我们手指触摸屏幕,记录此刻的触摸时间
    • 第二步:当我们手指离开屏幕,用离开的时间减去触摸的时间,得到间隔时间
    • 第三步:如果间隔时间小于150ms,并且没有滑动过屏幕,那么我们就定义为点击
  • 利用 fastclick 插件:GitHub官网地址 https://github.com/ftlabs/fastclick

    ⑦ 开源触摸滑动插件

    【Swiper】https://www.swiper.com.cn
    【SuperSlide】http://www.superslide2.com/
    【iscroll】https://github.com/cubiq/iscroll
    【使用方法】
  1. 确认插件实现的功能;
  2. 去官网查看使用说明;
  3. 下载插件;
  4. 打开demo实例文件,查看需要引入的相关文件,并且引入;
  5. 复制demo实例文件中的结构 html,css 样式以及 js 代码。

    Storage 本地存储

    1. 特性

  • 数据存储在用户浏览器中;
  • 设置、读取方便、甚至页面新都不会丢失数据;
  • 容量较大,sessionStorage 约5M、localStorage 约20M;
  • 只能存储字符串,可以将对象 JSON.stringify() 编码后存储。

    2. window.sessionStorage

    ① 生命周期:关闭浏览器页面就结束(不是关闭浏览器,而是关闭标签页就结束)
    ② 共享范围:在同一页面下数据可共享
    ③ 存储形式:以键值对的形式存储使用
    ④ 使用方法

  • 【存储数据】sessionStorage.setItem(key , value);

  • 【获取数据】sessionStorage.getItem(key);
  • 【删除数据】sessionStorage.removeItem(key);
  • 【清空数据】sessionStorage.clear();

    3. window.localStorage

    ① 生命周期:永久生效,除非手动删除,否则关闭页面也不会消失
    ② 共享范围:同一浏览器的多个页面,数据都可共享
    ③ 存储方式:以键值对的形式存储使用
    ④ 使用方法

  • 【存储数据】localStorage.setItem(key , value);

  • 【获取数据】localStorage.getItem(key);
  • 【删除数据】localStorage.removeItem(key);
  • 【清空数据】localStorage.clear();

【案例】记住用户名

<body>
  <input type="text" id="username">
  <input type="checkbox" id="remember">记住用户名
</body>
<script>
  var username = document.querySelector ('#username');
  var remember = document.querySelector('#remember');
  if ( localStorage.getItem('username') ){
    username.value = localStorage.getItem('username');
    remember.checked = true;
  }
  remember.addEventListener('change' , function(){
    if ( this.checked ) {
      localStorage.setItem('username' , username.value);
    } else {
      localStorage.removeItem('username');
    }
  }
</script>