react 实时获取元素动态宽高

6,172 阅读1分钟

在使用 useRef 去获取元素时,能拿到一开始的 dom 宽高,但是当后续宽高变化后不会主动更新,所以找到了 ResizeObserver API, 能监听到变化并通知对应回调

相关知识链接:

Example

自定义 hooks

import React from "react";

export default function useNodeBoundingRect(): [
  DOMRectReadOnly | null,
  Function,
  () => void
] {
  const [rect, setRect] = React.useState<DOMRectReadOnly | null>(null);

  const resizeObserver = new ResizeObserver((entries) => {
    setRect(entries[0].contentRect);
  });

  const ref = React.useCallback((node) => {
    if (node !== null) {
      resizeObserver.observe(node);
    }
  }, []);

  const cleanObserver = React.useCallback(() => {
    resizeObserver.disconnect();
  }, []);

  return [rect, ref, cleanObserver];
}

组件部分应用

import * as React from "react";
import styled from "styled-components";
import { observer } from "mobx-react-lite";

import useNodeBoundingRect from "@/common/hooks/useNodeBoundingRect";

const DEFAUTL_EDIT_TOP = 5;
const DEFAULT_TOP_ACTIONS_HEIGHT = 40;

const Actions = observer(() => {
  const root = useRootStore() as RootModel;
  const { isFormEditing } = root.uiStore;
  const { i18n } = Application.getApp();
  const [rect, topActions, cleanObserver] = useNodeBoundingRect();

  // const topActions = React.useRef(null);
  const [topActionsHeight, setTopActionsHeight] = React.useState<number>(
    DEFAULT_TOP_ACTIONS_HEIGHT
  );
  const [editTop, setEditTop] = React.useState<number>(DEFAUTL_EDIT_TOP);

  const renderActions = () => {
    if (isFormEditing) {
      return (
        <React.Fragment>
          <Button
            key="save"
            size="s"
            type="primary"
            onClick={onSave}
            loading={isSaving}
          >
            {i18n.t("Tr-0gnpzf")}
          </Button>
          <Button key="cancel" size="s" onClick={onCancel}>
            {i18n.t("Tr-7ca92e")}
          </Button>
        </React.Fragment>
      );
    }

    if ("具有编辑权限") {
      return (
        <Button key="edit" size="s" type="primary" onClick={onEdit}>
          {i18n.t("Tr-gqwqhu")}
        </Button>
      );
    }
  };

  // React.useLayoutEffect(() => {
  //     const dom = document.getElementById("top-actions");

  //     // 插件加载完毕
  //     if (!isPluginLoading && dom && topActions && topActions.current) {
  //         console.log(topActions.current.clientHeight);
  //         // 一开始进入页面延时获取顶部插件高度
  //         setTimeout(() => {
  //             console.log(dom.clientHeight);
  //             setTopActionsHeight(dom.clientHeight);
  //         }, 1500);
  //     }
  // }, [isPluginLoading, topActions]);

  React.useEffect(() => {
    if (rect && rect.height) {
      // 实际上的高度为:react.height + padding + border-width
      setTopActionsHeight(rect.height);
    }

    // cleanup
    return () => {
      cleanObserver();
    };
  }, [rect]);

  React.useEffect(() => {
    setEditTop(isFormEditing ? DEFAUTL_EDIT_TOP : topActionsHeight);
  }, [isFormEditing, topActionsHeight]);

  return (
    <React.Fragment>
      <StyledPluginsDiv id="top-actions" ref={topActions}>
        {!isFormEditing && <ActionPluginTree />}
      </StyledPluginsDiv>

      <StyledEditingDiv editTop={editTop}>{renderActions()}</StyledEditingDiv>
    </React.Fragment>
  );
});

export default Actions;

const StyledEditingDiv = styled.div<{ editTop: number }>`
  position: sticky;
  top: ${(props) => (props.editTop ? props.editTop + 65 : 105)}px;
  right: 0px;
  display: flex;
  justify-content: flex-end;
`;

const StyledPluginsDiv = styled.div`
  position: sticky;
  top: 0px;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
  z-index: 1;
  padding: 24px 0;
  margin: 0 0 16px -6px;
  background: #fff;
  border-bottom: 1px solid #eff0f2;
`;