阿卡不拉阿卡不拉
Vue3
阿卡的博客
Vue3
阿卡的博客
  • Vue3

    • 快速入门

      • 搭建工程 👌
      • 模板语法
      • 响应式基础
      • 响应式常用 API
      • 计算属性
      • 类与样式绑定
      • 条件和列表渲染
      • 事件处理
      • 表单处理
      • 生命周期
      • 侦听器
      • 组件介绍
      • Props
      • 自定义事件
      • 组件v-model
      • 插槽
      • 前端路由介绍
      • KeepAlive内置组件
      • 状态管理库
      • 组件库介绍
    • 深入本质

      • 虚拟DOM本质
      • 模板的本质
      • 组件树和虚拟DOM树
      • 数据拦截的本质
      • 响应式数据的本质
      • 响应式的本质
      • 响应式和组件渲染
      • 实现响应式系统 1
      • 实现响应式系统 2
      • 图解EFFECT
      • 实现响应式系统 3
      • 手写computed
      • 手写watch
      • 指令的本质
      • 插槽的本质
      • v-model的本质
      • setup 语法标签
      • 组件生命周期
      • keepalive 生命周期
      • keepalive的本质
      • key的本质
    • 细节补充

      • 【Vue】属性透传
      • 【Vue】依赖注入
      • 【Vue】组合式函数 👌
      • 【Vue】自定义指令
      • 【Vue】插件
      • 【Vue】Transition
      • 【Vue】TransitionGroup
      • 【Vue】Teleport
      • 【Vue】异步组件
      • 【Vue】Suspense
      • 【Router】路由模式
      • 【Router】路由零碎知识
      • 【Router】路由匹配语法
      • 【Router】路由组件传参
      • 【Router】内置组件和函数
      • 【Router】导航守卫
      • 【Router】过渡特效
      • 【Router】滚动行为
      • 【Router】动态路由
      • 【State】通信方式总结
      • 【State】Pinia 自定义插件
      • 【场景】封装树形组件
      • 【场景】自定义 ref 实现防抖
      • 【场景】懒加载
      • 【场景】虚拟列表
      • 【场景】虚拟列表优化
      • 【第三方库】VueUse
      • 【第三方库】vuedragable
      • 【第三方库】vue-drag-resize
      • 【第三方库】vue-chartjs
      • 【第三方库】vuelidate
      • 【第三方库】vue3-lazyload
      • 【场景】Websocket 聊天室
      • 【Vite】✨ 认识 Vite👌
      • 【Vite】配置文件 👌
      • 【Vite】✨ 依赖预构建 👌
      • 【Vite】构建生产版本 👌
      • 【Vite】环境变量与模式
      • 【Vite】CLI
      • 【Vite】Vite 插件
  • 笔面试

    • HTML

      • HTML 面试题汇总
      • 文档声明
      • 语义化
      • W3C 标准组织
      • SEO
      • iframe
      • 微格式
      • 替换元素
      • 页面可见性
    • CSS

      • CSS 面试题汇总
      • CSS 单位总结
      • 居中方式总结
      • 隐藏元素方式总结
      • 浮动
      • 定位总结
      • BFC
      • CSS 属性计算过程
      • CSS 层叠继承规则总结
      • @import 指令
      • CSS3 calc 函数
      • CSS3 媒体查询
      • 过渡和动画事件
      • 渐进增强和优雅降级
      • CSS3 变形
      • 渐进式渲染
      • CSS 渲染性能优化
      • 层叠上下文
      • CSS3 遮罩
    • JavaScript

      • JavaScript 面试题汇总
      • ✨ let、var、const 的区别
      • JS中的数据类型
      • 包装类型
      • 数据类型的转换
      • 运算符
      • ✨ 原型链
      • ✨ this 指向
      • ✨ 垃圾回收与内存泄漏
      • ✨ 执行栈和执行上下文
      • ✨ 作用域和作用域链
      • ✨ 闭包
      • DOM 事件的注册和移除
      • DOM 事件的传播机制
      • 阻止事件默认行为
      • 递归
      • ✨ 属性描述符
      • class 和构造函数区别
      • 浮点数精度问题
      • 严格模式
      • ✨ 函数防抖和节流
      • ✨ WeakSet 和 WeakMap
      • ✨ 深浅拷贝
      • 函数柯里化
      • Node 事件循环
      • 尺寸和位置
    • 浏览器

      • 浏览器面试题汇总
      • ✨ 浏览器的渲染流程
      • ✨ 资源提示关键词
      • 浏览器的组成部分
      • IndexedDB
      • ✨ File API
      • ✨ 浏览器缓存
      • ✨ 浏览器跨标签页通信
      • Web Worker
    • 网络

      • 网络面试题汇总
      • 五层网络模型 👌
      • 常见请求方法 👌
      • ✨cookie👌
      • 面试题
      • 加密
      • ✨JWT👌
      • ✨ 同源策略及跨域问题 👌
      • 文件上传
      • ✨ 输入 url 地址之后
      • 文件下载
      • ✨ session
      • ✨ TCP
      • ✨ CSRF 攻击
      • ✨XSS 攻击 👌
      • ✨ 网络性能优化
      • 断点续传
      • 域名和 DNS
      • SSL、TLS、HTTPS 的关系
      • ✨ HTTP 各版本差异 👌
      • HTTP 缓存协议
      • ✨ WebSocket
    • 工程化

      • CMJ 和 ESM
      • npx
      • ESLint
    • Vue2

      • Vue 面试题汇总相关
      • 组件通信方式总结
      • 虚拟 DOM
      • v-model
      • 数据响应式原理
      • diff
      • 生命周期详解
      • computed
      • 过滤器
      • 作用域插槽
      • 过度和动画
      • 优化
      • keep-alive
      • 长列表优化
      • 其他 API
      • 模式和环境变量
      • 更多配置
      • 更多命令
      • 嵌套路由
      • 路由切换动画
    • Vue3

      • ✨ Vue3 整体变化 👌
      • ✨ Vue3 响应式变化 👌
      • ✨ nextTick 实现原理 👌
      • 两道代码题 👌
      • Vue 运行机制
      • 渲染器核心功能
      • 事件绑定与更新

