需求描述:
在如下甘特图的蓝色活动条形区域,鼠标 hover 上后显示详情 tooltip,鼠标离开条形区域后只要不进入 tooltip 则消失
分析需求
1、container 容器与侧边人员容器处会发生明显遮挡问题,因此为了解决层级问题,此处使用 portal 的方式,将 tooltip 组件 portal 外层容器,避免遮挡的发生。
2、由于 trigger 与tooltip 不存在父子关系,则选择 mouse 事件控制 tooltip 的现实与隐藏。
3、tooltip 中具体内容需要网络请求得到。
我能想到的问题及解决方法
1、当 trigger 发生
mouseleave事件时,tooltip 在此种情况下不消失:当 tooltip 发生mouseenter时,该 tooltip 将继续存在。
解决方案1
方案1的问题:当快速移入移出同一个 trigger 时,或两个 trigger 间距极小(本例中为 1px)鼠标在两个元素间移动时,tooltip 的显示都会异常。
正常情况应该如下图所示。
只要 trigger 与 tooltip 是无缝连接的,因此 trigger 的 leave 事件与 tooltip 的 enter 事件是无时间差发生setTimeout(fn, 0)便可完成监听(实际浏览器执行 fn 回调的时间差为四毫秒)。
因此问题出在 trigger enter 触发的网络请求时间差上。
解决方案2
判断 mouseleave 的方向,如果从 trigger 下方离开则保留 tooltip 组件。
很明显,解决方案2 有明显的问题,被 portal 到外部的tooltip 会根据 trigger 来计算最终位置,当 trigger 的四个方向有任何遮挡时,则会将 tooltip portal 到不会被遮挡的位置;这就说明,tooltip 会出现在 trigger 的上下两个方向。
思路
来分析一下为什么会有这个显示问题:首先 trigger 的 enter 发生后触发 ajax 请求。在请求未结束前离开元素,过后请求结束。
那么就有一个 ajax 开始到结束的时间差引发的显示问题。
解决方法
如果不能用 leave 事件置 flag 来解决显示问题,那么:
换个思路考虑,如何保证,鼠标不聚焦在 trigger 上,tooltip 一定会消失呢?
由于我的 dom 结构如下:trigger 3,4 在容器 2 中,容器 1 为左侧人员显示区域,及其他相邻区域。
那么除了 trigger 之外,在其他区域均添加 mousemove 监听事件,当鼠标在 trigger 之外的区域 move 时,则控制 tooltip 显示框消失。
写在最后
其实这并不是一个最优方式,外部元素与一个小小的 tooltip 组件发生耦合,并且耗费浏览器性能监听 mouse 事件。但整个问题从出现到尝试解决的过程倒是比较有趣。有时候换个思路考虑问题,outside the box 也许能够更讨巧。
最后的最后
好奇怎么判断鼠标移出方向吗?一个小提示:可以用对角线斜率与移出点与中心点连线的斜率判断,代码如下:
// 0: up / right: 1 / 2: down / 3: left
const mouseLeaveDirection = (e: any) => {
const rect = e.target.getBoundingClientRect();
if (!rect.width) {
rect.width = rect.right - rect.left;
}
if (!rect.height) {
rect.height = rect.bottom - rect.top;
}
// 求各个点坐标
const x1 = rect.left;
const y1 = -rect.top;
const x4 = rect.left + rect.width;
const y4 = -(rect.top + rect.height);
const x0 = rect.left + rect.width / 2;
const y0 = -(rect.top + rect.height / 2);
// 计算对角线斜率
const k = (y1 - y4) / (x1 - x4);
const range = [k, -k];
const x = e.clientX;
const y = -e.clientY;
const kk = (y - y0) / (x - x0);
if (isFinite(kk) && range[0] < kk && kk < range[1]) {
return x > x0 ? 1 : 3;
} else {
return y > y0 ? 0 : 2;
}
};