antd 中 Tooltip 和 Popover 組件傳圖錯位
最近遇到一個需求,當鼠標 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 獲取,也就是說它是在父組件上實現的
圖8
**
我們回到其父組件 rc-tooltip 中尋找
圖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 源碼代碼如下:
圖13 rc-trigger PopupInner 源碼
這個組價很簡單,就是將傳入的 children 外層添加一個 div,並給這個 div 添加一些屬性,比如 className,visible 等屬性,所以關鍵點還是在 rc-align 裏面
圖14 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 問題修復。