尺寸和位置

在 JavaScript 中操作 DOM 节点使其运动的时候,常常会涉及到各种宽高以及位置坐标等概念,如果不能很好地理解这些属性所代表的意义,就会在书写代码时遇到不小的问题。而由于这些属性概念较多,加上浏览器之间实现方式不同,常常会造成概念混淆。

本章,我们就一起来总结一下使用 JavaScript 操作 DOM 时,尺寸和宽高相关的属性。

主要分为以下两部分:

  • DOM 对象相关尺寸和位置属性
    • 只读属性
      • clientWidth 和 clientHeight 属性
      • offsetWidth 和 offsetHeight 属性
      • clientTop 和 clientLeft 属性
      • offsetLeft 和 offsetTop 属性
      • scrollHeight 和 scrollWidth 属性
    • 可读可写属性
      • scrollTop 和 scrollLeft 属性
      • domObj.style.xxx 属性
  • event 事件对象相关尺寸和位置属性
    • clientX 和 clientY 属性
    • screenX 和 screenY 属性
    • offsetX 和 offsetY 属性
    • pageX 和 pageY 属性

DOM 对象相关尺寸和位置属性

在 DOM 对象所提供的尺寸和位置相关属性中,可以分为只读属性和可读可写属性。

只读属性

所谓的只读属性指的是 DOM 节点的固有属性,该属性只能通过 JavaScript 去获取而不能通过 JavaScript 去设置,而且获取的值是只有数字并不带单位的( px、em 等 )

常见的只读属性有:

  • clientWidth 和 clientHeight 属性
  • offsetWidth 和 offsetHeight 属性
  • clientTop 和 clientLeft 属性
  • offsetLeft 和 offsetTop 属性
  • scrollHeight 和 scrollWidth 属性

下面我们来一组一组进行介绍。

clientWidth 和 clientHeight 属性

该属性指的是元素的可视部分宽度和高度,即 padding + content,例如:

<div id="container" class="container"></div>
.container {
    width: 200px;
    height: 200px;
    background-color: red;
    border: 1px solid;
    overflow: auto;
    padding: 10px;
    margin: 20px;
}
var container = document.getElementById("container");
console.log("clientWidth:", container.clientWidth); // 220
console.log("clientHeight:", container.clientHeight); // 220

offsetWidth 和 offsetHeight 属性

这一对属性指的是元素的 border+padding+content 的宽度和高度。例如:

var container = document.getElementById("container");
console.log("offsetWidth:", container.offsetWidth); // 222
console.log("offsetWidth:", container.offsetWidth); // 222

可以看到该属性和 clientWidth 以及 clientHeight 相比,多了设定的边框 border 的宽度和高度。

clientTop 和 clientLeft 属性

这一对属性是用来读取元素的 border 的宽度和高度的。例如:

var container = document.getElementById("container");
console.log("clientTop:", container.clientTop); // 1
console.log("clientLeft:", container.clientLeft); // 1

offsetLeft 和 offsetTop 属性

首先需要介绍一下 offsetParent 属性,该属性是获取当前元素的离自己最近的并且定了位的祖先元素,该祖先元素就是当前元素的 offsetParent。

如果从该元素向上寻找,找不到这样一个祖先元素,那么当前元素的 offsetParent 就是 body 元素。

