【问题标题】:How to detect overflow of React component without ReactDOM?如何在没有 ReactDOM 的情况下检测 React 组件的溢出?
【发布时间】:2017-02-02 20:54:58
【问题描述】:

基本上,我希望能够检测一个反应组件是否有溢出的子级。就像在this question 中一样。我发现使用 ReactDOM 也可以实现同样的事情,但是 i cannot/should not use ReactDOM。我在建议的替代方案ref 上看不到任何内容,这是等效的。

所以我需要知道的是,在这些条件下是否可以检测到反应组件中的溢出。同样地,是否可以检测宽度?

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    除了@jered 的出色回答之外,我还想提一下限定符,如果引用直接放在DOM 元素。也就是说,它不会以这种方式与 Components 一起使用。

    所以,如果你和我一样,并且具备以下条件:

    var MyComponent = React.createClass({
      render: function(){
        return <SomeComponent id="my-component" ref={(el) => {this.element = el}}/>
      }
    })
    

    当您尝试访问 this.element(可能在 componentDidMountcomponentDidUpdate 中)的 DOM 属性并且您没有看到上述属性时,以下可能是适合您的替代方法

    var MyComponent = React.createClass({
      render: function(){
        return <div ref={(el) => {this.element = el}}>
                 <SomeComponent id="my-component"/>
              </div>
      }
    })
    

    现在您可以执行以下操作:

    componentDidUpdate() {
      const element = this.element;
      // Things involving accessing DOM properties on element
      // In the case of what this question actually asks:
      const hasOverflowingChildren = element.offsetHeight < element.scrollHeight ||
                                     element.offsetWidth < element.scrollWidth;
    },
    

    【讨论】:

      【解决方案2】:

      @Jemar Jones 提出的解决方案的实现:

      export default class OverflowText extends Component {
        constructor(props) {
          super(props);
          this.state = {
            overflowActive: false
          };
        }
      
        isEllipsisActive(e) {
          return e.offsetHeight < e.scrollHeight || e.offsetWidth < e.scrollWidth;
        }
      
        componentDidMount() {
          this.setState({ overflowActive: this.isEllipsisActive(this.span) });
        }
      
        render() {
          return (
            <div
              style={{
                width: "145px",
                textOverflow: "ellipsis",
                whiteSpace: "nowrap",
                overflow: "hidden"
              }}
              ref={ref => (this.span = ref)}
            >
              <div>{"Triggered: " + this.state.overflowActive}</div>
              <span>This is a long text that activates ellipsis</span>
            </div>
          );
        }
      }
      

      【讨论】:

      • 感谢工作代码示例。我发现我还需要从 componentDidUpdate 运行 isEllipsisActive,为了避免无限循环,我需要在更新状态之前添加一个检查: if (this.isEllipsisActive(this.span) !== this.state.overflowActive) { //在此处更新状态}
      【解决方案3】:

      是的,你可以使用ref

      在官方文档中详细了解ref 的工作原理:https://facebook.github.io/react/docs/refs-and-the-dom.html

      基本上,ref 只是一个回调,它在组件第一次渲染时运行,紧接在调用 componentDidMount 之前。回调中的参数是调用ref 函数的DOM 元素。所以如果你有这样的事情:

      var MyComponent = React.createClass({
        render: function(){
          return <div id="my-component" ref={(el) => {this.domElement = el}}>Hello World</div>
        }
      })
      

      MyComponent 挂载时,它将调用将this.domElement 设置为DOM 元素#my-componentref 函数。

      这样,使用getBoundingClientRect() 之类的东西在渲染后测量您的 DOM 元素并确定子元素是否溢出父元素是相当容易的:

      https://jsbin.com/lexonoyamu/edit?js,console,output

      请记住,没有办法测量 DOM 元素在渲染之前 的大小/溢出,因为根据定义,它们还不存在。在将某物渲染到屏幕之前,您无法测量它的宽度/高度。

      【讨论】:

      • 当元素有多个子元素时,您能否展示一下您在 jsbin 中使用getBoundingClientRect 显示的内容如何工作?
      • A div 将“包裹”其中的所有内容,只要它们都相对或静态定位。因此,如果您将所有 children 放在父级内部的 div 内(就像它在 jsbin 中的结构一样),那么它应该可以解决。
      • 如果您有更复杂的用例,那么您应该提供更多详细信息和一些代码示例,最好在单独的问题中提供。
      • 好吧,我实际上刚刚意识到其他事情。在您的 jsbin 中,从 refs 返回的 __reactInternalInstance 的类型是 ReactDOMComponent,而在我的类似代码中,我给出了 ReactCompositeComponentWrapper。出于某种原因,我要返回的这个实例与您的属性不同。你会明白为什么吗?
      • 我意识到我实际上可以在我的情况下将我的组件包装在一个 div 中,然后我就可以访问正常的 dom 元素属性!谢谢!
      【解决方案4】:

      同样可以使用 React hooks 来实现:

      您首先需要的是一个状态,该状态包含 text openoverflow active 的布尔值:

      const [textOpen, setTextOpen] = useState(false);
      const [overflowActive, setOverflowActive] = useState(false);
      

      接下来,你需要一个要检查溢出的元素的 ref:

      const textRef = useRef();
      <p ref={textRef}>
          Some huuuuge text
      </p>
      

      接下来是一个检查元素是否溢出的函数:

      function isOverflowActive(event) {
          return event.offsetHeight < event.scrollHeight || e.offsetWidth < e.scrollWidth;
      }
      

      然后你需要一个 useEffect 钩子,用上面的函数检查是否存在溢出:

      useEffect(() => {
          if (isOverflowActive(reviewTextRef.current)) {
              setOverflowActive(true);
              return;
          }
      
          setOverflowActive(false);
      }, [isOverflowActive]);
      

      现在有了这两种状态和一个检查溢出元素是否存在的函数,您可以有条件地渲染某些元素(例如,显示更多按钮):

      {!textOpen && !overflowActive ? null : (
          <button>{textOpen ? 'Show less' : 'Show more'}</button>
      )}
      

      【讨论】:

        【解决方案5】:

        致任何想知道如何使用 hooks 和 useRef 完成的人:

        // This is custom effect that calls onResize when page load and on window resize
        const useResizeEffect = (onResize, deps = []) => {
          useEffect(() => {
            onResize();
            window.addEventListener("resize", onResize);
        
            return () => window.removeEventListener("resize", onResize);
            // eslint-disable-next-line react-hooks/exhaustive-deps
          }, [...deps, onResize]);
        };
        
        const App = () => {
          const [isScrollable, setIsScrollable] = useState(false);
          const [container, setContainer] = useState(null);
          // this has to be done by ref so when window event resize listener will trigger - we will get the current element
          const containerRef = useRef(container);
          containerRef.current = container;
          const setScrollableOnResize = useCallback(() => {
            if (!containerRef.current) return;
            const { clientWidth, scrollWidth } = containerRef.current;
            setIsScrollable(scrollWidth > clientWidth);
          }, [containerRef]);
          useResizeEffect(setScrollableOnResize, [containerRef]);
        
          return (
            <div
              className={"container" + (isScrollable ? " scrollable" : "")}
              ref={(element) => {
                if (!element) return;
                setContainer(element);
                const { clientWidth, scrollWidth } = element;
                setIsScrollable(scrollWidth > clientWidth);
              }}
            >
              <div className="content">
                <div>some conetnt</div>
              </div>
            </div>
          );
        };
        

        【讨论】:

          【解决方案6】:

          我需要在 React TypeScript 中实现这一点,因此这里是使用 React Hooks 在 TypeScript 中更新的解决方案。如果至少有 4 行文本,此解决方案将返回 true。

          我们声明必要的状态变量:

            const [overflowActive, setOverflowActive] = useState<boolean>(false);
            const [showMore, setShowMore] = useState<boolean>(false);
          

          我们使用useRef声明必要的引用:

            const overflowingText = useRef<HTMLSpanElement | null>(null);
          

          我们创建一个检查溢出的函数:

            const checkOverflow = (textContainer: HTMLSpanElement | null): boolean => {
              if (textContainer)
                return (
                  textContainer.offsetHeight < textContainer.scrollHeight || textContainer.offsetWidth < textContainer.scrollWidth
                );
              return false;
            };
          

          让我们构建一个useEffect,当overflowActive 发生变化时将调用它,并检查我们当前的 ref 对象以确定该对象是否溢出:

            useEffect(() => {
              if (checkOverflow(overflowingText.current)) {
                setOverflowActive(true);
                return;
              }
          
              setOverflowActive(false);
            }, [overflowActive]);
          

          在我们组件的 return 语句中,我们需要将 ref 绑定到适当的元素。我使用Material UI 加上styled-components 所以这个例子中的元素将是StyledTypography

          <StyledTypography ref={overflowingText}>{message}</StyledTypography>
          

          styled-components中设置组件样式:

          const StyledTypography = styled(Typography)({
            display: '-webkit-box',
            '-webkit-line-clamp': '4',
            '-webkit-box-orient': 'vertical',
            overflow: 'hidden',
            textOverflow: 'ellipsis',
          });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-11-17
            • 1970-01-01
            • 2021-03-18
            • 1970-01-01
            • 1970-01-01
            • 2015-02-23
            • 1970-01-01
            • 2020-01-25
            相关资源
            最近更新 更多