2024-05-09    2024-05-17    5691 字  12 分钟

在页面渲染的过程中,获取一个窗口或元素的尺寸大小及位置信息是必要的。鉴于市面上浏览器的多种多样以及某些历史原因,相关的一些元素属性和 API 可能有差异,这里我们以它们在 Chrome 中的表现为准,展开聊一下相关方面的东东 ~

⭐ 写在最前面的

这个章节属于增补内容,在后续查询资料的过程中无意中发现在 MDN 上有更加详细易懂的相关知识。放在这里,以有利于后续内容的深度认识。详见📔 坐标系 - CSS:层叠样式表 | MDN

标准 CSS 对象模型坐标系

CSS 对象模型使用四种标准坐标系。为了帮助直观地理解这些主要坐标系,下图显示了一台显示器,显示器上有一个浏览器窗口,窗口中的内容滚动到了视口之外。在视口外滚动的页面内容在浏览器窗口上方显示为半透明,以指示“页面”坐标的原点在哪里。“客户端”、“页面”和“视口”坐标系的原点已突出显示。

![[assets/Pasted image 20240516144020.png]]

偏移

使用“偏移”模型指定的坐标使用的是被检查元素或发生事件的元素的左上角。

例如,当发生鼠标事件时,事件的 offsetXoffsetY 属性中指定的鼠标位置相对于事件发生节点的左上角。原点的嵌入距离由 padding-leftpadding-top 指定。

视口

你可以在 视口概念 - CSS:层叠样式表 | MDN 了解更多关于视口的信息。

“视口”(或“客户端”)坐标系以发生事件的视口或浏览上下文的左上角为原点。这就是呈现文档的整个视图区域。

例如,在台式电脑上,MouseEvent.clientXMouseEvent.clientY 属性表示事件发生时鼠标光标相对于 window 左上角的位置。使用触控笔或指针时,触摸事件中的 Touch.clientXTouch.clientY 坐标相对于同一原点。

窗口的左上角始终是 (0,0),与文档内容或任何滚动无关。换句话说,滚动文档会改变文档中给定位置的视口坐标。

页面

“页面”坐标系给出了一个像素相对于整个渲染文档左上角的位置。这意味着用户在文档中横向或纵向滚动元素后,除非元素通过布局变化移动,否则文档中元素的某一点将保持相同的坐标。

鼠标事件的 pageXpageY 属性提供了事件发生时鼠标相对于文档左上角的位置。触摸事件中的 Touch.pageXTouch.pageY 坐标相对于同一原点。

屏幕

最后,来介绍“屏幕”模型,原点是用户屏幕空间的左上角。该坐标系中的每个点都代表一个逻辑像素,因此每个坐标轴上的值都以整数递增或递减。如果文档中包含的窗口被移动,或者用户的屏幕几何形状发生变化(通过改变显示分辨率或在系统中添加或删除显示器),文档中给定点的位置就会发生变化。

MouseEvent.screenXMouseEvent.screenY 属性给出了鼠标事件相对于屏幕原点的位置坐标。触摸事件中的 Touch.screenXTouch.screenY 坐标相对于同一原点。

:: 不得不说,MDN 你值得拥有~

基础了解

💡 本章节内容主要摘录自《JavaScript 高级程序设计》的第 12 章的 12.1 和 12.4 小节。建议先阅读一下摘录或对应章节的内容,当然如果你想直接跳过这一章也无不可,在后续章节中我们会进一步总结相关方面的知识点。

下面让我们先来了解一下浏览器中窗口和元素的尺寸大小及位置的基础内容吧。

BOM 的核心是 window 对象,表示浏览器的实例。

window 对象的位置可以通过不同的属性和方法来确定。现代浏览器提供了 screenLeftscreenTop 属性,用于表示窗口相对于屏幕左侧和顶部的位置 ,返回值的单位是 CSS 像素。

在不同浏览器中确定浏览器窗口大小没有想象中那么容易。所有现代浏览器都支持 4 个属性:innerWidthinnerHeightouterWidthouterHeightouterWidthouterHeight 返回浏 览器窗口自身的大小(不管是在最外层 window 上使用,还是在窗格 <frame> 中使用)。innerWidthinnerHeight 返回浏览器窗口中页面视口的大小(不包含浏览器边框和工具栏)。
document.documentElement.clientWidthdocument.documentElement.clientHeight 返回页面视口的宽度和高度。

浏览器窗口自身的精确尺寸不好确定,但可以确定页面视口的大小,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let pageWidth = window.innerWidth,
	pageHeight = window.innerHeight;
	