offsetLeft 和 offsetTop 指的是当前元素,相对于其 offsetParent 左边距离和上边距离,即当前元素的 border 到包含它的 offsetParent 的 border 的距离。

下面我们来具体举例说明:

var container = document.getElementById("container");
console.log(container.offsetParent); // body

可以看到,我们直接访问 container 盒子的 offsetParent 属性,因为不存在定了位的祖先元素,所以显示出来的是 body 元素。

接下来我们对上面的代码进行改造:

<div id="container" class="container">
    <div id="item" class="item"></div>
</div>
.container {
    width: 200px;
    height: 200px;
    background-color: red;
    border: 1px solid;
    overflow: auto;
    padding: 10px;
    margin: 20px;
    position: relative;
}
.item {
    width: 50px;
    height: 50px;
    background-color: blue;
    position: absolute;
    left: 100px;
    top: 100px;
}

当前效果如下:

image-20211105143812415

接下来我们来获取 item 盒子的 offsetLeft 以及 offsetTop 属性值。

var container = document.getElementById("container");
var item = document.getElementById("item");
console.log(item.offsetParent); // container 盒子 dom 对象
console.log(item.offsetLeft); // 100
console.log(item.offsetTop); // 100

接下来我们不对 item 子元素进行定位,而是使用 margin 的方式来设置子盒子的位置,如下:

.item {
    width: 50px;
    height: 50px;
    background-color: blue;
    margin: 50px;
}

然后再次获取 item 盒子的 offsetLeft 以及 offsetTop 属性值,如下:

var container = document.getElementById("container");
var item = document.getElementById("item");
console.log(item.offsetParent); // container 盒子 dom 对象
console.log(item.offsetLeft); // 60
console.log(item.offsetTop); // 60

可以看到,打印出来的是 60,因为我们设置的 margin 的值为 50,但是其定了位的父元素还设置了 10 像素的 padding,所以加起来就是 60。

scrollHeight 和 scrollWidth 属性

顾名思义,这两个属性指的是当元素内部的内容超出其宽度和高度的时候,元素内部内容的实际宽度和高度。

如果当前元素的内容没有超过其高度或者宽度,那么返回的就是元素的可视部分宽度和高度,即和 clientWidth 和 clientHeight 属性值相同。

<div id="container" class="container">
    Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nulla repellat
    porro atque culpa rem sunt sed! Voluptates vel incidunt accusamus reiciendis
    aut, adipisci ut. Hic, impedit officia.Quis, animi beatae. Facere dolorum
    quasi laborum, rem facilis illum necessitatibus sint doloribus beatae
    exercitationem sapiente! Quod vel cupiditate quam libero, delectus natus.
</div>
.container {
    width: 200px;
    height: 200px;
    background-color: red;
    border: 1px solid;
    overflow: auto;
    padding: 10px;
    margin: 20px;
}

上面的代码中,我们为 container 盒子加入了一些文字,使其能够产生滚动效果,接下来访问 scrollHeight 和 scrollWidth 属性,如下:

var container = document.getElementById("container");
console.log("scrollWidth:", container.scrollWidth); // scrollWidth: 220
console.log("scrollHeight", container.scrollHeight); // scrollHeight 372

如果 container 盒子不具备滚动的条件,那么返回的值和 clientWidth 和 clientHeight 属性值是相同的。

<div id="container" class="container"></div>
var container = document.getElementById("container");
console.log("scrollWidth:", container.scrollWidth); // scrollWidth: 220
console.log("scrollHeight", container.scrollHeight); // scrollHeight 220

可读可写属性

所谓的可读可写属性指的是不仅能通过 JavaScript 获取该属性的值,还能够通过 JavaScript 为该属性赋值。

scrollTop 和 scrollLeft 属性

这对属性是可读写的,指的是当元素其中的内容超出其宽高的时候,元素被卷起的高度和宽度。

<div id="container" class="container">
    Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nulla repellat
    porro atque culpa rem sunt sed! Voluptates vel incidunt accusamus reiciendis
    aut, adipisci ut. Hic, impedit officia.Quis, animi beatae. Facere dolorum
    quasi laborum, rem facilis illum necessitatibus sint doloribus beatae
    exercitationem sapiente! Quod vel cupiditate quam libero, delectus natus.
</div>
.container {
    width: 200px;
    height: 200px;
    background-color: red;
    border: 1px solid;
    overflow: auto;
    padding: 10px;
    margin: 20px;
}
var container = document.getElementById("container");
container.onscroll = function () {
    console.log("scrollTop:", container.scrollTop);
    console.log("scrollLeft", container.scrollLeft);
};

在上面的代码中,我们的 container 盒子内容超出了容器的高度,我们为该盒子绑定了 scroll 事件,滚动的时候打印出 scrollTop 和 scrollLeft,即元素被卷起的高度和宽度。

