对这个系列感兴趣的可以关注订阅专栏:从零开始打造一个低代码平台
前言
组件的属性里会有位置的信息,例如left
top
,很多鼠标事件也都会带位置信息,例如 MouseEvent,DragEvent里的clientX
和clientY
等。left
/top
跟clientX
/clientY
是不同的坐标系。前者是相对父元素,后者是相对可视窗口。这一章,我们介绍一下前端几种位置坐标,以及如何使用它们。
一、几种位置坐标
一、clientX、clientY
相对于当前窗口可视区域的x,y坐标,MouseEvent
DragEvent
事件里的clientX
clientY
就是这个坐标系里的坐标,还有DOM组件接口getBoundingClientRect
返回的也是这个坐标系下的位置。
二、pageX、pageY
相对于整个页面来说,包括了被卷去的body部分的长度。
三、screenX、screenY
相对于电脑屏幕的x,y坐标。
四、offsetX、offsetY
相对于带有定位的父盒子的x,y坐标。offsetX
相对于style.left
,offsetY
相当于style.top
,两个差异:
- 前者是数值型,例如
100
;后者是字符串,例如100px
- 前者是只读的,后者可修改
这几种坐标中最常用的是clientX
clientY
,和offsetX
offsetY
style.left
style.top
。
二、坐标转换
基于上一章,我们拿到DragEvent
事件里的坐标clientX
clientY
,但我们给组件设置的位置属性是style.left
style.top
,之间怎么转换呢?
left
top
是相对于父元素的,所以我们将元素的left
= clientX
- parent.clientX
,只要算出parent.clientX
就可以得到元素的left
。
2.1 得到元素的父节点元素
function getParentElement(element: HtmlElement) {
return element.parentElement;
}
2.2 得到父节点元素的clientX clientY
const { left: parentClientX, top: parentClientY } = parentElement.getBoundingClientRect();
2.3 得到元素的 left & top
const left = clientX - parentClientX;
const top = clientY - parentClientY;
三、解决组件拖拽的位置偏差
基于以上,我们现在可以解决上一章里组件拖拽时的位置偏差问题了。
改造完的Canvas
如下:
import { useState } from "react";
interface CanvasProps {
className?: string;
}
export const Canvas: React.FC<CanvasProps> = ({ className }) => {
const [widgets, setWidgets] = useState<
{ type: string; left: number; top: number; width: number; height: number }[]
>([]);
return (
<div
className={className ?? ""}
onDragOver={(e) => {
e.preventDefault();
}}
onDrop={(e) => {
const { clientX, clientY, target } = e;
const targetElement = target as HTMLElement;
const rect = targetElement.parentElement?.getBoundingClientRect();
const width = 100;
const height = 50;
const left = clientX - (rect?.left ?? 0) - width / 2;
const top = clientY - (rect?.top?? 0) - height / 2;
setWidgets([
...widgets,
{ type: "btn", left, top, width, height },
]);
}}
>
{widgets.map((widget, index) => {
return (
<div
key={index}
className="absolute bg-gray-700 rounded-md flex justify-center items-center"
style={{
left: `${widget.left}px`,
top: `${widget.top}px`,
width: `${widget.width}px`,
height: `${widget.height}px`,
}}
>
<i>Button</i>
</div>
);
})}
</div>
);
};
现在拖拽生成的组件就跟鼠标位置一致了。同时,我们还做了一点点调整,让组件的位置偏移[width/2, height/2]
,这样让鼠标的位置刚好在组件的中心,更符合用户习惯。
总结
这一章我们介绍了前端的几种坐标系,和最常用的两种坐标之间如何转换,基于此我们解决了上一章遗留的组件位置偏差问题。在后续的章节中,我们还会大量地用到这两种坐标和对应的转换,来解决组件移动和缩放等常用操作,所以记住这几种坐标吧。