前端頁面中的tooltip一直是被廣泛應用的功能,tooltip本身的dom結構萬變不離其宗,最大的問題可能就是那個氣泡框下面的三角形的渲染,但是對於老手來說,解決這種小問題和喫飯喝水一樣簡單。
// ant中tooltip的使用方法
import { Tooltip, Button } from 'antd';
ReactDOM.render(
<div>
<Tooltip placement="topLeft" title="Prompt Text">
<Button>Align edge / 邊緣對齊</Button>
</Tooltip>
<Tooltip placement="topLeft" title="Prompt Text" arrowPointAtCenter>
<Button>Arrow points to center / 箭頭指向中心</Button>
</Tooltip>
</div>,
mountNode,
);
自從我開始使用react和antd之後,我一直很好奇antd裏面的tooltip是怎麼封裝出來的,後來的工作中因爲機緣巧合的關係,一直都沒時間仔細研究一下,今天稍微花了點時間,實現和antd裏面差不多的使用效果。
其實實現tooltip並不是什麼很困難的事情,困難的是如何將其優雅地封裝起來,如同ant中的一樣,使用Tooltip模塊將需要觸發的實體包裹起來,對於使用者來說是最直觀,最方便的使用方法。而且ant的tooltip的實現中,並不會侵入頁面的原始結構。
技術難點:
1. 觸發事件的注入
想要在react中實現,不入侵頁面的結構的tooltip,就必須要將觸發事件直接注入到props.children中,而不是在外面再包一層dom,再包一層dom會影響到css樣式的編寫。所以需要用到Rreact.cloneElement的Api
return (
<>
{React.Children.map(props.children, (child: any) => {
return React.cloneElement(child, {
className: child.props.className + ' tooltip-wrap-content',
onMouseEnter: mouseIn,
onMouseLeave: mouseOut
});
})}
</>
);
這個api可以在渲染子節點的時候主動注入自己想要傳遞給子節點的參數,這樣就可以將觸發事件注入進去了。當然不能直接覆蓋子節點的事件,需要做一下代理,這裏是簡單寫了。
2. tooltip的結構
這個其實是小問題,tooltip的結構可以分爲兩個問題,一個是tooltip應該渲染到哪裏,一個是tooltip本身的dom結構的渲染。
在React v16中有一個createPortal的Api,可以將react節點渲染至任意dom(包括react節點)下,用這個方法的話,我們就可以避免將tooltip渲染到實體的附近了,能最大程度避免污染到原始結構。
由於採用了觸發實體與tooltip渲染位置分離的結構,所以就不能直接用絕對定位來確定tooltip在窗口裏的position了,需要使用fixed的定位方式。同時計算觸發實體距離窗口邊界的距離,直接根據窗口定位。雖然不是很有意義,但是還是將如何計算實體距離窗口邊界代碼貼出來好了,畢竟也曾經困擾過我一段時間:
function getOffset(dom) {
let parent: any = dom;
let left: number = 0;
let top: number = 0;
while (parent) {
left += parent.offsetLeft;
top += parent.offsetTop;
parent = parent.offsetParent;
}
return {
left, top,
width: dom.offsetWidth,
height: dom.offsetHeight
};
}
只要搞清楚offsetParent,offsetLeft,offsetTop,offsetWidth,offsetHeight代表的意義就不難看懂上面的代碼了。