网页中的距离
当页面超过视口高度的时候,右侧会出现滚动条,代表当前可视区域的位置。
窗口部分也就是视口,viewport。
如果我们点击了可视区域内的一个元素,如何拿到位置信息呢?
我们只看 y 轴方向好了,x 轴也是一样的。
事件对象可以拿到 pageY
、clientY
、offsetY
,分别代表到点击的位置到文档顶部,到可视区域顶部,到触发事件的元素顶部的距离。
还有个 screenY
,是拿到到屏幕顶部的距离。
代码测验
import { MouseEventHandler, useEffect, useRef } from "react";
function App() {
const ref = useRef<HTMLDivElement>(null);
const clickHandler: MouseEventHandler<HTMLDivElement> = (e) => {
console.log("box pageY", e.pageY);
console.log("box clientY", e.clientY);
// React事件是合成事件,会少一些原生事件的属性,所以这里拿不到e.offsetY
console.log("box offsetY", e.offsetY);
console.log("box screenY", e.screenY);
};
useEffect(() => {
document.getElementById("box")!.addEventListener("click", (e) => {
console.log("box2 pageY", e.pageY);
console.log("box2 clientY", e.clientY);
console.log("box2 offsetY", e.offsetY);
console.log("box2 screenY", e.screenY);
});
}, []);
return (
<div>
<div
id="box"
ref={ref}
style={{
marginTop: "800px",
width: "100px",
height: "100px",
background: "blue",
}}
onClick={clickHandler}
></div>
</div>
);
}
export default App;
那么我们在 React 事件中如何获取 offsetY
呢?
看一幅图就知道啦!
我们可以使用 pageY - window.scrollY - getBoundingClientRect().top
计算出 offsetY
。
这里的 getBoundingClientRect
是返回元素距离可以可视区域的距离和宽高的:
提示
element.getBoundingClientRect:拿到 width、height、top、left 属性,其中 top、left 是元素距离可视区域的距离,
width、height 绝大多数情况下等同 offsetHeight、offsetWidth,但旋转之后就不一样了,拿到的是包围盒的宽高
当然,你也可以访问原生事件对象,拿到 offsetY 属性:
const clickHandler: MouseEventHandler<HTMLDivElement> = (e) => {
console.log("box pageY", e.pageY);
console.log("box clientY", e.clientY);
// 访问原生事件对象
console.log("box offsetY", e.nativeEvent.offsetY);
console.log("box screenY", e.screenY);
};
此外,窗口的滚动距离用 window.scrollY
获取,那元素也有滚动条呢?
元素内容的滚动距离用 element.scrollTop
获取。
此外,元素还有 offsetTop
和 clientTop
属性:
import { useEffect, useRef } from "react";
function App() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
console.log("offsetTop", ref.current?.offsetTop);
console.log("clientTop", ref.current?.clientTop);
}, []);
return (
<div>
<div
style={{
position: "relative",
margin: "100px",
padding: "200px",
border: "1px solid blue",
}}
>
<div
id="box"
ref={ref}
style={{
border: "20px solid #000",
width: "100px",
height: "100px",
background: "pink",
}}
></div>
</div>
</div>
);
}
export default App;
可以看到,clientTop
也就是上边框的高度 20px。
offsetTop
是距离最近的有 position 属性(relative 或 absolute 或 fixed)的元素的距离。
所以是 200px。
注释掉就是 301px 了,这时候就是相对于文档顶部,所以是 200px padding+ 1px border + 100px margin。
offsetTop
相对于哪个元素,那个元素就是 offsetParent
。
还可以递归累加到 offsetParent
的 offsetTop
,直到 offsetParent
为 null
,也就是到了根元素,这时候算出来的就是元素到根元素的 offsetTop
:
import { useEffect, useRef } from "react";
function App() {
const ref = useRef<HTMLDivElement>(null);
function getTotalOffsetTop(element: HTMLElement) {
let totalOffsetTop = 0;
while (element) {
totalOffsetTop += element.offsetTop;
element = element.offsetParent as HTMLElement;
}
return totalOffsetTop;
}
useEffect(() => {
console.log("offsetTop", ref.current?.offsetTop);
console.log("clientTop", ref.current?.clientTop);
console.log("totol offsetTop", getTotalOffsetTop(ref.current!));
}, []);
return (
<div>
<div
style={{
position: "relative",
margin: "100px",
padding: "200px",
border: "1px solid blue",
}}
>
<div
id="box"
ref={ref}
style={{
border: "20px solid #000",
width: "100px",
height: "100px",
background: "pink",
}}
></div>
</div>
</div>
);
}
export default App;
但是你会发现它少计算了 border 的宽度:
因为 offsetTop 元素顶部到 offsetParent 内容部分的距离,不包括 border。
这时候加上 clientTop 就可以了,它就是上边框的高度。
function getTotalOffsetTop(element: HTMLElement) {
let totalOffsetTop = 0;
while (element) {
// 最初的元素不用加,因为是算他到别的元素的距离
if (totalOffsetTop > 0) {
totalOffsetTop += element.clientTop;
}
totalOffsetTop += element.offsetTop;
element = element.offsetParent as HTMLElement;
}
return totalOffsetTop;
}
clientHeight
:内容高度,不包括边框
offsetHeight
:包含边框的高度
scrollHeight
:滚动区域的高度,不包括边框
window.innerHeight
:窗口的高度