- 一、创建btn和tooltip组件
- 二、获取当前btn的几何属性和全局定位属性
- 三、tooltip需要得到btn的几何属性和全局定位属性
- 四、tooltip根据btn的位子几何信息进行定位(当滚轮滑动时重新定位tooltip)
- 五、创建点击事件、点击btn反转display状态、点击不发生在tooltip中 隐藏tooltip
const domContainer = document.querySelector('#app');
const body = document.querySelector('body');
const useState = React.useState;
const useEffect = React.useEffect;
const useRef = React.useRef;
const forwardRef = React.forwardRef;
const useImperativeHandle = React.useImperativeHandle;
const useLayoutEffect = React.useLayoutEffect;
function RCTooltip(props) {
return (
<div>
{props.message}
</div>
)
}
function findClassNameFromParent(e, className) {
let i = true;
let parentNode = e.target.parentNode || null;
let res = false;
while (i) {
if (parentNode) {
if (parentNode.className && parentNode.className.includes(className)) {
res = true;
i = false
} else {
parentNode = parentNode.parentNode;
}
} else {
res = false;
i = false;
}
}
return res
}
function TooltipWrap(props) {
const [isShow, setIsShow] = useState(undefined);
useEffect(() => {
setIsShow(props.isOpen)
console.log('change isOpen', props.isOpen)
}, [props.isOpen]);
useEffect(() => {
const bodyClick = (e) => {
if (!(e.target.className.includes('Tooltip') || findClassNameFromParent(e, 'Tooltip'))) {
setIsShow(false)
props.onVisibleChange && props.onVisibleChange(false)
}
}
window.document.addEventListener('click', bodyClick)
return () => { window.document.removeEventListener('click', bodyClick) }
}, []);
useEffect(() => {
if (isShow === undefined) {
setIsShow(props.defaultDisplayStatus);
props.onVisibleChange && props.onVisibleChange(props.defaultDisplayStatus)
return
}
setIsShow(!isShow)
props.onVisibleChange && props.onVisibleChange(!isShow)
}, [props.change]);
return (
<div
className={`Tooltip ${isShow ? 'show' : 'hidden'}`}
ref={props.RCTooltipRef}
>
<RCTooltip {...props}></RCTooltip>
</div>
)
}
function Tooltip(props) {
const el = <TooltipWrap {...props} ></TooltipWrap>
return (
props.DomNode && ReactDOM.createPortal(
el,
props.DomNode
)
)
}
function TooltipContent(props, ref) {
const RCBtnRef = useRef(null)
useImperativeHandle(ref, () => ({
el: RCBtnRef.current,
}));
const onclick = (e) => {
e.nativeEvent.stopImmediatePropagation()
props.changeTooltip()
}
return (
<div ref={RCBtnRef} onClick={onclick} id="RCBtn">{props.children}</div>
)
}
TooltipContent = forwardRef(TooltipContent);
function MyTooltip(props) {
const RCBtnRef = useRef(null);
const RCTooltipRef = useRef(null);
const [change, setChange] = useState(0);
const [el, setEl] = useState(null);
const init = () => {
selectPosition(RCBtnRef, RCTooltipRef, props)
}
useEffect(() => {
if (el === null) return
const content = document.createElement('div')
body.appendChild(content)
content.appendChild(el);
init();
window && window.addEventListener('scroll', (e) => {
selectPosition(RCBtnRef, RCTooltipRef, props)
});
window.onresize = function () {
init();
}
return () => {
content.removeChild(el)
}
}, [el]);
const changeTooltip = () => {
if (!el) {
setEl(document.createElement('div'))
}
setChange(change + 1);
}
useEffect(() => {
if (!el && props.isOpen) {
setEl(document.createElement('div'))
}
}, [props.isOpen])
return (
<div>
<TooltipContent ref={RCBtnRef} changeTooltip={changeTooltip}>
{
props.children
}
</TooltipContent>
<Tooltip
{...props}
change={change}
RCTooltipRef={RCTooltipRef}
DomNode={el}
defaultDisplayStatus={true}
></Tooltip>
</div>
);
}
function selectPosition(RCBtnRef, RCTooltipRef, props) {
setTimeout(() => {
const RCBtnDom = RCBtnRef.current.el.children[0];
const RCTooltipDom = RCTooltipRef.current;
const RCBtnDomStyles = RCBtnDom.getBoundingClientRect();
const RCTooltipDomStyles = RCTooltipDom.getBoundingClientRect();
const { width, left, top } = RCBtnDomStyles;
const { width: RCTooltipWidth, height: RCTooltipHeight } = RCTooltipDomStyles;
switch (props.position) {
case 'top':
RCTooltipDom.style.top = top - RCTooltipHeight + document.documentElement.scrollTop + 'px'
RCTooltipDom.style.left = left + 'px'
break;
case 'right':
RCTooltipDom.style.top = top + document.documentElement.scrollTop + 'px'
RCTooltipDom.style.left = left + width + 'px'
break;
case 'top-center':
RCTooltipDom.style.top = top - RCTooltipHeight + document.documentElement.scrollTop + 'px'
RCTooltipDom.style.left = left - (RCTooltipWidth - width) / 2 + 'px'
break;
default:
break;
}
}, 0)
}
function Btn(props) {
return <div className="Btn">{props.children}</div>
}
function Tip(props) {
return (
<div>
<div className="tip">{props.children}</div>
<div onClick={props.onclick} className="close">X</div>
</div>
)
}
function app() {
const [isOpen, setIsOpen] = useState(false);
const onclick = () => {
console.log('close')
setIsOpen(false)
}
const handleVisibleChange = (visible) => {
setIsOpen(visible);
}
return (
<React.Fragment>
<div className="container">
{}
<MyTooltip position="top" message="giao giao!">
<Btn>?</Btn>
</MyTooltip>
{}
<MyTooltip position="right" message={<Btn>giao giao!</Btn>}>
<Btn>?</Btn>
</MyTooltip>
{}
<MyTooltip position="top-center" isOpen={isOpen} onVisibleChange={handleVisibleChange} message={<Tip onclick={onclick}>giao giao!</Tip>}>
<Btn>?</Btn>
</MyTooltip>
</div>
</React.Fragment>
)
}
ReactDOM.render(React.createElement(app), domContainer);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="./index.css"/>
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel" src="like_button.jsx"></script>
</html>
*{
margin: 0;
padding: 0;
}
.Tooltip{
position: absolute;
padding:10px 15px;
border-radius: 10px;
background-color: wheat;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
}
#RCBtn{
display: inline-block;
}
.TooltipWrap{
display: inline-block;
}
.show {
transform: translateX(0px);
}
.hidden {
transform: translateX(-9999px);
}
.Btn{
cursor:help;
height: 35px;
padding: 5px 10px;
background-color: chocolate;
border-radius: 5px;
text-align: center;
line-height: 40px;
color: white;
}
body{
width: 100%;
padding-top: 100px;
}
.area{
width: 100px;
margin: 0 auto;
}
.container{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
