在页面布局中,我们通常会给指定区域做限制,若元素超出了指定区域则会隐藏(overflow: hidden;
)。
而文字提示框组件却能精准定位指定元素,并不受overflow: hidden;
元素的影响。
这是因为文字提示框组件是直接挂载在body
下的,所以它不受页面布局的影响,其它的弹窗组件也是类似的原理,例如:modal
组件、对话框组件、mask
组件、toast
组件等。
要实现提示框组件,有两个核心点需要注意:
1、元素定位的位置
2、挂载在页面顶层(body下)
元素定位的位置
在上图中,点击用户头像,提示框可以精准定位到头像的中心位置并打开,其实是通过dom
操作获取元素在窗口的位置和元素的大小
<div id="user-avatar">
<img @click="onUser" />
</div>
const UseMaskRef = ref();
const onUser = () => {
// 获取元素dom
let element: any = document.querySelector("#user-avatar");
// 获取元素的大小及其相对于视口的位置
// https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getBoundingClientRect
let rect = element.getBoundingClientRect();
// 获取 x 和 y 坐标
let x = rect.left + window.scrollX;
let y = rect.top + window.scrollY;
let w = rect.width;
let h = rect.height;
// 获取当前dom在视口的偏移量,这里计算中心点
// dom的宽度&高 / 2 + 偏移量
let offsetX = w / 2 + x;
let offsetY = h / 2 + y;
let params = {
offsetX,
offsetY
};
// 打开mask组件
UseMaskRef.value.open(params);
};
- rect.left 和 rect.top 是节点相对于视口(浏览器可视区域)的坐标。
- window.scrollX 和 window.scrollY 是当前页面的水平和垂直滚动量。这些值加上视口坐标可以得到节点相对于整个页面的坐标。
挂载在页面顶层(body下)
在这里我们要做一个重要的步骤,将提示框挂载到顶层
vue3中有个标签:Teleport
该标签可以模板内容“传送”到页面的指定位置,而不受常规的组件渲染树的限制。
<Teleport to="body">
<div class="use-mask">
<!-- 其它自定义内容 -->
<div @click="onCloseMask">关闭提示框</div>
</div>
</Teleport>
.use-mask {
box-sizing: border-box;
width: 460px;
height: 388px;
background: red;
position: absolute; // 默认开启绝对定位
display: none; // 默认隐藏
}
在这一步我们将组件挂载到body中,然后设置元素定位
interface IParams {
offsetX: number;
offsetY: number;
}
// 打开提示框
const open = (params: IParams) => {
ListenDom(1, params.offsetX, params.offsetY);
};
// 主要逻辑
const ListenDom = (type: number, offsetX?: number, offsetY?: number) => {
// type: 1打开 0关闭
// 选择元素
let element: any = document.querySelector(".use-mask");
// 如果是打开对话框
if (type == 1) {
// 根据偏移量设置定位位置
element.style.left = `${offsetX}px`;
element.style.top = `${offsetY}px`;
// 显示提示框
element.style.display = `block`;
} else {
// 此时type为0
// 关闭或者点击关闭按钮,隐藏对话框或者利用dom操作删除对话框dom节点
element.style.display = `none`;
}
// 点击事件处理函数,这一步主要处理对话框关闭和解绑
function handleClickOutside(event: any) {
// 检查点击是否在指定元素之外,如果点击提示框之外的位置,关闭提示框
if (!element.contains(event.target)) {
// 隐藏提示框或者利用dom操作删除对话框dom节点
element.style.display = `none`;
// 解绑监听
document.removeEventListener("mousedown", handleClickOutside);
}
}
// 在整个文档上监听点击事件
if (type == 1) {
// 打开提示框,绑定事件处理函数
document.addEventListener("mousedown", handleClickOutside);
} else {
// 移除事件监听
document.removeEventListener("mousedown", handleClickOutside);
}
};
// 关闭提示框
const onCloseMask = () => {
ListenDom(0);
};
到这里我们就是实现了将mask提示框组件的渲染和关闭。
参考文档:Teleport
vue3-Teleport
getBoundingClientRect
Node.contains()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。