PC 端网页特效一(offset、client、scroll)
学习目标:
- 能够说出常见 offset 系列属性的作用
- 能够说出常见 client 系列属性的作用
- 能够说出常见 scroll 系列属性的作用
一、元素偏移量 offset 系列
1.1 offset 概述
offset 就是偏移量,我们使用 offset 系列相关属性可以动态的得到该元素的位置(偏移)、大小等。
- 获得元素距离带有定位父元素的位置
- 获得元素自身的大小(宽度 高度)
offset 系列常用属性:
offset 系列属性 | 作用 |
---|---|
element.offsetParent | 返回作为该元素带有定位的父级元素,如果父级都没有定位则返回 body |
element.offsetTop | 返回元素相对带有定位父元素上方的偏移 |
element.offsetLeft | 返回元素相对带有定位父元素左方的偏移 |
element.offsetWidth | 返回元素包括 padding、边框、内容区的宽度值 |
element.offsetHeight | 返回元素包括 padding、边框、内容区的高度值 |
<style>
*{
margin: 0;
padding: 0;
}
.father{
position: relative;
width: 200px;
height: 200px;
background-color: red;
margin: 20px; /* 外边距 */
padding:5px; /* 内边距 */
}
.son{
width: 50px;
height: 50px;
background-color: blue;
margin-left: 50px; /* 左外边距 */
padding:5px; /* 内边距 */
}
</style>
<div class="father"> <div class="son"></div> </div>
<script>
let father = document.querySelector(".father");
console.log(father.offsetTop); // 20
console.log(father.offsetLeft); // 20
console.log(father.offsetWidth); // 210
console.log(father.offsetHeight); // 210
console.log(father.offsetParent); // <body>
let son = document.querySelector(".son");
console.log(son.offsetTop); // 5
console.log(son.offsetLeft); // 55
console.log(son.offsetWidth); // 60
console.log(son.offsetHeight); // 60
console.log(son.offsetParent); // <div class="father">
</script>
注意:① 该系列返回的数值都是不带单位的数值。② offsetTop 、offsetLeft 、offsetParent 这三个属性都要以带了定位的父元素为准的,如果没有父亲或父亲都没有定位,则以 body 为准。
1.2 offset 与 style 区别
- offset 可以得到任意样式表中的样式值;style 只能得到行内样式表中的样式值。
- offset 系列返回的结果是没有单位的数值;style.width 返回的是带 有单位的字符串。
- offsetWidth 等包含 padding + border + width;style.width 只有 width,不含内边距和边框。
- offsetWidth 等是只读属性,只能获取不能赋值;style.width 等是可读写属性,能获取能赋值。
- 想获取元素大小,用 offset 更合适;想给元素调整大小,需要用 style 相关属性改变。
案例1:获取鼠标在盒子内的坐标
我们可以通过鼠标点击在页面中的坐标(e.pageX,e.pageY)和盒子在页面当中的距离(box.offsetLeft,box.offsetTop)去计算出鼠标在盒子内的坐标。onmousemove
是鼠标移动事件的处理程序名称。
<script>
let box = document.querySelector(".box");
box.onmousemove = function(e){
let x = e.pageX - box.offsetLeft;
let y = e.pageY - box.offsetTop;
this.innerHTML = "鼠标在盒子内的X坐标:"+ x + "<br>鼠标在盒子内的Y坐标:" + y;
}
</script>
案例2:模态框拖拽
弹出框,我们也称之为模态框。要求如下:
- 点击弹出层,会弹出模态框,并且显示灰色半透明的遮挡层。
- 点击关闭按钮,可以关闭模态框,并且同时关闭灰色半透明的遮挡层。
- 鼠标放在模态框最上面一行,可以按住鼠标拖拽模态框在页面中移动。
- 鼠标松开,停止拖动模态框移动。
实现
- 点击弹出层,模态框和遮挡层会显示出来:display:block;
- 点击关闭按钮,模态框和遮挡层会隐藏起来:display:none;
- 在页面中的拖拽原理:鼠标按下并且移动,之后松开鼠标。涉及到的事件有:鼠标按下 mousedown;鼠标移动 mousemove;鼠标松开 mouseup;
- 拖拽过程:鼠标在按下移动的过程中,在模态框中的坐标是不变的,那么在页面中的坐标减去在模态框中的坐标就可以得到模态框在页面中的坐标了。
- 事件源是模块框中最上面的一行。
<script>
// 1.获取元素
let login = document.querySelector(".login");
let mask = document.querySelector(".login-bg");
let link = document.querySelector("#link");
let closeBtn = document.querySelector("#closeBtn");
let 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) {
let x = e.pageX - login.offsetLeft;
let 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>
案例3:仿京东放大镜效果
整个案例可以分为三个功能模块:
-
鼠标经过小图片盒子,黄色的遮挡层和大图片盒子显示,离开隐藏两个盒子。
mouseover
和mouseout
事件。 -
黄色的遮挡层跟随鼠标功能。
-
移动花色遮挡层,大图片跟随移动功能。
<div class="xtu"> // 裝小图片的盒子
<img src="./images/iphone.jpg"> // 小图片
<div class="mask"></div> // 遮挡层
<div class="dtu"> // 裝大图的盒子
<img src="./images/iphone.jpg" alt="" class="dimg"> // 大图片
</div>
</div>
<style>
.xtu {/* 小图片盒子 */
position: relative; /*遮挡层和裝大图的盒子作为子代有绝对定位,父盒子要有相对定位*/
width: 225px;
height: 225px;
}
.xtu>img {/* 小图片 */
width: 100%; /*小图片与父盒子一样大*/
}
.mask { /* 遮挡层 */
display: none; /* 刚开始隐藏 */
position: absolute; /* 加绝对定位 */
top: 0; /* 初始位置 */
left: 0;
width: 150px; /* 遮挡层大小 */
height: 150px;
background-color: #FEDE4F;
opacity: .5; /* 遮挡层透明度 */
border: 1px solid #ccc;
cursor: move; /* 设置鼠标样式 */
}
.dtu {/* 大图片盒子 */
position: absolute; /* 加绝对定位 */
display: none; /* 刚开始隐藏 */
overflow: hidden; /* 图片超出部分隐藏 */
left: 230px; /* 定位到小图片盒子旁边 */
top: 0;
width: 320px; /* 大图片盒子大小 */
height: 320px;
background-color: pink;
}
.dtu>img{/* 大图片 */
position: absolute; /* 加绝对定位 */
}
</style>
<script>
let xtu = document.querySelector(".xtu"); // 获取小图片盒子
let mask = document.querySelector(".mask"); // 获取遮挡层
let dtu = document.querySelector(".dtu"); // 获取大图片盒子
let dimg = document.querySelector(".dimg") // 获取大图片
// 1当鼠标经过小图片时就显示遮挡层和大图片
xtu.addEventListener("mouseover", function () {
mask.style.display = "block";
dtu.style.display = "block";
})
// 2当鼠标离开小图片时就隐藏遮挡层和大图片
xtu.addEventListener("mouseout", function () {
mask.style.display = "none";
dtu.style.display = "none";
})
// 3鼠标移动的时候,让遮挡层跟着鼠标走
xtu.addEventListener("mousemove", function (e) {
// e.pageX - xtu.offsetLeft 鼠标在小图片内的水平坐标
// maskX 为 mask 中心在小图片内的坐标
let maskX = e.pageX - xtu.offsetLeft - mask.offsetWidth / 2;
let maskY = e.pageY - xtu.offsetTop - mask.offsetHeight / 2;
if (maskX < 0) maskX = 0;
if (maskY < 0) maskY = 0;
// maxMaskMoveX 为 遮挡层 能够移动的最大距离
let maxMaskMoveX = xtu.offsetWidth-mask.offsetWidth;
let maxMaskMoveY = xtu.offsetHeight-mask.offsetHeight;
if (maskX > maxMaskMoveX) maskX = maxMaskMoveX;
if (maskY > maxMaskMoveY) maskY = maxMaskMoveY;
mask.style.left = maskX + "px";
mask.style.top = maskY + "px";
// 4遮挡层移动的时候,让大图片跟着遮挡层移动
// 满足等比例移动 maskX/maxMaskMoveX = dtuX/maxDtuMoveX
// maxDtuMoveX = 图片的宽度(dimg.offsetWidth) - 大图片框的宽度(dtu.offsetWidth)
let maxDtuMoveX = dimg.offsetWidth - dtu.offsetWidth; // 大图片水平能移动的最大距离
let maxDtuMoveY = dimg.offsetHeight - dtu.offsetHeight; // 大图片垂直能移动的最大距离
let dtuX = maskX * maxDtuMoveX / maxMaskMoveX; // 计算大图片水平移动距离
let dtuY = maskY * maxDtuMoveY / maxMaskMoveY; // 计算大图片垂直移动距离
dimg.style.left= -dtuX + "px";
dimg.style.top = -dtuY + "px";
})
</script>
二、元素可视区 client 系列
2.1 client 概述
client 是客户端的意思,我们使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到钙元素的边框大小、元素大小等。
client 系列属性 | 作用 |
---|---|
element.clientTop | 返回元素上边框大小 |
element.clientLeft | 返回元素左边框大小 |
element.clientWidth | 返回元素包括 padding 和内容区的宽度值(不含边框) |
element.clientHeight | 返回元素包括 padding 和内容区的高度值(不含边框) |
client 系列返回的也是不带单位的数值。注意与 offsetWidth 的区别:clientWidth 不含边框、offsetWidth 包含边框。
<style> /*css*/
div{
width: 200px;
height: 150px;
border: 5px solid red;
}
</style>
<body> <!--html -->
<div></div>
<script> // css
let div = document.querySelector("div");
console.log(div.clientWidth); // 200
console.log(div.clientHeight); // 150
console.log(div.clientTop); // 5
console.log(div.clientLeft); // 5
</script>
</body>
案例1:淘宝 flexible.js 源码分析
-
立即执行函数。可以查看 10.15、立即调用的函数表达式(IIFE) 。
-
源码分析:比较重要的知识点罗列如下
① :document.documentElement 获取 html 根元素
② :window.devicePixelRatio 获取物理像素比的,PC 端的物理像素比为 1 。移动端为 2 。
③ :pageshow 事件、 load 事件、persisted 事件。
触发 load 事件的三种情况:1)a 标签的超链接 2)刷新按钮 3)前进后退按钮(但火狐浏览器会有缓存,点击后退按钮不会刷新页面)
pageshow 事件在页面显示时触发,无论页面是否来自缓存。
pageshow 事件会在 load 事件触发后触发。
事件对象中的 persisted 可以判断是否是缓存中的页面触发的 pageshow 事件,这个事件是给 window 添加的。如下第 22 行代码。
// flexible.js 文件内容
(function flexible(window, document) { // 立即执行函数,传入了 window 和 document 参数
let docEl = document.documentElement // 获取 html 根元素
let dpr = window.devicePixelRatio || 1 // dpr:物理像素比(pc端的物理像素比是1,移动端是2)
// 【函数1】设置 body 的字体大小的函数
function setBodyFontSize() {
if (document.body) { // 有 body ,则设置 fontSize 大小
document.body.style.fontSize = (12 * dpr) + 'px'
} else { // 没有 body ,则等页面元素加载好后再设置 fontSize 大小
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize(); // 调用设置 body 的字体大小的函数
// 【函数2】设置 1rem = viewWidth / 10 :设置 html 元素的文字大小
function setRemUnit() {
let rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit() // 调用设置 html 元素的文字大小的函数
// 【事件1】当页面尺寸大小发生变化的时候,要重新设置下 rem 的大小
window.addEventListener('resize', setRemUnit) // resize 事件:页面大小发生改变
// 【事件2】pageshow 是重新加载页面触发的事件。
window.addEventListener('pageshow', function(e) {
// e.persisted 为 true 表示这个页面来自于缓存
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)) // 立即执行函数,传入了 window 和 document 参数
三、元素滚动 scroll 系列
3.1 scroll 概述
scroll 是滚动的意思,与页面滚动条相关。当我们使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。scroll 系列返回的也是不带单位的数值。
scroll 系列属性 | 作用 |
---|---|
element.scrollTop | 返回被卷去的内容高度(上边框的下沿到内容的顶部) |
element.scrollLeft | 返回被卷去的内容左侧的宽度 |
element.scrollWidth | 返回自身实际的宽度,不含边框,包含 padding |
element.scrollHeight | 返回自身实际的高度,不含边框,包含 padding |
<style>
div{
width: 200px;
height: 150px;
border: 10px solid red;
padding:10px;
overflow:auto; /* 超出自动显示滚动条*/
}
</style>
<body>
<div>内容内容内容内容内容内容内容内容内容内容内容内容内容内容内容
内容内容内容内容内容内容内容容内容内容内容内容内容内容内容内容
内容内容内容内容内容内容内容容内容内容内容内容内容内容内容内容
内容内容内容内容内容内容内容容内容内容内容内容内容内容内容内容
</div>
<script>
let div = document.querySelector("div");
console.log(div.scrollHeight); // 262 = 实际内容高度 + 2*padding:10
console.log(div.clientHeight); // 170 = height:150 + 2*padding:10
console.log(div.offsetHeight); // 190 = height:150 + 2*padding:10 + 2*border:10
</script>
scroll 滚动事件:当我们滚动条发生变化时会触发的事件。
<script>
let div = document.querySelector("div");
div.addEventListener("scroll",function(){
console.log(div.scrollTop);
})
</script>
案例1:仿淘宝固定右侧侧边栏
要求:
- 原先侧边栏是绝对定位
- 当页面滚动到一定位置,侧边栏改为固定定位
- 页面继续滚动,会让返回顶部显示出来
分析:
- 需要用到页面滚动事件 scroll ,因为是页面滚动,事件源是 document。
- 滚动到每个位置,就是判断页面被卷上去的高度。
- 页面内容被卷去的头部可以通过 window.pageYOffset 获得,卷去的左侧:window.pageXOffset。
- 注意:元素内容被卷去的头部是 element.scrollTop,左侧卷去多少:element.scrollLeft。
<body> <!-- 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>
</body>
<style> /* css */
.w{
width: 400px;
margin: 10px auto;
}
.header {
height: 150px;
background-color: purple;
}
.banner {
height: 250px;
background-color: skyblue;
}
.main {
height: 1000px;
background-color: yellowgreen;
}
.slider-bar{
position: absolute;
left:50%;
top:300px;
margin-left: 205px;
width: 45px;
height: 130px;
background-color: pink;
}
span {
display: none;
position: absolute;
width: 45px;
height: 45px;
background-color: red;
font-size: 16px;
bottom: 0;
}
</style>
<script>
// 获取相关元素
let sliderbar = document.querySelector(".slider-bar");
let banner = document.querySelector(".banner");
let goBack = document.querySelector(".goBack");
let banner_top = banner.offsetTop; // banner 上边缘到页面顶端的距离
let sliderbar_top = sliderbar.offsetTop; // sliderbar 上边缘到页面顶端的距离
// 页面滚动事件
document.addEventListener("scroll",function(){
if(window.pageYOffset >= banner_top){ // 页面滚动到 banner 区时
sliderbar.style.position = "fixed"; // sliderbar 该给固定定位
// sliderbar 的位置固定在原来 banner 与 sliderbar 的高度差处
sliderbar.style.top = sliderbar_top - banner_top + "px";
goBack.style.display = "block"; // 显示返回顶部
}else{
sliderbar.style.position = "absolute";
sliderbar.style.top = sliderbar_top + "px";
goBack.style.display = "none";
}
})
</script>
3.2 三大系列总结
scrollHeight、clientHeight 与 offsetHeight 的区别
- scrollHeight: 返回元素内实际内容的高度,包括 padding,不包括 border。
- clientHeight: 返回元素的高度,包括 padding,不包括 border。
- offsetHeight: 返回元素包括 padding、边框、内容区的高度值。
他们的主要用法:
- offset 系列常用于获得元素的位置 offsetLeft、offsetTop
- client 系列常用于获取元素的大小 clientWidth、clientHeight
- scroll 系列常用于获取滚动距离 scrollTop、scrollLeft
- 注意页面滚动距离通过 window.pageYOffset 获得