最近遇到一个需求,当鼠标 hover 一个 icon 的时候展示一张图片,因为项目时基于 antd 进行开发的立马想到了 Popover 组件,写下了如下代码:
<Popover content={<img src="https://wtniu.xyz/res/pic.png" alt="" />}>
<span>hover me</span>
</Popover>
猛一看没啥毛病,奈何现实无情打脸,真实效果如下(CSDN不支持上传视频,所有效果都用截图替代):
图片并没有出现在理想的位置,而是偏移到下方,为啥出现这个情况,官网没写,度娘和谷歌也没给答案,留给我的只有一条路,扒源码。
下面是 popover 的源码,关键在 React.createElement,插一句,Babel 其实就是把 JSX 转译成一个名为 React.createElement()
函数调用,对这个方法不明白的可以参考官网解释,分析后可以看到,这货基本上是个马甲,真正的逻辑都交给 Tooltip 去实现了。
图1 Popover 源码
**
看到这,我意识到那 Tooltip 会不会也有这个问题,于是我写下如下代码:
<Tooltip title={<img src="https://wtniu.xyz/res/pic.png" alt="" />}>
<span>hover me</span>
</Tooltip>
果然,结果印证了我的想法。
这里图片超出是因为 Tooltip 设置了最大宽度的缘故。
那么接下来要做的就是看下 Tooltip 的源码:
图2 Tooltip 源码
**
看过之后发现 Tooltip 依旧是个马甲,依赖的是 RcTooltip,于是马不停蹄,继续扒 RcTooltip 的源码。
图3 rc-tooltip 源码
**
没想到还是马甲,意不意外,啥都不说了继续扒。
** 图4 rc-trigger 源码**
**
这次不是马甲了,作者洋洋洒洒写了近千行的代码,我直接截取其中的一段,这里第一行代码很关键,React.cloneElement 中第一个参数 child 就是 Tooltip组件包裹的内容,也就是例子中的 hover me,newChildProps 就是给这个组价添加的各种属性, 比如点击事件和hover事件等,通过分析找到hover 事件其实触发的是 onMouseEnter
图5 rc-trigger 源码
**
而这个方法又调用了 delaySetPopupVisible
** 图6 rc-trigger 源码**
**
delaySetPopupVisible 又调用了 setPopupVisible
图7 rc-trigger 源码
**
而这个组件的作用是控制组件的显示和隐藏,如果 event 事件存在,将 event 传递进去。
通过 debugger 发现 alignPoint event 都不存在,setPoint 这里不会执行,真正执行的是 onPopupVisibleChange,而这个函数是通过 props 获取,也就是说它是在父组件上实现的
图9 rc-tooltip 源码
**
源码显示 onPopupVisibleChange 其实是 onVisibleChange,而 onVisibleChange 也是父组件传递的,所以绕了一大圈又回到了 tooltip 组件,而 tooltip 组件上的 onVisibleChange 其实就是官方文档中的 API,从这里也可以看到,这个 API 通过props 不断向下传递,最终在 rc-trigger 的 setPopupVisible 中执行,但是这里我们没有传入 onVisibleChange 属性,此路不通,所以我们回到 **图4 rc-trigger 源码,**继续分析,这里有两部分组成,一个是我们刚刚分析过的 trigger,还有一份是 portal,那么这个 portal 是什么呢?
图10 rc-trigger 源码
**
其实就是我们需要显示的部分,源码中的 this.getComponent() 就是我们需要显示的图片,并用 Portal 进行包裹,Portal 源自 rc-util 我们先不管,继续分析 getComponent 方法
图11 rc-trigger 源码
**
发现逻辑又指向了 Popup,我们继续分析 Popup
图12 rc-trigger Popup 源码
**
这里有两个 React.createElement,分别对应了两个组件,其中 Align 对应的 是 rc-align,PopupInner 对应的是 PopupInner.js ,我们先分析 PopupInner 源码代码如下:
这个组价很简单,就是将传入的 children 外层添加一个 div,并给这个 div 添加一些属性,比如 className,visible 等属性,所以关键点还是在 rc-align 里面
这里关键点有两个,一个是 55 行的判断,一个是 64 行的 onAlign 方法,我们一个一个分析,首先是 55 行 判断后有两个方法 alignElement, alignPoint,这两个方法都是通过 dom-align 组件获取的,源码就不贴了,这里说下这两个方法是含义,如果 element 存在,那么依赖传入元素的宽高来设置组件显示的位置,否则依赖触发事件上面的 pageX, pageY 来设置。另外一个 onAlign 则是通过 props 传递进来的
图15 rc-trigger Popup 源码
这里的 popupDomNode 就是要显示的元素,而 align 是显示的方位,通过 debugger 我们看到,当组件内传入图片的时候包裹图片的元素已经显示,但是图片还没有加载,导致父组件塌陷,宽和高都变成了最小值
图16 rc-trigger Popup
通过对 dom-align 源码分析,可以看到元素在参与计算的时候使用的是图片未加载之前的宽高和位置,此时宽高是塌陷的,所以经过计算的位置也是错误,dom-align 中其实存在一个修正位置的算法,但奈何不住输入一个错误的值。
图15 dom-align getRegion 源码
这个组件还要一个诡异的点,就是第二次 hover 的时候显示的位置是正确的,通过 debugger,我们发现第二次显示的位置仍然是错误的,但是此时图片已经显示,和第一次的区别在于宽高塌陷的问题没有了,放开 debugger 后位置自动修改到正确位置。
通过以上分析,我们可以得出结论了,图片位置显示错误是宽高塌陷所致,那么我们给图片手动设置一个宽高,或者给图片包裹到一个固定宽高的div中再传入到组件中,问题不就可以解决了,抱着这个想法,修改代码如下:
<Popover content={<img style={{ width: 300, height: 223 }} src="https://wtniu.xyz/res/pic.png" alt="" />}>
<span>hover me</span>
</Popover>
再次刷新后 hover 问题修复。