// 检查 pageWidth 是不是一个数值
if (typeof pageWidth != "number") {
    // 检查页面是否处于标准模式
	if (document.compatMode == "CSS1Compat") {
		pageWidth = document.documentElement.clientWidth;
		pageHeight = document.documentElement.clientHeight;
	} else {
		pageWidth = document.body.clientWidth;
		pageHeight = document.body.clientHeight;
	}
}

在移动设备上, window.innerWidthwindow.innerHeight 返回视口的大小,也就是屏幕上页面可视区域的大小。 Mobile Internet Explorer 支持这些属性,但在 document.documentElement.clientWidthdocument.documentElement.clientHeight 中提供了相同的信息。

在其他移动浏览器中, document.documentElement.clientWidthdocument.documentElement.clientHeight 返回的布局视口的大小,即渲染页面的实际大小。

:: 经测试,这两个属性在移动浏览器上返回的为可见视口大小。页面实际大小放在了 document.body 对象中。

布局视口是相对于可见视口的概念,可见视口只能显示整个页面的一小部分 。 Mobile Internet Explorer 把布局视口的信息保存在 document.body.clientWidthdocument.body.clientHeight 中。

浏览器窗口尺寸通常无法满足完整显示整个页面,为此用户可以通过滚动在有限的视口(可见视口)中查看文档。度量文档相对于视口滚动距离的属性有两对,返回相等的值: window.pageXoffset/window.scrollXwindow.pageYoffset/window.scrollY

window 的另一个属性 screen 对象,是为数不多的几个在编程中很少用的 JavaScript 对象。这个对象中保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度。每个浏览器都会在 screen 对象上暴露不同的属性。

属 性 说 明
availHeight
availLeft
availTop
availWidth
colorDepth
height
left
pixelDepth
top
width
orientation
屏幕像素高度减去系统组件高度(只读)
没有被系统组件占用的屏幕的最左侧像素(只读)
没有被系统组件占用的屏幕的最顶端像素(只读)
屏幕像素宽度减去系统组件宽度(只读)
表示屏幕颜色的位数;多数系统是 32(只读)
屏幕像素高度
当前屏幕左边的像素距离
屏幕的位深(只读)
当前屏幕顶端的像素距离
屏幕像素宽度
返回 Screen Orientation API 中屏幕的朝向

窗口和视口

> 浏览器中的尺寸大小和位置

![[assets/distance2.png]]

如上图所示,我们对所要遇到的窗口和视口进行了简单的标准。其中:

  • S 屏幕窗口
  • B 浏览器窗口
  • P 页面视口
  • L 布局视口
  • E 元素视口

对于浏览器窗口(B 窗口)而言,其窗口大小用 outerWidthouterHeight 来表示,我们称之为浏览器窗口的实际大小。该窗口的位置则由其到屏幕左上角的距离来表示,分别为 screenLeftscreenTop

页面视口 P (不包含浏览器边框和工具栏等,包含滚动条)的大小由 innerWidthinnerHeight 表示。它的位置是由浏览器位置及边框、工具栏等的尺寸计算而来的。

布局视口 L(页面视口 P 除去滚动条)的大小由 document.documentElement.clientWidthdocument.documentElement.clientHeight 决定。确切地说,在桌面端使用“可见视口”好一些,因为在移动端布局视口指的是实际渲染页面的大小(由 document.body.clientWidthdocument.body.clientHeight 表示)。

💡 布局视口是相对于可见视口的概念,可见视口只能显示整个页面的一小部分 。

文档内容往往不能在可见视口中完整地展示出来,这里就需要我们滚动页面。浏览器允许文档在可见视口的 X 轴和 Y 轴方向滚动。其相对于视口滚动距离的属性有两对,返回相等的值: window.pageXoffset /window.scrollXwindow.pageYoffset / window.scrollY

关于元素视口,即页面中的 HTMLElement 大小及位置,比较重要,我们放在下个章节单独说明。

元素视口

如图所示,E 视口即粉色条框是一个描述 GitHub 仓库的项目框。

HTMLElement 接口表示所有的 HTML 元素。一些 HTML 元素直接实现了 HTMLElement 接口,其他的间接实现 HTMLElement 接口。详见 HTMLElement - Web API | MDN

offsetWidth 和 offsetHeight

![[assets/Pasted image 20240510085915.png]]

HTMLElement.offsetWidth 是一个只读属性,返回一个元素的布局宽度。。一个典型的(译者注:各浏览器的 offsetWidth 可能有所不同)offsetWidth 是测量包含元素的边框 (border)、水平线上的内边距 (padding)、竖直方向滚动条 (scrollbar)(如果存在的话)、以及 CSS 设置的宽度 (width) 的值。

