目录
一.BOM 简介
- BOM(Browser Object Model):浏览器对象模型,提供 与浏览器窗口进行交互的对象
- JavaScript 语法标准化是 ECMA,DOM 标准化是 W3C
- BOM 是 Netscape 浏览器标准的一部分,没有标准兼容性差
- BOM 包含 DOM
- DOM 把 文档 当做对象来看,BOM 把 浏览器 当做对象来看
- DOM 的顶级对象是 document ,BOM 的顶级对象是 window
- DOM 学习操作页面元素,BOM 学习与浏览器窗口交互的一些对象
- 注意:window下的一个特殊属性 window.name
二. window 对象的常见事件
1.页面(窗口)加载事件
- 有了页面(窗口)加载事件 就可以把 JS 代码写到 页面元素的 上方。
- window.onload :文档内容完全加载完成时 触发事件,包括 图像、脚本文件、CSS 文件等加载完成。
- DOMContentLoaded(IE9以上才支持): DOM 加载完成时 触发事件,不包括 样式表,图片,flash 等加载。
- 如果页面图片多,从访问到 onload 触发需要较长时间, 影响用户体验,此时用 DOMContentLoaded 事件合适。
传统注册事件方式 只能写一次,多个就以最后一个 window.onload 为准;addEventListener 则没有限制
window.onload = function(){} // 只能写一次 window.addEventListener("load",function(){}); // 可以写多次 document.addEventListener('DOMContentLoaded',function(){}) // IE9 以上支持
2.调整窗口大小事件
- window.onresize 调整窗口大小事件:只要 窗口大小 发生像素变化,就会触发事件,用于响应式布局。
- window.innerWidth :当前屏幕的宽度。
<script> window.addEventListener('load', function() { // 注册页面加载事件 var div = document.querySelector('div'); window.addEventListener('resize', function() { // 注册调整窗口大小事件 // window.innerWidth 获取窗口大小 屏幕大小改变 样式改变 if (window.innerWidth <= 800) { div.style.display = 'none'; } else { div.style.display = 'block'; }})}) </script> <div></div>
3.定时器
3.1 setTimeout() 炸弹定时器
- 语法规范: window.setTimeout(调用函数, 延时时间);
- 清除 setTimeout() 定时器:clearTimeout(定时器名字);
- window 在调用的时候可以省略
- 延时时间单位是毫秒 可以省略,如果省略默认的是0
- 调用函数可以 直接写函数,还可以写 函数名
- 页面中可能有很多的定时器,给定时器 加名字
// 将函数写在里面 setTimeout(function() { // windows 可以省略不写 console.log('时间到了'); }, 2000); // 将函数写在外面 内部写函数名 function callback() { console.log('爆炸了'); } var timer1 = setTimeout(callback, 3000); // 记得给定时器写名字 // 清除定时器 <button> 点击停止定时器 </button> var btn = document.querySelector('button'); var timer = setTimeout(function() { console.log('快准备清除定时器'); }, 5000); btn.addEventListener('click', function() { // 给按钮添加点击事件 点击后清除定时器 clearTimeout(timer); // 清除定时器 })
五秒后关闭广告:
<body> <img src="images/ad.jpg" alt="" class="ad"> <script> var ad = document.querySelector('.ad'); // 获取要操作的元素 setTimeout(function() { // 开启定时器 ad.style.display = 'none'; }, 5000); </script> </body>
3.2 setInterval() 闹钟定时器
- 语法规范: window.setInterval(调用函数, 延时时间);
- setTimeout :延时时间到了,就调用回调函数,只调用一次
- setInterval :每隔一个延时时间,就调用回调函数,调用很多次
京东购物倒计时:
- 获取时分秒盒子,用户输入目标时间总毫秒数,开启定时器,每隔一秒调用一次
- 在开启定时器之前,应该先调用一次 countDown,防止第一次刷新页面有空白
- 定时器调用的函数,要获取当前时间,获取当前和目标时间的时间差,将毫秒格式化
<div> <span class="hour">1</span> <span class="minute">2</span> <span class="second">3</span> </div> <script> // 1. 获取元素 (时分秒盒子) var hour = document.querySelector('.hour'); // 小时的黑色盒子 var minute = document.querySelector('.minute'); // 分钟的黑色盒子 var second = document.querySelector('.second'); // 秒数的黑色盒子 var inputTime = +new Date('2020-4-1 18:00:00'); // 用户输入时间总的毫秒数 countDown(); // 先调用一次这个函数,防止第一次刷新页面有空白 // 2. 开启定时器 setInterval(countDown, 1000); // 每隔一秒调用一次定时器 更新显示时间 function countDown() { var nowTime = +new Date(); // 当前时间总的毫秒数 var times = (inputTime - nowTime) / 1000; // times 是剩余时间总的秒数 var h = parseInt(times / 60 / 60 % 24); // 时 h = h < 10 ? '0' + h : h; hour.innerHTML = h; // 把剩余的小时给 小时黑色盒子 var m = parseInt(times / 60 % 60); // 分 m = m < 10 ? '0' + m : m; minute.innerHTML = m; var s = parseInt(times % 60); // 当前的秒 s = s < 10 ? '0' + s : s; second.innerHTML = s; } </script>
发送短信倒计时:
- 设置剩余秒数为全局变量,给按钮添加点击事件,点击后禁用按钮,并开启定时器
- 定时器每秒调用一次,当剩余秒数为零,则清除定时器,恢复按钮文字和禁用状态
- 当前剩余秒数不为零,则每一次调用后 剩余时间都要-1(time--) 并显示出来
手机号码: <input type="number"> <button> 发送 </button> <script> var btn = document.querySelector('button'); var time = 60; // 全局变量,定义剩下的秒数 btn.addEventListener('click', function() { // 注册单击事件 btn.disabled = true; // 点击一次后,禁用按钮 var timer = setInterval(function() { // 判断剩余秒数 if (time == 0) { // 清除定时器 和 复原按钮 clearInterval(timer); btn.disabled = false; btn.innerHTML = '发送'; } else { btn.innerHTML = '还剩下' + time + '秒'; time--; // 每隔一秒调用一次定时器 相应的剩余时间也在减少 } }, 1000); }); </script>
三.location 、navigator、history 对象
1.location 对象
1.1 location 简介
- window 对象 有 location 属性,用于 获取 / 设置 / 解析 窗体URL
- 因为 location 属性返回的是对象,所以也称为 location 对象
- location 对象常见属性:
- location.href = 'http://...'; :获取或设置整个 URL
- location.search:返回参数(网址最后那部分是参数)
- location 对象常见方法:
- location.assign('http://...');:类似 href 跳转页面,记录浏览历史,可以 实现后退功能
- location.replace('http://...');:页面替换,不记录浏览历史,不可以 实现后退功能
- location.reload(true);:重新加载页面(true = 强制刷新)
五秒后跳转至首页:
- 打开页面之后有两种方式跳转到首页:①点击按钮直接跳转 ②定时器自动加载五秒倒计时
<button> 点击 </button> <div></div> <script> var btn = document.querySelector('button'); var div = document.querySelector('div'); btn.addEventListener('click', function() { location.href = 'http://www.index.cn'; // 点击按钮后 立刻跳转 }) var timer = 5; // 打开网页后 自动开启定时器 进入五秒倒计时 再跳转 setInterval(function() { if (timer == 0) { location.href = 'http://www.index.cn'; // 0s后跳转 } else { div.innerHTML = '您将在' + timer + '秒钟之后跳转到首页'; timer--; // 每隔一秒时间都-1 }}, 1000); </script>
1.2 获取 URL 参数
- URL 格式:protocol://host[:port]/path/[?query]#fragment
- Example:http://www.index.cn/index.html?name=tea&age=20#link
- location.search:获取页面参数(可以看网址最后部分)
- 自动获取用户名的欢迎登陆页面:
- 先写提交的表单页面,再写获取表单的页面
- 获取表单的页面,先得到页面参数 location.search,截取参数去掉?,将字符串参数转为数组,将对应数组写入页面
// login.html 负责书写 表单要提交的内容 <form action="index.html"> // 这个表单要提交到 index.html 页面 用户名: <input type="text" name="uname"> // 名字用 uname 存储 <input type="submit" value="登录"> </form> // index.html 负责接收 提交表单 的页面 <div></div> <script> console.log(location.search); // ?uname=andy // 1.先去掉? substr('起始的位置',截取几个字符); var params = location.search.substr(1); // uname=andy // 2. 利用=把字符串分割为数组 split('='); 得到["uname", "ANDY"] var arr = params.split('='); var div = document.querySelector('div'); div.innerHTML = arr[1] + '欢迎您'; // 3.把数据写入div中 </script>
2.navigator 对象
- navigator 对象包含有关浏览器的信息。
- 它有很多属性,最常用的是 userAgent,可以返回由客户机发送服务器的 user-agent 头部的值。
判断用户在哪个终端打开页面,实现跳转至不同响应式布局if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android| Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS |Symbian|Windows Phone)/i))) { window.location.href = ""; //手机 } else { window.location.href = ""; //电脑}
3.history 对象
- window 对象提供了 history 对象,与浏览器 历史记录 进行交互,包含用户(在浏览器窗口中)访问过的 URL
- history 对象 在实际开发中 比较少用,会在一些 OA 办公系统中 见到。
- 前进: history.forward(); = history.go(1);
- 后退: history.back(); = history.go(-1);
四.JS 执行机制 和 this 指向问题
1.JS执行机制、单线程、同步异步
- 单线程:所有任务需要排队,前一个任务结束,才会执行后一个任务。
- 导致的问题: JS 执行的时间过长,会造成页面的渲染不连贯。
- 同步任务:在 主线程上 排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
- 异步任务:不进入主线程、而 进入”任务队列” 的任务,当主线程中的任务运行完了,才会 从”任务队列”取出 异步任务 放入 主线程 执行。
- 一般而言,异步任务有以下三种类:
- 普通事件,如 click、resize 等
- 资源加载,如 load、error 等
- 定时器,包括 setInterval、setTimeout 等
2.this 指向问题
- this 的指向在 函数定义时 确定不了,函数执行时 才能确定,一般情况下,this 最终指向的是 调用它的对象。
- 全局作用域或者普通函数中 this 指向 全局对象window(注意定时器里面的 this 指向 window)
- 方法调用中 谁调用 this 指向谁
- 构造函数中 this 指向 构造函数的实例
<button> 点击 </button> <script> // 1. 全局作用域 或 普通函数中 this 指向全局对象window( 定时器里的this指向window) console.log(this); // 全局作用域 function fn() { // 普通函数 console.log(this); } window.fn(); window.setTimeout(function() { // 定时器 console.log(this); }, 1000); // 2. 方法调用中 谁调用this指向谁 var o = { sayHi: function() { console.log(this); // this指向的是 o 这个对象 }} o.sayHi(); var btn = document.querySelector('button'); btn.addEventListener('click', function() { console.log(this); // 事件处理函数中 this指向的是 btn这个按钮对象 }) // 3. 构造函数中 this指向 构造函数的实例 function Fun() { console.log(this); // this 指向的是 下面 fun 实例对象 } var fun = new Fun(); </script>
五.元素偏移量 offset 系列
1.offset 概述
- offset (偏移量):动态的 得到元素的位置(偏移)、大小等。
- 获得元素距离 带有定位(重要!!) 父元素的位置
- 获得元素 自身大小(宽度高度)
- 返回值 不带单位
2.offset 与 style 区别
- offset 可以得到 任意样式表的 样式值,style 只能得到 行内样式表的 样式值
- offset 返回数值 没有单位,style 返回字符串 有单位
- offsetWidth 包含 padding+border+width,style.width 不包含 padding+border
- offsetWidth 是只读属性,style.width 是可读写属性
- 获取元素大小位置,用 offset 更合适;给元素更改值,用 style 改变
3.获取鼠标在盒子内的坐标
- 在盒子内点击,想要得到鼠标 距离盒子左右(不是距离浏览器页面) 的距离。
- 首先得到 鼠标在页面中的 坐标(e.pageX, e.pageY)
- 其次得到 盒子在页面中 的坐标 ( box.offsetLeft, box.offsetTop)
- 鼠标在页面中的坐标 - 盒子在页面中的坐标 = 鼠标在盒子内的坐标
- 移动鼠标,会改变 鼠标在页面中的 坐标,而 盒子在页面中的 坐标不变,因此 鼠标在盒子内的 坐标改变
弹出框(模态框)拖拽效果:
- 点击弹出按钮,会弹出模态框, 并显示灰色半透明遮挡层,display:block;
- 点击关闭按钮,会关闭模态框,同时关闭灰色半透明遮挡层,display:none;
- 拖拽原理:鼠标 按下mousedown 并且 移动mousemove, 之后鼠标松开mouseup
- 拖拽过程:鼠标按住模态框最上层(id 为 title),移动时获得最新的值,赋给模态框的 left 和 top 值
- 模态框位置 = 鼠标的坐标 - 鼠标在盒子内的坐标,拖拽过程中 鼠标在盒子内坐标不变(即移动松开 写到 按下 里面)
- 鼠标按下,得到 鼠标在盒子内的坐标 = 鼠标页面坐标 - 模态框页面坐标
- 鼠标移动,把 模态框页面坐标 设置为 :鼠标页面坐标 - 鼠标盒子内坐标
- 鼠标松开,停止拖拽,即解除鼠标移动事件
// 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); }) })
5.京东放大镜
- 分为三个模块:鼠标经过小图片, 遮挡层和大图片显示,离开则隐藏;遮挡层跟随鼠标;大图片跟随遮挡层移动
- 遮挡层跟随鼠标:获得鼠标在盒子的坐标,把数值给 遮挡层 作为 left 和 top 值,遮挡层不能超出小图片盒子范围
- 遮挡层需要减去自身高度和宽度的一半,实现鼠标在遮挡层中间的效果
- 遮挡层的最大移动距离 = 小图片盒子宽度 - 遮挡层盒子宽度
- 大盒子的最大移动距离 = 大图片自身宽度 - 大图片盒子宽度
- 遮挡层移动距离:遮挡层最大距离 = 大图片移动距离:大图片最大移动距离,由此得到 大图片移动距离
window.addEventListener('load', function() { // 页面加载完后执行js 否则无法获取元素 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) 鼠标在盒子内的坐标 - 盒子高宽的一半 = mask 最终的 left 和 top 值 // (3) mask 移动的距离 减法相当于遮挡层 mask 左移和上移 var maskX = x - mask.offsetWidth / 2; var maskY = y - mask.offsetHeight / 2; // (4) 如果移动距离 maskX < 0 就停在 0 的位置 // 遮挡层的最大移动距离 = 预览小图片盒子宽度 - 遮挡层盒子宽度 var 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'; // 3. 大图片的移动距离 = 遮挡层移动距离 * 大图片最大移动距离 / 遮挡层的最大移动距离 var bigIMg = document.querySelector('.bigImg'); // 大图 // 大图片最大移动距离 = 大图本身的宽度 - 大图盒子的宽度 var bigMax = bigIMg.offsetWidth - big.offsetWidth; // 大图片的移动距离 X Y var bigX = maskX * bigMax / maskMax; var bigY = maskY * bigMax / maskMax; bigIMg.style.left = -bigX + 'px'; // 遮挡层 和 大图 移动方向是相反的 bigIMg.style.top = -bigY + 'px'; }) })
效果展示:
六.元素可视区 client 系列
1.client 概述
- client(客户端):动态得到元素的 边框大小、元素大小 等。
clientWidth 和 offsetWidth 最大的区别:不包含边框。
2.flexible.js
2.1 立即执行函数
- 主要作用: 创建一个独立的作用域。 避免了命名冲突问题
- (function(){})() :参数可有可无(这个比较好理解),记得结尾加分号
(function(a, b) { console.log(a + b); })(1, 2); // 第二个小括号可以看做是调用函数
- (function(){}()):参数可有可无,记得结尾加分号
(function sum(a, b) { console.log(a + b); var num = 10; // 局部变量 }(2, 3));
2.2 pageshow 事件
- 下面三种情况 刷新页面 都会触发 load 事件:
- a标签的超链接
- F5或者刷新按钮(强制刷新)
- 前进后退按钮
- 问题:火狐浏览器,有“往返缓存”,保存着页面数据 + DOM 、JavaScript 的状态,此时后退 不能 刷新页面
- 解决方案:使用 pageshow 事件(
重新加载页面触发的事件
),这个事件给 window 添加
e.persisted 返回true :如果页面是从缓存取过来的页面,也要重新......
window.addEventListener('pageshow', function(e) { // e.persisted 返回true :如果页面是从缓存取过来的页面,也要重新计算 rem 大小 if (e.persisted) { setRemUnit()} })
2.3 mouseover / mouseout VS mouseenter / mouseleave
- mouseover / mouseout:鼠标经过自身盒子会触发,经过子盒子还会触发
- mouseenter / mouseleave:鼠标经过自身盒子触发
- mouseenter(鼠标经过) mouseleave(鼠标离开):这两个事件不会冒泡,所以不触发
2.4 淘宝 flexible.js 源码分析
(function flexible(window, document) { // 立即执行函数 var docEl = document.documentElement // 获取的 html 的根元素 var dpr = window.devicePixelRatio || 1 // dpr 物理像素比 function setBodyFontSize() { // 设置 body 的字体大小 if (document.body) { // 如果页面中有 body 元素 就设置 body 字体大小 document.body.style.fontSize = (12 * dpr) + 'px' } else { // 如果页面中没有 body 元素,则等页面 DOM 元素加载完毕 再设置 body 字体大小 document.addEventListener('DOMContentLoaded', setBodyFontSize) }} setBodyFontSize(); function setRemUnit() { // 设置 html 元素的文字大小 var rem = docEl.clientWidth / 10 // clientWidth:不包括边框 border docEl.style.fontSize = rem + 'px' } setRemUnit() // 当页面尺寸大小发生变化时,重新设置 rem 大小 window.addEventListener('resize', setRemUnit) // pageshow:重新加载页面触发的事件 window.addEventListener('pageshow', function(e) { // e.persisted 返回true :如果页面是从缓存取过来的页面,也要重新计算 rem 大小 if (e.persisted) { setRemUnit()} }) // 有些移动端的浏览器 不支持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))
七.元素滚动 scroll 系列
1.scroll 概述
- scoll(滚动的):动态得到元素的 元素大小、滚动距离 等。
2.页面被卷去的头部
- 浏览器的高(或宽)度不足以显示整个页面时,会自动出现滚动条,滚动触发 onscroll 事件。
- 页面上因滚动而被隐藏的高度,称为页面被卷去的头部。
仿淘宝固定右侧侧边栏 + 返回顶部:
- 原先侧边栏是绝对定位,当页面滚动到一定位置,侧边栏改为固定定位
- 页面继续滚动,会让 返回顶部 显示出来
- 是页面滚动,所以事件源是 document
- 页面被卷去的头部:可以通过 window.pageYOffset 获得 被卷去的左侧:window.pageXOffset
- 区分:元素被卷去的头部:element.scrollTop , 页面被卷去的头部: window.pageYOffset
//1. 获取元素 var sliderbar = document.querySelector('.slider-bar'); // 侧边栏 var banner = document.querySelector('.banner'); // banner.offestTop 就是被卷去头部的大小 一定要写到滚动的外面 var bannerTop = banner.offsetTop var sliderbarTop = sliderbar.offsetTop - bannerTop; // 侧边栏 固定定位之后 应该变化的数值 var main = document.querySelector('.main'); // 主体部分 var goBack = document.querySelector('.goBack'); // 返回顶部 var mainTop = main.offsetTop; // 2. 页面滚动事件 scroll document.addEventListener('scroll', function() { // 3 .当页面被卷去的头部 >= bannerTop 时 侧边栏改为固定定位 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'; } // 5. 点击返回顶部模块,就让窗口滚动的页面的最上方 goBack.addEventListener('click', function() { // 里面的 x 和 y 不跟单位的 直接写数字即可 // window.scroll(0, 0); animate(window, 0); // 因为是窗口滚动 所以对象是 window })
页面被卷去的头部兼容性解决方案:
- 页面被卷去的头部,有兼容性问题,解决方案如下:
function getScroll() { return { left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft||0, top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0 };} 使用的时候 getScroll().top
3.offset、client、scroll 总结
- offset:获得元素位置 offsetLeft offsetTop
- client:获取元素大小 clientWidth clientHeight
- scroll:获取滚动距离 scrollTop scrollLeft(页面滚动的距离通过 window.pageXOffset 获得)
- 区分:页面滚动的距离通过 window.pageXOffset 获得
八.动画函数封装
1.动画实现原理
- 核心原理:通过定时器 setInterval() 不断移动盒子位置。
- 实现步骤:
- 获得盒子当前位置
- 利用定时器不断重复:让盒子在当前位置加上1个移动距离
- 加一个结束定时器的条件
- 元素需要添加定位,才能使用 element.style.left
- 封装动画函数,给不同元素记录不同定时器原理:
- 声明动画函数:function animate(目标对象, 目标位置) {内部逻辑代码}
- 调用动画函数:animate(目标对象, 目标位置);
- 不断的点击按钮,元素动画速度会越来越快,因为开启了太多的定时器
- 解决方案: 清除以前的定时器,让元素只有一个定时器执行:clearInterval(obj.timer);
// 简单动画函数封装:obj = 目标对象 target = 目标位置 function animate(obj, target) { // 不断的点击按钮,span 的速度会越来越快,因为开启了太多的定时器 // 解决方案: 清除以前的定时器,让元素只有一个定时器执行 clearInterval(obj.timer); var timer = setInterval(function() { if (obj.offsetLeft >= target) { clearInterval(timer); // 停止动画 本质是停止定时器 } obj.style.left = obj.offsetLeft + 1 + 'px'; // 记得定位 才能写 left }, 30); } var btn = document.querySelector('button'); var span = document.querySelector('span'); // 调用函数 btn.addEventListener('click', function() { // 点击按钮后 给 span 调用动画函数 animate(span, 200); })
3.缓动效果原理
- 缓动动画就是让元素运动速度改变,最常见的是让速度慢慢降下来,思路如下:
- 让盒子 每次移动的距离 慢慢变小,速度就会慢慢落下来。
- 核心算法: (目标值 - 现在的位置) / 10 = 每次移动的距离步长(需要取整)
- 停止条件: 盒子位置 = 目标位置 就停止定时器
- 使用动画函数在 多个目标值之间移动 思路如下:
- 让动画函数从 800 移动到 500,当点击按钮时候,判断步长是正值还是负值
- 如果是正值,则步长 往大了取整
- 如果是负值,则步长 向小了取整
4.动函数添加回调函数
- 将函数作为参数传到动画函数里面,当动画函数执行完之后,再执行参数函数,这个过程就叫做回调。
- 回调函数写的位置:定时器结束的位置。
- 比如给动画函数添加回调函数,那么执行完动画之后会执行回调函数里的代码,比如:
animate(span, 800, function() { span.style.backgroundColor = 'red'; // 先移动到 800px 处,再背景变红 });
5.动画函数完整版代码
function animate(obj, target, callback) { // 清除以前的定时器,只保留当前定时器执行 clearInterval(obj.timer); obj.timer = setInterval(function() { // 步长值写到定时器的里面,进行 取整 操作,步长公式:(目标值 - 现在位置) / 10 var step = (target - obj.offsetLeft) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); if (obj.offsetLeft == target) { clearInterval(obj.timer); // 停止动画 本质是停止定时器 callback && callback(); // 回调函数写到定时器里面 } obj.style.left = obj.offsetLeft + step + 'px'; }, 15); }
引用 animate 动画函数:
<script src="animate.js"></script> <div class="sliderbar"> <span> ← </span> <div class="con"> 问题反馈 </div> </div> <script> var sliderbar = document.querySelector('.sliderbar'); var con = document.querySelector('.con'); // 鼠标经过 sliderbar 就会让 con 盒子滑动到左侧 // 鼠标离开 sliderbar 就会让 con 盒子滑动到右侧 sliderbar.addEventListener('mouseenter', function() { animate(con, -160, function() { // 利用回调函数 书写自己想要的效果 sliderbar.children[0].innerHTML = '→'; // 动画执行完毕,就把 ← 改为 → }); }) sliderbar.addEventListener('mouseleave', function() { animate(con, 0, function() { sliderbar.children[0].innerHTML = '←'; }); }) </script>
九.常见网页特效
1.轮播图
- 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。
- 点击右侧按钮一次,图片往左播放一张,以此类推,左侧按钮同理。
- 图片播放的同时,下面小圆圈模块跟随一起变化,点击小圆圈,可以播放相应图片。
- 鼠标不经过轮播图,轮播图自动播放图片,鼠标经过轮播图模块, 自动播放停止。
思路分析:
① js较多,单独建 js 文件夹,建js文件,引入页面② 需要添加 load 事件③ 鼠标经过轮播图模块,左右按钮显示,离开隐藏左右按钮。1.1 动态生成小圆圈② 核心思路:小圆圈的个数要跟图片张数一致③ 所以先得到 ul 里面图片的张数(图片放入 li 里面,所以就是 li 的个数)④ 利用循环动态生成小圆圈(这个小圆圈要放入 ol 里面)⑤ 创建节点 createElement(‘li’)⑥ 插入节点 ol. appendChild(li)⑦ 第一个小圆圈需要添加 current 类1.2 小圆圈的排他思想② 点击当前小圆圈,就添加 current 类③ 其余的小圆圈就移除这个 current 类④ 注意: 刚才生成小圆圈的同时,可以直接绑定点击事件1.3 点击小圆圈滚动图② 此时用到 animate 动画函数, 引入js 文件(因为index.js 依赖 animate.js 所以, animate.js 要写到 index.js 上面)③ 使用动画函数的前提,该元素必须有定位④ 注意是 ul 移动 而不是小 li⑤ 滚动图片的核心算法: 点击某个小圆圈 , 让图片滚动 小圆圈的索引号 x 图片的宽度 做为 ul 移动距离⑥ 此时需要知道小圆圈的索引号,可以在生成小圆圈时,设置自定义属性,点击的时候获取自定义属性即可1.4 点击右侧按钮一次,就让图片滚动一张② 声明变量 num , 点击一次,自增 1 , 让 num x 图片宽度,就是 ul 的滚动距离③ 图片无缝滚动原理④ 把 ul 第一个 li 复制一份,放到 ul 的最后面⑤ 当图片滚动到克隆的最后一张图片时, 让 ul 快速的、不做动画的跳到最左侧: left 为 0⑥ 同时 num 赋值为 0 ,重新开始滚动图片了1.5 克隆第一张图片② 克隆 ul 第一个 li cloneNode() 加 true 深克隆 复制里面的子节点 false 浅克隆③ 添加到 ul 最后面 appendChild1.6 点击右侧按钮, 小圆圈跟随变化② 声明变量 circle ,每次点击自增 1 ,注意,左侧按钮也需要这个变量,因此要声明全局变量③ 图片有 5 张,小圆圈只有 4 个,少一个,必须加一个判断条件④ 如果 circle == 4 就重新复原为 01.7 自动播放功能② 添加定时器③ 自动播放轮播图,实际就类似于点击了右侧按钮④ 此时使用手动调用右侧按钮点击事件 arr ow_r.click()⑤ 鼠标经过 focus 就停止定时器⑥ 鼠标离开 focus 就开启定时器window.addEventListener('load', function() { // 1. 获取元素 var arrow_l = document.querySelector('.arrow-l'); // 左按钮 var arrow_r = document.querySelector('.arrow-r'); // 右按钮 var focus = document.querySelector('.focus'); // 焦点图盒子 var 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() { // 鼠标离开 focus 开始自动播放 相当于手动调用点击事件 arrow_r.click(); // 每 2s 执行一次 右按钮单击事件 }, 2000); }); // 3. 动态生成小圆圈:有几张图片,就生成几个小圆圈 var ul = focus.querySelector('ul'); var ol = focus.querySelector('.circle'); for (var i = 0; i < ul.children.length; i++) { var li = document.createElement('li'); // 创建一个 li li.setAttribute('index', i); // 记录当前小圆圈的索引号 通过自定义属性来做 ol.appendChild(li); // 把 li 插入到 ol 里面 // 4. 小圆圈排他思想 生成小圆圈的同时 直接绑定 点击事件 li.addEventListener('click', function() { // 干掉所有人 把所有 li 清除 current 类名 for (var i = 0; i < ol.children.length; i++) { ol.children[i].className = '';} // 留下我自己 当前 li 设置current 类名 this.className = 'current'; // 5. 点击小圆圈,移动图片 移动的是 ul // ul 的移动距离 = 被点击的小圆圈的索引号 x 图片的宽度 注意向左移动是负值 var index = this.getAttribute('index'); num = index; // 点击某个 li 就把 这个li 的索引号 给 num circle = index; // 点击某个 li 就把 这个li 的索引号 给 circle animate(ul, -index * focusWidth); // 给 ul 做移动动画 }) } ol.children[0].className = 'current'; // 把 ol 的第一个 li 设置类名为 current // 6. 克隆 第一张图片(li)放到 ul 最后面,true 表示克隆包括内容 var first = ul.children[0].cloneNode(true); ul.appendChild(first); var num = 0; // 7. 点击右侧按钮, 图片滚动一张 var circle = 0; // circle 控制小圆圈的播放 var flag = true; // flag 节流阀:防止没有播放完移动动画 就直接进行下一次移动动画 arrow_r.addEventListener('click', function() { if (flag) { flag = false; // 关闭节流阀 // 走到最后复制的一张图片,此时 ul 要快速复原 left 改为 0 if (num == ul.children.length - 1) { ul.style.left = 0; num = 0; } num++; animate(ul, -num * focusWidth, function() { // 用回调函数操控节流阀 flag = true; // 打开节流阀 }); // 8. 点击右侧按钮,小圆圈跟随一起变化 circle++; if (circle == ol.children.length) { // 走到最后克隆的图片 复原 circle = 0; } circleChange(); // 调用函数 } }); // 9. 左侧按钮做法 arrow_l.addEventListener('click', function() { if (flag) { flag = false; if (num == 0) { num = ul.children.length - 1; ul.style.left = -num * focusWidth + 'px'; } num--; animate(ul, -num * focusWidth, function() { flag = true; }); // 点击左侧按钮,小圆圈跟随一起变化 circle--; circle = circle < 0 ? ol.children.length - 1 : circle; circleChange(); } }); function circleChange() { for (var i = 0; i < ol.children.length; i++) { // 清除小圆圈的 current 类名 ol.children[i].className = ''; } ol.children[circle].className = 'current'; // 留下当前小圆圈的 current 类名 } // 10. 自动播放轮播图 var timer = setInterval(function() { arrow_r.click(); // 手动调用点击事件 }, 2000); })
2.节流阀
- 防止 轮播图按钮 连续点击 造成 播放过快。
- 节流阀目的:上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法 连续触发。
- 核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
- 开始设置一个变量 var flag= true;
- If(flag){flag = false; do something} 关闭水龙头
- 利用回调函数动画执行完毕, flag = true 打开水龙头
2.1 返回顶部
- 带有动画的返回顶部,是页面滚动,使用 window.scroll(x,y),里面的 x 和 y 不跟单位的 直接写数字即可
// 点击返回顶部模块,就让窗口滚动的页面的最上方 goBack.addEventListener('click', function() { // 里面的 x 和 y 不跟单位的 直接写数字即可 // window.scroll(0, 0); animate(window, 0); // 因为是窗口滚动 所以对象是 window }
2.2 筋斗云导航栏
- 筋斗云的 起始位置 是 0
- 鼠标经过某个 li,把当前 li 的offsetLeft 作为为目标值(云到鼠标经过的导航栏)
- 鼠标离开某个 li,把目标值设为 0(云回第一个导航栏)
- 点击了某个 li, 把 li 当前的位置 存储起来,作为筋斗云的 起始位置
window.addEventListener('load', function() { var cloud = document.querySelector('.cloud'); // 筋斗云 var c_nav = document.querySelector('.c-nav'); // 导航栏 var lis = c_nav.querySelectorAll('li'); // 导航栏元素 var current = 0; // current 作为筋斗云的起始位置 for (var i = 0; i < lis.length; i++) { // (1) 鼠标经过 把当前 li 的位置做为目标值 lis[i].addEventListener('mouseenter', function() { animate(cloud, this.offsetLeft); }); // (2) 鼠标离开 回到起始的位置 lis[i].addEventListener('mouseleave', function() { animate(cloud, current); }); // (3) 鼠标点击 把当前位置作为目标值 lis[i].addEventListener('click', function() { current = this.offsetLeft; });}})
十. 触屏事件
1.触屏事件(Touch)、触摸事件对象(TouchEvent)
- 触屏(触摸)事件 touch:touch 对象代表一个触摸点(手指 / 触摸笔)
- TouchEvent 包含:touchstart、touchmove、touchend
- touches:正在 触摸屏幕的 所有手指的列表
- targetTouches:正在 触摸当前 DOM 元素的 手指列表,给元素注册触摸事件,所以重点记住 targetTocuhes
- 如果侦听的是一个 DOM 元素,touches = TargetTouches
- targetTouches[0]:正在触摸 DOM 元素的第一个手指的相关信息,比如 手指的坐标 e.targetTouches[0]
- changedTouches:手指状态发生了改变的列表 从无到有 或者 从有到无
- 当手指离开屏幕的时候,就没有了 touches 和 targetTouches 列表,但是会有 changedTouches
3.移动端拖动元素
- touchstart、touchmove、touchend,实现拖动元素
- 拖动元素需要手指的坐标值,使用 targetTouches[0] 里面的 pageX 和 pageY
- 拖动元素三步曲:
- 触摸元素 touchstart: 获取手指初始坐标,同时获得盒子原来的位置
- 移动手指 touchmove: 计算手指的滑动距离,并且改变盒子的 left、top
- 离开手指 touchend:
注意: 手指移动也会 触发滚动屏幕 所以要 阻止默认的屏幕滚动 e.preventDefault();
<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) { // 计算手指的移动距离: 手指移动之后的坐标 - 手指初始的坐标 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'; e.preventDefault(); // 阻止屏幕滚动的默认行为 }); </script>
4.classList 属性
- classList 属性:返回元素的类名,
所有类名都不带点
,
IE10 以上版本支持。- 添加类:element.classList.add(’类名’);在后面追加类名 不会 覆盖以前的类名
- 移除类:element.classList.remove(’类名’);
- 切换类:element.classList.toggle(’类名’);
5.click 延时解决方案
- 移动端 click 事件会有 300ms 的延时,原因是移动端双击 会缩放页面,解决方案有三种:
- 1.禁用缩放
<meta name="viewport" content="user-scalable=no">
- 2.使用 fastclick 插件 解决300ms 延迟(最常用)
<script src="fastclick.js"></script> // 引入 fastclick.js 文件 <body> ... <script> if ('addEventListener' in document) { // 如果文档中存在 事件侦听器 document.addEventListener('DOMContentLoaded', function() { FastClick.attach(document.body); }, false); } var div = document.querySelector('div'); // 写了上述代码之后 就不会有延时点击 div.addEventListener('click', function() { alert(11); }) </script> </body>
- 3.利用 touch 事件自己封装函数 解决300ms 延迟
- 原理:手指离开的时间 - 手指触摸的时间 < 150ms,且没有滑动屏幕,就定义为点击,代码如下:
function tap (obj, callback) { var isMove = false; var startTime = 0; // 记录触摸时候的时间变量 obj.addEventListener('touchstart', function (e) { startTime = Date.now(); // 记录触摸时间}); obj.addEventListener('touchmove', function (e) { isMove = true; // 判断是否有滑动,有滑动算拖拽,不算点击 }); obj.addEventListener('touchend', function (e) { if (!isMove && (Date.now() - startTime) < 150) { // 如果手指触摸和离开时间小于150ms 算点击 callback && callback(); // 执行回调函数 } isMove = false; // 取反 重置 startTime = 0; });} tap(div, function(){ // 执行代码 }); // 调用
十一.插件和框架
- JS 插件:是 js 文件,为了解决某个问题而专门存在,功能单一,并且比较小。
- JS 插件举例:animate.js 动画插件,fastclick 取消延时插件
1.移动端常见插件网站
Swiper : https://www.swiper.com.cn/
superslide: http://www.superslide2.com/
iscroll: https://github.com/cubiq/iscroll
2.移动端视频插件 zy.media.js
- 插件使用方法:
- 确认插件实现的功能,去官网下载插件
- 打开 demo 实例文件,复制结构 html,样式 css,js 插件文件
<link rel="stylesheet" href="zy.media.min.css"> // 引入模板所需要的 css文件 <script src="zy.media.min.js"></script> // 引入所需要的 js插件 <style type="text/css"> // css 是模板里复制的,注意不要改各种名字 ... <body> <div class="playvideo"> // html 是模板里复制的 不要改名字 <div class="zy_media"> <video data-config='{"mediaTitle": "小蝴蝶"}'> <source src="mov.mp4" type="video/mp4"> 您的浏览器不支持HTML5视频 </video> </div> <div id="modelView"> </div> </div> <script> zymedia('video', { autoplay: false }); </script> </body>
3.框架 VS 插件
- 框架: 大而全,一整套解决方案
- 插件: 小而专一,某个功能的解决方案
- 前端常用的框架: Bootstrap、Vue、Angular、React 等,既能开发PC端,也能开发移动端
- 前端常用的移动端插件: swiper、superslide、iscroll 等
MUI 专门用于做手机 APP 的前端框架:http://dev.dcloud.net.cn/mui/
十二.本地存储
1.本地存储特性
- 数据存储在 浏览器 中
- 设置、读取方便、甚至 页面刷新 不丢失数据
- 容量较大,sessionStorage 约 5M、localStorage 约 20M
- 只能存储字符串,可以将对象 JSON.stringify() 编码后存储
2.window.sessionStorage
- 生命周期:关闭浏览器窗口
- 在 同一个窗口(页面)下 数据可以共享
- 以 键值对 的形式存储使用
- 存储数据:sessionStorage.setItem(key, value)
- 获取数据:sessionStorage.getItem(key)
- 删除数据:sessionStorage.removeItem(key)
- 清空数据(所有都清除掉):sessionStorage.clear()
var ipt = document.querySelector('input'); // 输入框 var set = document.querySelector('.set'); // 存储数据按钮 var get = document.querySelector('.get'); // 获取数据按钮 var remove = document.querySelector('.remove'); // 删除数据按钮 var del = document.querySelector('.del'); // 清空数据按钮 set.addEventListener('click', function() { var val = ipt.value; sessionStorage.setItem('uname', val); // 存储 sessionStorage.setItem('pwd', val); }); get.addEventListener('click', function() { console.log(sessionStorage.getItem('uname'));}); // 获取 remove.addEventListener('click', function() { sessionStorage.removeItem('uname');}); // 删除一个 del.addEventListener('click', function() { sessionStorage.clear();}); // 清除所有
3.window.localStorage
- 生命周期:永久生效,除非手动删除 否则关闭页面也会存在
- 多窗口(页面)共享(同一浏览器)
- 以 键值对 的形式存储使用
- 存储数据:localStorage.setItem(key, value)
- 获取数据:localStorage.getItem(key)
- 删除数据:localStorage.removeItem(key)
- 清空数据(所有都清除掉):localStorage.clear()
4.记住用户名
- 如果勾选记住用户名, 下次打开浏览器,自动显示上次登录的用户名,用到本地存储中的 localStorage
- 打开页面,先判断是否有用户名,如果有,就在表单里面显示用户名,并且勾选复选框
- 当复选框发生改变时的 change 事件:如果勾选,就存储,否则就移除
<body> <input type="text" id="username"> <input type="checkbox" name="" id="remember"> 记住用户名 <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> </body>