React 工具提示的自定义边缘检测会导致页面偶尔“闪烁”

Mab*_*eek 5 javascript reactjs react-hooks

因此,我为作为图表库一部分的工具提示创建了简单的边缘检测。我无法使用已经实现边缘检测的工具提示(如 MUI 工具提示),因为图表软件只允许 HTML 工具提示,所以我只能创建一个仅对屏幕右侧进行边缘检测的 React 组件。由于工具提示始终向右侧打开,因此我只需处理右侧即可。

因此,当悬停靠近边缘时,工具提示会检查它与边缘的距离,然后我使用 CSS 偏移将工具提示放置在远离屏幕右侧的点。

该组件如下所示:

export const MyTooltip = ({ children }) => {

  useEffect(() => {
    const container =
      document.getElementById('tooltip');

    if (container) {
      const x = container.getBoundingClientRect().x;

      if (x > window.outerWidth - 200) {
    container.style.left = 'auto';
        container.style.right = '0';
        container.style.transform = `translateX(${
          window.outerWidth - x - 40
        }px)`;
      }
    }
  }, []);

  return (
    <TooltipStyle>
      <div id="tooltip" className="custom-tooltip">
        {children}
      </div>
    </TooltipStyle>
  );
};
Run Code Online (Sandbox Code Playgroud)

TooltipStyle 是一个样式化组件,具有用于背景、填充等的 .custom-tooltip 类的 CSS。200px 是所有实例中工具提示的宽度,40px 是填充。

问题:有时,当工具提示在 DOM 中呈现时,页面会瞬间“闪烁”。非常难看。我知道这是因为 useEffect 挂钩中的代码实际上设置了工具提示的偏移量,但我不确定什么是好的解决方案。

任何帮助深表感谢!谢谢。

onk*_*kar 0

如果您可以在 Codesandbox 上提供最小的可重复性,那就太好了。例如,https://codesandbox.io/s/me6kg

在您的代码中,当用户将鼠标悬停在容器上时,您每次都会创建一个新的工具提示。它被放置在 DOM 上,然后您使用钩子更改元素上的 CSS 属性。
如果您避免重新渲染工具提示,那么它可能会解决问题。我们可以shouldComponentUpdate为此使用生命周期。

body {
  padding: 2rem;
  white-space: nowrap;
}

div { 
  display: inline-block;
}
Run Code Online (Sandbox Code Playgroud)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js" crossorigin="anonymous"></script>

<div id="root">App will go here</div>

<script type="text/babel">
   const root = ReactDOM.createRoot(document.getElementById('root'));

   class Tooltip extends React.Component {
    constructor(props) {
     super(props);
     this.shown = false;
     this.self = React.createRef(null);
    }

    componentDidMount() {
     this.shown = true;
    }

    shouldComponentUpdate(nextProps, nextState) {
     let ref = this.self.current;
     let portWidth = 0;
     if (document.documentElement) 
       portWidth = document.documentElement.clientWidth;   //iframe
     else 
       portWidth = window.outerWidth;

     let left = '0px';
     if (ref) {
      const x = ref.getBoundingClientRect().x;
      if (x > portWidth - 200 - 40) {
       left = `${portWidth - x - 40 - 200}px`;
      }
     }

     ref.style.left = left;
     ref.style.visibility = nextProps.show ? 'visible' : 'hidden';
     if (!this.shown) {
      return true;
     }
     // prevent rerenders
     return false;
    }

    render() {
     console.log('Render a tooltip.');
     const styles = {
      position: 'absolute',
      width: '200px',
      color: 'white',
      backgroundColor: 'black',
      top: '-2rem',
      left: this.props.left ? this.props.left : '0px',
      visibility: this.props.show ? 'visible' : 'hidden',
     };
     return (
      <div ref={this.self} style={styles}>
       {this.props.children}
      </div>
     );
    }
   }

   class Box extends React.Component {
    constructor(props) {
     super(props);
     this.state = {show: false};
     this.handleEnter = this.handleEnter.bind(this);
     this.handleLeave = this.handleLeave.bind(this);
    }

    handleEnter() {
     this.setState({ show: true });
    }

    handleLeave() {
     this.setState({ show: false });
    }

    render() {
     const styles = {
      border: '1px solid gray',
      height: '100px',
      width: '300px',
      position: 'relative',
      left: this.props.left ? this.props.left : '0px',
     };

     return (
      <div
       style={styles}
       onMouseEnter={this.handleEnter}
       onMouseLeave={this.handleLeave}
      >
       {this.props.content}
       <Tooltip show={this.state.show}>
          I am<em><b>{this.props.content}</b></em>
       </Tooltip>
      </div>
     );
    }
   }

   root.render(
    <React.Fragment>
     <Box content="Box 1" />
     <Box content="Box 2" left="calc(100vw - 300px)" />
    </React.Fragment>
   );
</script>
Run Code Online (Sandbox Code Playgroud)

使用水平滚动并检查工具提示位置。

注意:如果您在输出中没有看到水平滚动条,请减小浏览器宽度。


这只是一个最低限度的代码。可以进行许多改进。