HTMLElement.offsetHeight 是一个只读属性,它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。通常,元素的 offsetHeight 是一种元素 CSS 高度的衡量标准,包括元素的边框、内边距和元素的水平滚动条(如果存在且渲染的话),不包含 :before:after 等伪类元素的高度。

clientWidth 和 clientHeight

![[assets/Pasted image 20240510090654.png]]

只读属性 Element.clientWidth 对于内联元素以及没有 CSS 样式的元素为 0;否则,它是元素内部的宽度(以像素为单位)。该属性包括内边距(padding),但不包括边框(border)、外边距(margin)和垂直滚动条(如果存在)。

只读属性 Element.clientHeight 对于没有定义 CSS 或者内联布局盒子的元素为 0;否则,它是元素内部的高度(以像素为单位),包含内边距,但不包括边框、外边距和水平滚动条(如果存在)。

clientHeight 可以通过 CSS height + CSS padding - 水平滚动条高度(如果存在)来计算。

scrollWidth 和 scrollHeight

Element.scrollWidth 这个只读属性是元素内容宽度的一种度量,包括由于 overflow 溢出而在屏幕上不可见的内容。

![[assets/Pasted image 20240510091057.png]]

scrollWidth 值等于元素在不使用水平滚动条的情况下适合视口中的所有内容所需的最小宽度。宽度的测量方式与 clientWidth 相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条(如果存在)。它还可以包括伪元素的宽度,例如 ::before::after。如果元素的内容可以适合而不需要水平滚动条,则其 scrollWidth 等于 clientWidth

Element.scrollHeight 只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。

scrollHeight 的值等于该元素在不使用滚动条的情况下为了适应视口中所用内容所需的最小高度。高度的度量方式与 clientHeight 相同:包括元素的内边距,但不包括元素的边框、外边距以及水平滚动条(如果存在)。它也包括 ::before::after 这样的伪元素的高度。如果元素的内容不需要垂直滚动条就可以容纳,则其 scrollHeight 等于 clientHeight

offsetLeft 和 offsetTop

HTMLElement.offsetLeft 是一个只读属性,返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值。

HTMLElement.offsetParent 是一个只读属性,返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table, td, th, body 元素。当元素的 style.display 设置为 “none” 时,offsetParent 返回 nulloffsetParent 很有用,因为 offsetTopoffsetLeft 都是相对于其内边距边界的。

HTMLElement.offsetTop 为只读属性,它返回当前元素相对于其 offsetParent 元素的顶部内边距的距离。

clientLeft 和 clientTop

clientLeft 表示一个元素的左边框的宽度,以像素表示。如果元素的文本方向是从右向左(RTL, right-to-left),并且由于内容溢出导致左边出现了一个垂直滚动条,则该属性包括滚动条的宽度。clientLeft 不包括左外边距和左内边距。clientLeft 是只读的。

clientTop 一个元素顶部边框的宽度(以像素表示)。不包括顶部外边距或内边距。clientTop 是只读的。

scrollLeft 和 scrollTop

Element.scrollLeft 属性可以读取或设置元素滚动条到元素左边的距离。

注意如果这个元素的内容排列方向(direction)是 rtl (right-to-left) ,那么滚动条会位于最右侧(内容开始处),并且 scrollLeft 值为 0。此时,当你从右到左拖动滚动条时,scrollLeft 会从 0 变为负数。

Element.scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。

一个元素的 scrollTop 值是这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为 0

这个 Element.scrollLeftMax 是只读的属性返回一个 Number 表示一个元素横向滚动条可滚动的最大距离。Element.scrollTopMax 返回一个只读 Number 表示元素所能滚动的最大高度。这两个属性还没纳入规范。

事件中的位置

MouseEvent

当定点设备的按钮(通常是鼠标的主键)在一个元素上被按下和放开时,click 事件就会被触发。

如果在一个元素上按下按钮,而将指针移到元素外再释放按钮,则在包含这两个元素的最具体的父级元素上触发事件。

click 事件会在 mousedownmouseup 事件依次触发后触发。

mousedown 事件在定点设备(如鼠标或触摸板)按钮在元素内按下时,会在该元素上触发。

mouseup 事件在定点设备(如鼠标或触摸板)按钮在元素内释放时,在该元素上触发。其与 mousedown 事件相对应。

mouseenter 事件在定点设备(通常指鼠标)首次移动到元素的激活区域内时,在该元素上触发。

mouseleave 事件在定点设备(通常是鼠标)的指针移出某个元素时被触发。

