1. 滚动事件和加载事件
1.1 滚动事件
- 当页面进行滚动时触发的事件
- 为什么要学?
- 很多网页需要检测用户把页面滚动到某个区域后做一些处理, 比如固定导航栏,比如返回顶部
- 事件名:scroll
- 监听整个页面滚动:
- 给 window 或 document 添加 scroll 事件
-
1.2 加载事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件
- 为什么要学?
- 有些时候需要等页面资源全部处理完了做一些事情
- 老代码喜欢把 script 写在 head 中,这时候直接找 dom 元素找不到
- 事件名:load
- 监听页面所有资源加载完毕:
- 给 window 添加 load 事件
- 注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件
- 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载
- 事件名:DOMContentLoaded
- 监听页面DOM加载完毕:
使用场景:
我们想要页面滚动一段距离,比如100px,就让某些元素显示隐藏,那我们怎么知道,页面滚动了100像素呢?
就可以使用scroll 来检测页面滚动的距离
- 获取宽高:
- 获取元素的内容总宽高(不包含滚动条)返回值不带单位
- scrollWidth 和 scrollHeight
- 获取位置:
- 获取元素内容往左、往上滚出去看不到的距离
- scrollLeft 和 scrollTop
- 这两个属性是可以修改的
- 开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素。检测页面滚动的头部距离(被卷去的头部)用 document.documentElement.scrollTop
案例:仿淘宝固定右侧侧边栏
1.原先侧边栏是绝对定位
2. 当页面滚动到一定位置,侧边栏改为固定定位
3. 页面继续滚动,会让 返回顶部显示出来
案例分析
- 需要用到页面滚动事件 scroll 因为是页面滚动,所以事件源是 document
- 滚动到某个位置,就是判断页面被卷去的上部值。
- 页面被卷去的头部:可以通过 window.pageYOffset 获得 如果是被卷去的左侧则是 window.pageXOffset
- 注意,元素被卷去的头部是 element.scrollTop , 如果是页面被卷去的头部 则是 window.pageYOffset ,并且window.pageYOffset 是只读的
- 其实这个值 可以通过盒子的 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 注意是只读属性
- 注意: 返回的数值都不带单位
offset 系列常用属性:
使用场景:
前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。简单说,就是通过js的方式,得到元素在页面中的位置。这样我们可以做,页面滚动到这个位置,就可以返回顶部的小盒子显示…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>
案例:模态框拖拽
弹出框,我们也称为模态框。
- 点击弹出层, 会弹出模态框, 并且显示灰色半透明的遮挡层。
- 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明遮挡层。
- 鼠标放到模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
- 鼠标松开,可以停止拖动模态框移动。
案例分析
- 点击弹出层, 模态框和遮挡层就会显示出来 display:block;
- 点击关闭按钮,模态框和遮挡层就会隐藏起来 display:none;
- 在页面中拖拽的原理: 鼠标按下并且移动, 之后松开鼠标
- 触发事件是鼠标按下 mousedown, 鼠标移动mousemove 鼠标松开 mouseup
- 拖拽过程: 鼠标移动过程中,获得最新的值赋值给模态框的left和top值, 这样模态框可以跟着鼠标走了
- 鼠标按下触发的事件源是 最上面一行,就是 id 为 title
- 鼠标的坐标 减去 鼠标在盒子内的坐标, 才是模态框真正的位置。
- 鼠标按下,我们要得到鼠标在盒子的坐标。
- 鼠标移动,就让模态框的坐标 设置为 : 鼠标坐标 减去盒子坐标即可,注意移动事件写到按下事件里面。
- 鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除
```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>
案例:仿京东放大镜
案例分析:
- 整个案例可以分为三个功能模块
- 鼠标经过小图片盒子, 黄色的遮挡层 和 大图片盒子显示,离开隐藏2个盒子功能
- 黄色的遮挡层跟随鼠标功能。
移动黄色遮挡层,大图片跟随移动功能。
鼠标经过小图片盒子, 黄色的遮挡层 和 大图片盒子显示,离开隐藏2个盒子功能
就是显示与隐藏
黄色的遮挡层跟随鼠标功能。
- 把鼠标坐标给遮挡层不合适。因为遮挡层坐标以父盒子为准。
- 首先是获得鼠标在盒子的坐标。
- 之后把数值给遮挡层做为left 和top值。
- 此时用到鼠标移动事件,但是还是在小图片盒子内移动。
- 发现,遮挡层位置不对,需要再减去盒子自身高度和宽度的一半。
- 遮挡层不能超出小图片盒子范围。
- 如果小于零,就把坐标设置为0
- 如果大于遮挡层最大的移动距离,就把坐标设置为最大的移动距离
- 遮挡层的最大移动距离: 小图片盒子宽度 减去 遮挡层盒子宽度
新案例:仿京东固定导航栏案例
需求:当页面滚动到秒杀模块,导航栏自动滑入,否则滑出
分析:
①:用到页面滚动事件
②:检测页面滚动大于等于 秒杀模块 的位置 则滑入,否则滑出<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<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 注意是只读属性
会在窗口尺寸改变的时候触发事件:
- resize
<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>
检测屏幕宽度:
案例: 淘宝 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 事件。
- a标签的超链接
- F5或者刷新按钮(强制刷新)
- 前进后退按钮
但是 火狐中,有个特点,有个“往返缓存”,这个缓存中不仅保存着页面数据,还保存了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 三大系列总结
他们主要用法:- offset系列 经常用于获得元素位置 offsetLeft offsetTop ,盒子元素的大小=含padding+边框+内容
- client 经常用于获取元素大小 clientWidth clientHeight ,不含边框,不含滚动条,含padding+内容
- scroll 经常用于获取滚动距离 scrollTop scrollLeft ,不含边框,不含滚动条,含padding+内容
- 注意页面滚动的距离通过 window.pageXOffset(只读) 或者 document.documentElement.scrollTop(可读写) 获得
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 缓动效果原理
缓动动画就是让元素运动速度有所变化,最常见的是让速度慢慢停下来
思路:- 让盒子每次移动的距离慢慢变小,速度就会慢慢落下来。
- 核心算法: (目标值 - 现在的位置 ) / 10 做为每次移动的距离 步长
- 停止的条件是: 让当前盒子位置等于目标位置就停止定时器
注意步长值需要取整
<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。
当我们点击按钮时候,判断步长是正值还是负值如果是正值,则步长 往大了取整
如果是负值,则步长 向小了取整
<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文件即可。
单独新建一个JS文件。
- 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. 常见网页特效案例
新案例:手风琴效果
<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>
案例:网页轮播图
轮播图也称为焦点图,是网页中比较常见的网页特效。
分析:- 需求①:小图标鼠标经过事件
- 鼠标经过小图片,当前高亮,其余兄弟变淡,添加类
- 需求② :大图片跟随变化
- 对应的大图片跟着显示,如果想要过渡效果,可以使用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>
7.1 节流阀
防止轮播图按钮连续点击造成播放过快。
节流阀目的:当上一个函数动画内容执行完毕,再去执行下一个函数动画,让事件无法连续触发。
核心实现思路:利用回调函数,添加一个变量来控制,锁住函数和解锁函数。
开始设置一个变量 var flag = true;
If(flag) {flag = false; do something} 关闭水龙头
利用回调函数 动画执行完毕, flag = true 打开水龙头案例二:返回顶部
滚动窗口至文档中的特定位置。
window.scroll(x, y)
注意,里面的x和y 不跟单位,直接写数字案例分析:
- 带有动画的返回顶部
- 此时可以继续使用我们封装的动画函数
- 只需要把所有的left 相关的值 改为 跟 页面垂直滚动距离相关就可以了
- 页面滚动了多少,可以通过 window.pageYOffset 得到
- 最后是页面滚动,使用 window.scroll(x,y)
案例三:筋头云案例
鼠标经过某个小li, 筋斗云跟这到当前小li位置
鼠标离开这个小li, 筋斗云复原为原来的位置
鼠标点击了某个小li, 筋斗云就会留在点击这个小li 的位置案例分析:
- 利用动画函数做动画效果
- 原先筋斗云的起始位置是0
- 鼠标经过某个小li, 把当前小li 的 offsetLeft 位置 做为目标值即可
- 鼠标离开某个小li, 就把目标值设为 0
- 如果点击了某个小li, 就把li当前的位置存储起来,做为筋斗云的起始位置
- 获取宽高: