JavaScript WebAPI学习
元素偏移量 offset 系列
-
offset 可以得到元素得位置,大小等
- 获得元素距离带有定位的父元素位置
- 获得元素自身的宽度高度
-
注意:返回的数值不带单位
offset常用属性
location 对象的方法 | 返回值 |
---|---|
element.offsetParent | 返回带有定位的父级元素,如果父级都没有定位则返回 body |
element.offsetTop | 返回带有定位父元素的上方的距离 |
element.offsetLeft | 返回带有定位的父元素的左侧的距离 |
element.offsetWidth | 返回盒子的宽度 (包括padding,border) 返回值不含单位 |
element.offsetHeight | 返回盒子的高度 (包括padding,border) 返回值不含单位 |
element.offsetParent
和element.parentNode
的区别:offsetParent 是返回带有定位的父亲,否则返回 body,而 parentNode 返回的是最近一级的父亲,无论有没有定位
offset 与 style 的区别
- offset 与 style 都能获取元素的宽高等属性
- style 只能获取行内样式的值 (写在标签内的属性)
- 但是 offset 可以得到任意样式表中的值
- style 返回的有单位,offset 返回值没有单位
- 重要!offset 是一个只读属性,只能获取不能赋值
- style 是一个可读写属性,可以获取也可以赋值
offset 案例:获取鼠标在盒子内的坐标
-
实现效果:在盒子内点击后返回鼠标距离盒子边缘的距离
-
思路:通过
e.pageX
获取鼠标在页面的横坐标,然后在减去offsetLeft
就是鼠标在盒子中的坐标 -
代码示例:
<style>
.box {
height: 200px;
width: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
var box = document.querySelector('.box');
box.addEventListener('mousemove', function (e) {
// 这里的box.完全可以替换成this,this 指向函数调用者
console.log(e.pageX - box.offsetLeft);
console.log(e.pageY - box.offsetTop);
})
</script>
</body>
offset实例:拖动登录框
-
实现效果:点击登录键后,出现一个小框里面是登录界面,然后这个界面可以拖动
-
实现思路:出现和隐藏比较简单,只需要绑定两个点击事件,然后点击时候
display: none;
就可以了 -
拖拽的思路:先有一个鼠标按下的事件 (mousedown) 和鼠标移动的事件 (mousemove),然后把鼠标在网页中的坐标减去鼠标在盒子中的坐标然后就可以获得盒子移动的坐标
-
因为代码实在太长了,这里只给出 js 的代码
// 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);
})
})
- 这里还要讲一下我学习时的误区,我当时想鼠标松开时解除mousedown 事件不就好了,然而这里我就进入了一个误区,在鼠标按下的时候,mousemove 事件就已经被注册了,当我们的鼠标一移动,就不断的运行 mousemove 事件,也就是整个对话框和你的鼠标走,这时再去关闭 mousedown 事件已经没有作用了,因为对话框和你的鼠标走是在 mousemove 事件中运行的,所以我们在鼠标抬起的时候停止 mousemove事件
案例:查看商品的放大镜效果
-
在放在图片上的时候会出现大图来查看细节
-
实现方法:
-
鼠标经过图片,黄色遮挡层和大图片盒子显示,离开时隐藏这两个盒子
-
黄色遮挡层跟随鼠标的中央位置
- 注意遮挡层应该把鼠标在盒子内的坐标赋给遮挡层的 left,top 值
- 操作的时候一定不要忘了单位!
- 移动的时候遮盖层不能超过父盒子的边缘,这里只需要判断他减完的值不要小于 0,假如小于 0 的话,那就让他的left值等于 0 就可以了
-
移动黄色的遮挡层,大图片跟随移动
-
代码示例:
-
test.html
<style>
.goods_img {
height: 400px;
width: 400px;
border: 1px solid black;
cursor: move;
}
.shelter {
display: none;
position: absolute;
top: 0;
left: 0;
height: 100px;
width: 100px;
pointer-events: none;
background-color: rgb(247, 243, 21, .5);
}
.big {
display: none;
position: absolute;
left: 410px;
top: 0px;
width: 500px;
height: 500px;
background-color: rgb(5, 5, 5);
overflow: hidden;
}
.big img {
position: absolute;
height: 800px;
width: 800px;
}
</style>
<body>
<img src="images/b3.png" alt="" class="goods_img">
<div class="shelter"></div>
<div class="big"><img src="images/big.jpg" alt=""></div>
</body>
- detail.js
window.addEventListener('load', function () {
// 获取一堆元素
var goods_img = this.document.querySelector('.goods_img');
var big = this.document.querySelector('.big');
var shelter = this.document.querySelector('.shelter');
var big_img = big.querySelector('img');
// 鼠标经过小图片时,显示遮挡层和大图片
goods_img.addEventListener('mouseover', function () {
shelter.style.display = 'block';
big.style.display = 'block';
})
// 鼠标离开时,隐藏遮挡层和大图片
goods_img.addEventListener('mouseout', function () {
shelter.style.display = 'none';
big.style.display = 'none';
})
// 当鼠标移动时,开始获取鼠标的横纵坐标进行计算
goods_img.addEventListener('mousemove', function (m) {
// 在盒子内的坐标
var x = m.pageX - goods_img.offsetLeft;
var y = m.pageY - goods_img.offsetTop;
// 遮挡层在在盒子内的 top 和 left 值
var maskX = x - shelter.offsetWidth / 2;
var maskY = y - shelter.offsetWidth / 2;
// 遮挡层移动的最大距离, 因为商品盒子是正方形,所以两个是相等的
var maskMax = goods_img.offsetWidth - shelter.offsetWidth;
// 判断盒子不能超出图片边界
if (maskX <= 0) {
maskX = 0;
} else if (maskX >= maskMax) {
maskX = maskMax;
}
if (maskY <= 0) {
maskY = 0;
} else if (maskY >= maskMax) {
maskY = maskMax;
}
shelter.style.left = maskX + 'px';
shelter.style.top = maskY + 'px';
var bigMax = big_img.offsetWidth - big.offsetWidth;
// 这个是按照比例来显示右侧的图片的
var bigX = maskX * bigMax / maskMax;
var bigY = maskY * bigMax / maskMax;
// 盒子不动,图片要往下往左走,所以这里为负值
big_img.style.left = -bigX + 'px';
big_img.style.top = -bigY + 'px';
})
})
-
用到的小知识:
- js写在外部的做法:
- js写在外部时需要等待所有页面加载完成才能执行js操作,所以 window 要加上一个 load事件
- js引入时
<script src="detail.js"></script>
这样引入就可以
-
写这段代码时所遇到的问题:
- 当在商品图片里移动鼠标时,遮罩层会一闪一闪的,这是因为鼠标到商品图片上上以后会执行 mouseover 事件,出现遮罩层后会因为没有在商品图片上而执行 mouseout 事件,所以就会出现一闪一闪的状况
- 解决方法:在遮罩层上面加
pointer-events: none;
属性,这个属性的意思是鼠标穿过有这个属性的元素,鼠标事件指向他下面的元素 - 还有还有,一定不要忘记加单位
元素可视区 client 系列
- 获取元素可视区的相关信息,例如元素边框大小,元素大小等
client 对象的属性 | 返回值 |
---|---|
element.clientTop | 返回元素上边框大小 |
element.clientLeft | 返回元素左边框大小 |
element.clientWidth | 返回盒子的宽度 (包括padding,不含边框,返回值不含单位) |
element.clientHeight | 返回盒子的高度 (包括padding,不含边框,返回值不含单位) |
- 和 offsetWidth 的区别:clientWidth 不包含边框,offsetWidth 包含边框
立即执行函数
- 立即执行函数:不需要调用,立马自己能够执行的函数
- 一共有两种写法:
// 1.
(function() {})()
// 2.
(function() {}())
// 第二个小括号相当于调用函数 (可以传参)
- 立即执行函数最大的作用是:独立创建了一个作用域 (和外界的作用域隔开,里面都是局部变量)
惊,这个莫非是命名空间
读淘宝 flexible.js 代码
-
看淘宝写的代码真的赏心悦目,思考的极为周全,真的应该好好阅读大厂写的代码
-
大家可以看看老师写注释的源码
-
pageshow
事件:页面重新加载触发的事件 -
和 load 事件的区别:
-
下面三种情况都能触发 load 事件:
- a 标签的超链接
- F5 或者刷新按钮
- 前进后退按钮
-
火狐中有一个特例叫往返缓存:缓存不止缓存了页面数据,同时缓存了 DOM 和 JavaScript 的状态,实际上是把整个缓存都保存到了页面里,这时的上面的按钮就不能触发 load 事件
-
这个时候用 pageshow事件就能很好的解决这个问题
-
而且 pageshow 有一个属性,
persisted
:这个属性如果是从缓存加载的话那就返回 true
元素滚动 scroll 系列
- scroll 可以动态获取元素的大小,滚动的距离 (经常获取滚动条)
scroll 对象的属性 | 返回值 |
---|---|
element.scrollTop | 返回被滚动上去的距离 (不含单位) |
element.scrollLeft | 返回被滚动到左边的距离 (不含单位) |
element.scrollWidth | 返回内容的宽度 (包括padding,不含边框,返回值不含单位) |
element.scrollHeight | 返回内容的高度 (包括padding,不含边框,返回值不含单位) |
-
scrollWidth 和 clientWidth 的区别:scrollWidth 假如内容超出边框的话返回的是内容的宽度,而 clientWidth 假如超过边框的话仍然返回的是盒子的宽度
-
onscroll
事件:当拖动滚动条时触发
scroll 案例:仿淘宝固定右侧侧边栏
- 实现效果:淘宝右侧有一个导航栏,一旦超过某个位置他就会变成固定定位 (无论页面如何滚动他都固定在那里)
- 小知识:
window.pageYOffset
获取页面被滚动到上面的距离 (左右滚动是 pageXOffset)- 这个属性有兼容性问题,IE9 以上支持
- 代码示例:
var sliderbar = document.querySelector('.slider-bar');
var banner = document.querySelector('.banner');
// 页面滚动事件
var bannerTop = banner.offsetTop;
var sliderbarTop = sliderbar.offsetTop - bannerTop;
document.addEventListener('scroll', function () {
// 如果他滚动到了 banner 这个模块,就变成固定定位,并且更改一下 top 的值
if (window.pageYOffset >= bannerTop) {
sliderbar.style.position = 'fixed';
sliderbar.style.top = sliderbarTop + 'px';
} else {
sliderbar.style.position = 'absolute';
sliderbar.style.top = '300px';
}
})
三个系列的小结
- 这三个系列大小的对比
三个系列大小的对比 | 返回值 |
---|---|
element.offsetWidth | 返回元素padding,边框,内容的宽度 返回值没有单位 |
element.clientWidth | 返回元素padding,内容的宽度 返回值没有单位 |
element.scrollWidth | 返回元素padding,内容的宽度,假如内容超过边框返回内容实际的宽度 |
- offset 获取元素位置:offsetLeft
- client 获取元素大小:clientWidth
- scroll 获取滚动距离:scrollTop
mouseenter 和 mouseover 的区别
-
mouseover 经过自身盒子会触发,经过子盒子也会触发
-
而 mouseenter 只会经过自身盒子触发
-
产生这两种区别的原因是 mouseenter 不会冒泡
-
同样,mouseleave 在鼠标离开时也不会冒泡
动画函数封装
- 动画原理:通过定时器 setInterval() 不断移动盒子位置
- 代码示例:有一个有定位的小盒子,一直向右移动
var div = document.querySelector('div');
var timer = setInterval(function () {
if (div.offsetLeft > 800) {
clearInterval(timer);
}
div.style.left = div.offsetLeft + 5 + 'px';
}, 30)
- 中间遇到的问题:
- div 的 offsetLeft 属性是只读的,所以要用 style 来改变元素 left 值
动画函数的封装
- 写一个函数来封装这个动画
// obj:要进行动画的元素 target:目标位置
function animate(obj,target) {
var timer = setInterval(function () {
if (obj.offsetLeft > target) {
clearInterval(timer);
}
obj.style.left = obj.offsetLeft + 5 + 'px';
}, 30)
}
-
上面那个函数的优化:把 timer 作为我们引入元素的一个属性
- 这样做的好处:不在用 var 开辟内存空间,通过元素可以找到每一个定时器的 timer
-
这里有一个小 bug ,当我们是点击一个按钮才让盒子走时,当我们不断的点击按钮,盒子移动的速度会越来越快
-
解决方法:让我们的元素只有一个定时器
- 先清除所有的定时器,保证只有一个定时器执行
// 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 + 5 + 'px';
}, 30)
}
缓动动画原理
-
缓动动画:动画是缓慢停下来的
-
原理:每次移动的距离慢慢的变小,使用公式:(目标值 - 现在的位置) / 10
- 公式里的 10 可以更换为任意值,值越小步长越大
-
代码示例:
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);
}
- 这里出现了小数精度的问题,盒子停下来时他其实和目标值差零点几
- 我们可以把步长取整,而且是向上取整 (为了防止盒子会倒退几个像素) 这样就不会出现差一点的问题
- 但是这里又出现了一个小问题:当我们从一个远往近了移动时,发现和目标值差很多
- 这是因为从远往近移动是负值,所以这时候应该向下取整
- 下面加一个判断,当移动的距离是正值时向上取整,当移动距离为负时向下取整
动画函数单独封装到 js 文件内
- 可以把常用的动画封装成一个文件
- 使用的时候:
<script src="XXX.js"></script>
引入
网页轮播图
- 终于做到了心心念念的网页轮播图了
- 一共有五个功能:
- 鼠标经过轮播图时,显示左右按钮,离开时隐藏按钮
- 点击左右按钮时图片往左或右播放一张
- 播放的同时下面的小圆圈也同时变化
- 鼠标经过轮播图会暂停播放图片
显示 / 隐藏左右按钮
- 鼠标经过轮播图的盒子显示这两个按钮,离开时隐藏
- 代码示例:
// 鼠标经过显示左右按钮
focus.addEventListener('mouseenter', function () {
arrow_l.style.display = 'block';
arrow_r.style.display = 'block';
})
focus.addEventListener('mouseleave', function () {
arrow_l.style.display = 'none';
arrow_r.style.display = 'none';
})
动态生成小圆圈
- 根据图片的个数动态生成有多少个小圆圈
- 代码示例:
// 动态生成小圆圈
var ul = focus.querySelector('ul');
var ol = focus.querySelector('ol');
for (var i = 0; i < ul.children.length; i++) {
// 创建li
var li = document.createElement('li');
// 插入到ol里
ol.appendChild(li);
}
// 把 ol 的第一个 li 设置为选中状态
ol.children[0].className = 'current';
单击小圆圈让小圆圈处于选中状态
- 给每个小圆圈绑定点击事件,然后把其他所有的 li 去除current 类,只给点击的这个元素 添加 current
- 代码示例:
// 给每个元素绑定点击事件
li.addEventListener('click',function(){
// 把所有 li 去除 current 类名
for(var i = 0;i<ol.children.length;i++) {
ol.children[i].className = '';
}
// 把点击的这个留下
this.className = 'current';
})
点击小圆点实现图片滚动
-
图片滚动是通过移动 ul 来实现滚动效果
-
我们可以直接使用之前封装好的移动函数,注意引入文件时要写在 index.js 上面,和函数声明是一样的,要先声明在使用
-
滚动图片的方式:点击某个小圆圈,就让图片滚动索引号乘以图片的宽度作为 ul 移动的距离
-
通过设置自定义属性来记录当前小圆圈的索引号
// 给每个小圆圈记录索引号
li.setAttribute('index', i);
- 这个事件还是在鼠标点击之后完成的,所以还写在 click 事件内
li.addEventListener('click', function () {
// 把所有 li 去除 current 类名
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
// 把点击的这个留下
this.className = 'current';
// 点击图片实现图片滑动效果
// 点击某个 li 获取当前的索引号
var index = this.getAttribute('index');
// 移动的距离就是当前索引号乘以图片的宽度
animate(ul, -index * focusWidth);
})
点击右侧按钮实现图片滚动
-
声明一个变量 num 每点击一次,就让这个变量自增,再乘以图片的宽度,就是 ul 的移动距离
-
无缝滚动的原理:在所有图片后面再放一个第一张图片,当从最后一张滚动到伪第一张后再迅速无动画的跳回真实的第一张
// 点击右侧按钮,图片滚动一张
var num = 0;
arrow_r.addEventListener('click', function () {
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth);
})
-
但是使用这种方法就会出现一个 bug,他下面的小圆圈就会多一个
-
而且我们不想每增加一次图片就要在 html 里修改
-
下面是解决方法:
-
克隆 ul 里的第一个 li cloneNode() 里面参数为 true 是深克隆(复制里面的子节点),false 浅克隆
-
并且添加到 ul 的最后,用 appendChild() 添加
-
又因为这个 li 是在自动生成 li 之后添加的,所以不会多出一个圆点
// 克隆第一张图片放到 ul 的最后面
var first = ul.children[0].cloneNode(true);
ul.appendChild(first);
- 下面的小圆点也跟着上面的一起变化
- 在声明一个专门控制小圆点的变量,然后他只要运行到了我们克隆的图片,就让他归零,注意一定要先加加后判断
arrow_r.addEventListener('click', function () {
if (num == ul.children.length - 1) {
ul.style.left = 0;
num = 0;
}
num++;
animate(ul, -num * focusWidth);
circle++;
if (circle == ol.children.length) {
circle = 0;
}
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
ol.children[circle].className = 'current';
})
一点小bug
- 在我们点击右移按钮在点击底下的小圆点时,就会出现不同步的情况
- 我们要让两个模块的变量联系起来,让上面点击完小圆点之后也要告诉下面的右移模块我们现在到哪里了 (小圆点的变量也是一样的,也需要上面告诉他我们移动到了哪里)
// index 是指示点击小圆点时位置的变量
// num 和 circle 是指示右移箭头位置的变量和指示小圆点是哪个的变量
num = index;
circle = index;
左侧的按钮实现滚动
- 左侧按钮和右侧按钮的功能差不多
- 像镜子一样把 arrow_r 翻转一下就可以了
arrow_l.addEventListener('click', function () {
if (num == 0) {
num = ul.children.length - 1;
ul.style.left = -num * focusWidth + 'px';
}
num--;
animate(ul, -num * focusWidth);
circle--;
if (circle < 0) {
circle = ol.children.length - 1;
}
for (var i = 0; i < ol.children.length; i++) {
ol.children[i].className = '';
}
ol.children[circle].className = 'current';
});
轮播图的自动播放效果
- 自动播放就相当于点了右侧的按钮
- 一个超级强的事件:手动调用点击事件
element.click()
模拟人手点击这个按钮
var timer = setInterval(function () {
arrow_r.click();
}, 2000);
- 鼠标一经过时停止定时器也就停止滚动,鼠标一经过就开始定时器
节流阀
-
节流阀:当上一个动画执行完毕时再去执行下一个函数动画
-
在这里我们当一个图片滚动完成后才能接着点击这个按钮
-
我们用回调函数来实现锁住函数和解锁函数
-
具体思路:
- 我们在事件外面声明一个变量,当我们运行这个事件后的时候我们把这个变量置为 false,里面有一个判断条件,当变量为 false 时运行不了这个函数
- 我们再在动画运行的最后把这个变量重新置为 true 他就不会动画动的过快了
案例:返回顶部动画版
- 窗口滚动到文档中的指定位置:
window.scroll(x,y)
- 里面的 x 和 y 都是不加单位的
- 但是这种方法出来的效果是直接回到顶部的,没有动画效果
- 这里我们可以直接修改我们的动画函数,把所有 left 修改为 top 就可以了
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 - window.pageYOffset) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
if (window.pageYOffset == target) {
// 停止动画 本质是停止定时器
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
// if (callback) {
// // 调用函数
// callback();
// }
callback && callback();
}
// 把每次加1 这个步长值改为一个慢慢变小的值 步长公式:(目标值 - 现在的位置) / 10
// obj.style.top = window.pageYOffset + step + 'px';
window.scroll(0, window.pageYOffset + step);
}, 15);
}
案例:筋斗云
- 实现样式:
- 鼠标经过某个 li 筋斗云移动到当前 li 的位置
- 离开这个 li 时,筋斗云复原为原来的位置
- 点击了某个 li 筋斗云就会停留在这个 li 的位置
-
具体思路:
- 通过设置筋斗云的offsetLeft值实现筋斗云的移动
- 设置一个全局变量作为筋斗云的偏移量,然后绑定三个事件
- 鼠标经过把筋斗云的位置移动到鼠标处
- 鼠标离开把上面设置的全局变量设置为筋斗云的偏移量
- 鼠标点击改变全局变量为现在的偏移量
-
代码示例:
// 1. 获取元素
var cloud = document.querySelector('.cloud');
var c_nav = document.querySelector('.c-nav');
var lis = c_nav.querySelectorAll('li');
// 2. 给所有的小li绑定事件
// 这个current 做为筋斗云的起始位置
var current = 0;
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;
});
}