mouseout 事件在定点设备(通常是鼠标)移动至元素或其子元素之外时,会在该元素上触发。当指针从一个元素移入其子元素时,因为子元素遮盖了父元素的可视区域,所以 mouseout 也会被触发。

当一个定点设备(通常指鼠标)在一个元素本身或者其子元素上移动时,mouseover 事件在该元素上触发。

mousemove 事件在定点设备(通常指鼠标)的光标在元素内移动时,会在该元素上触发。

以上事件,都是 MouseEvent ,即鼠标事件。其涉及到的与光标点位置有关的属性如下:

属性 说明
MouseEvent.screenX 鼠标指针相对于屏幕的 X 轴坐标。
MouseEvent.screenY 鼠标指针相对于屏幕的 Y 轴坐标。
MouseEvent.offsetX 鼠标指针相对于目标节点的内填充边的 X 轴坐标。
MouseEvent.offsetY 鼠标指针相对于目标节点的内填充边的 Y 轴坐标。
MouseEvent.pageX 鼠标指针相对于整个文档的 X 轴坐标。
MouseEvent.pageY 鼠标指针相对于整个文档的 Y 轴坐标。
MouseEvent.clientX
MouseEvent.x
鼠标指针相对于局部 DOM 元素的 X 轴坐标。
MouseEvent.clientY
MouseEvent.y
鼠标指针相对于局部 DOM 元素的 Y 轴坐标。
MouseEvent.movementX 鼠标指针相对于最后一次 mousemove事件位置的 X 轴坐标。
MouseEvent.movementY 鼠标指针相对于最后一次 mousemove事件位置的 Y 轴坐标。

TouchEvent

touchstart 事件在一个或多个触点与触控设备表面接触时被触发。

touchend 事件在一个或多个触点从触控平面上移开时触发。注意,也有可能触发 touchcancel 事件。

touchcancel 事件在触点被中断时触发,中断方式基于特定实现而有所不同(例如,创建了太多的触点)。

touchmove 事件在触点于触控平面上移动时触发。

以上事件,都是 TouchEvent ,即鼠标事件。其涉及到的与触摸点位置有关的属性如下:

属性 描述
TouchEvent.touches 一个包含所有的 Touch对象的 TouchList,这些 Touch 对象表示当前与表面接触的触点(不论事件目标或状态变化)。
TouchEvent.targetTouches 一个包含所有的 Touch对象的 TouchList,这些 Touch 对象表示当前与触摸表面接触的触点,且触点起始于事件发生的目标元素。
TouchEvent.changedTouches 一个包含所有的 Touch对象的 TouchList,这些 Touch 对象表示在前一个 touch 事件和当前的事件之间,状态发生变化的独立触点。
这里我们附上一个 Touch 对象的实例,如下:
1. clientX: 177.60000610351562
2. clientY: 118.4000015258789
3. force: 1
4. identifier: 0
5. pageX: 177.60000610351562
6. pageY: 118.4000015258789
7. radiusX: 18.399999618530273
8. radiusY: 18.399999618530273
9. rotationAngle: 0
10. screenX: 410
11. screenY: 235

scroll 事件

当用户滚动某个元素的内容时 scroll 事件将会被触发。Element.onscroll 同等于 element.addEventListener("scroll" ... )

其中 e.target.scrollTope.target.scrollLeft 为滚动过程中元素的滚动位移。

我要说的是…

是不是已经晕乎乎了?放心,只需要适当的练习就可以掌握它们了。认真观察一下,不难发现,有几个词汇是经常出现的,如 screen、page、client、offset、scroll 等。弄清了它们几个的区别,基本上就可以着手开发一些你想要的功能了。

与 screen 相关的属性,顾名思义就是与显示器窗口有关的。如 window.screen.widthwindow.screen.height 表示的就是显示器屏幕的宽、高。

后面元素事件中的 screenXscreenY 便是触发事件的光标点距离显示器屏幕的 X 轴、Y 轴坐标了。

💡 如无特殊说明,一般默认左上角为坐标原点 (0, 0)

类似,与 page 有关的是与实际文档大小有关。与 client 有关的是可见窗口区域。与 offset 相关的一般是偏移距离。与 scroll 有关的一般是滚动距离。

在实际开发过程中, 我们一般处理与元素事件相关的位置,多数又是与包含可定位元素的低级元素的相对位置关系。故一般使用与 offset 相关的属性的场合多比较多。

此类内容,还是需要在实际的练习中慢慢掌握。建议初学者,只需要在 Chrome 中练习即可,不用考虑浏览器的兼容性。

实例分析

……