头图

image.png
在页面布局中,我们通常会给指定区域做限制,若元素超出了指定区域则会隐藏(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);
};
  1. rect.left 和 rect.top 是节点相对于视口(浏览器可视区域)的坐标。
  2. 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()


兔子先森
360 声望14 粉丝

致力于新技术的推广与优秀技术的普及。