效果如下:

2021-11-05 15.15.58

该属性因为是可读可写属性,所以可以通过赋值来让内容自动滚动到某个位置。例如很多网站右下角都有回到顶部的按钮,背后对应的 JavaScript 代码就是通过该属性来实现的。

domObj.style.xxx 属性

对于一个 DOM 元素来讲,它的 style 属性返回的也是一个对象,并且这个对象中的任意一个属性是可读写的。例如 domObj.style.top、domObj.style.wdith 等,在读的时候,它们返回的值常常是带有单位的(如 px )。

但是,对于这种方式,它只能够获取到该元素的行内样式,而并不能获取到该元素最终计算好的样式。如果想要获取计算好的样式,需要使用 *obj.currentstyle( IE )*和 getComputedStyle( IE 之外的浏览器 )

另一方面,由于 domObj.style.xxx 属性能够被赋值,所以 JavaScript 控制 DOM 元素运动的原理就是通过不断修改这些属性的值而达到其位置改变的,需要注意的是,给这些属性赋值的时候需要带单位的要带上单位,否则不生效。

event 事件对象相关尺寸和位置属性

对于元素的运动的操作,通常还会涉及到事件里面的 event 对象,而 event 对象也存在很多位置属性,且由于浏览器兼容性问题会导致这些属性间相互混淆,这里也来进行一个总结。

clientX 和 clientY 属性

这对属性是当事件发生时,鼠标点击位置相对于浏览器(可视区)的坐标,即浏览器左上角坐标的( 0 , 0 ),该属性以浏览器左上角坐标为原点,计算鼠标点击位置距离其左上角的位置,

不管浏览器窗口大小如何变化,都不会影响点击位置的坐标。

<div id="container" class="container"></div>
* {
    margin: 0;
    padding: 0;
}
.container {
    width: 200px;
    height: 200px;
    background-color: red;
    border: 1px solid;
    overflow: auto;
    padding: 10px;
    margin: 20px;
}
var container = document.getElementById("container");
container.onclick = function (ev) {
    var evt = ev || event;
    console.log(evt.clientX + ":" + evt.clientY);
};

在上面的代码中,我们为 container 盒子绑定了点击事件,获取该盒子的 clientX 以及 clientY 的值,接下来我们来进行点击测试:

image-20211105153614467

我们分别点击该盒子的最左上角和最右下角,打印出来的值分别是(20 , 20)和 (241 , 241),可以看出这确实是以浏览器左上角坐标的( 0 , 0 )为原点来进行计算的。

screenX 和 screenY 属性

screenX 和 screenY 是事件发生时鼠标相对于屏幕的坐标,以设备屏幕的左上角为原点,事件发生时鼠标点击的地方即为该点的 screenX 和 screenY 值。例如:

var container = document.getElementById("container");
container.onclick = function (ev) {
    var evt = ev || event;
    console.log(evt.screenX + ":" + evt.screenY);
};
image-20211105154500066

我们将浏览器缩小,同样是点击 container 盒子的最左上角,打印出来的是(368 , 365),因为这是以屏幕最左上角为原点的。

offsetX 和 offsetY 属性

这一对属性是指当事件发生时,鼠标点击位置相对于该事件源的位置,即点击该 DOM 元素,以该 DOM 左上角为原点来计算鼠标点击位置的坐标。例如:

var container = document.getElementById("container");
container.onclick = function (ev) {
    var evt = ev || event;
    console.log("offsetX:", evt.offsetX);
    console.log("offsetY:", evt.offsetY);
};

同样点击 container 元素的最左上角,打印出( 0, 0 )

pageX 和 pageY 属性

顾名思义,该属性是事件发生时鼠标点击位置相对于页面的位置,通常浏览器窗口没有出现滚动条时,该属性和 clientX 及 clientY 是等价的。

例如:

var container = document.getElementById("container");
container.onclick = function (ev) {
    var evt = ev || event;
    console.log("pageX:", evt.pageX);
    console.log("pageY:", evt.pageY);
    console.log("clientX:", evt.clientX);
    console.log("clientY:", evt.clientY);
};

此时点击 container 盒子,得到的 pageX、pageY 的值和 clientX、clientY 完全相同。

但是当浏览器出现滚动条的时候,pageX 通常会大于 clientX,因为页面还存在被卷起来的部分的宽度和高度。

例如:

...
body{
  height: 5000px;
}
...

我们为 body 添加一个 height 为 500px,使其能够产生滚动效果,此时两组属性的区别就出来了。如下:

2021-11-05 16.06.06

-EOF-

最近更新:: 2025/7/16 12:57
Contributors: AK
Prev
Node 事件循环