antd 的小魔法

1,410 阅读2分钟

前言

最近在用 antd 的时候发现一个小细节:

打开 Modal 的时候,内容窗口会从鼠标点击位置弹出来!

modal

这神奇的跟手感是怎么回事,好好奇!于是开始动手去翻看 Modal 的源码。

分析

ant-design

首先打开 ant-design 仓库,发现在 Modal.tsx 里有这段:

let mousePosition: { x: number; y: number } | null;

// ref: https://github.com/ant-design/ant-design/issues/15795
const getClickPosition = (e: MouseEvent) => {
  mousePosition = {
    x: e.pageX,
    y: e.pageY,
  };
  // 100ms 内发生过点击事件,则从点击位置动画展示
  // 否则直接 zoom 展示
  // 这样可以兼容非点击方式展开
  setTimeout(() => {
    mousePosition = null;
  }, 100);
};

// 只有点击事件支持从鼠标位置动画展开
if (typeof window !== 'undefined' && window.document && window.document.documentElement) {
  document.documentElement.addEventListener('click', getClickPosition, true);
}

Cool!这样就能记下来点击的位置了~

之后有将 mousePosition 传递给基础组件 rc-dialog ,剩余的逻辑肯定是不在 antd 啦~

rc-dialog

接着打开 rc-dialog 仓库,在 Content/index.jsx 里可以找到:

const [transformOrigin, setTransformOrigin] = React.useState<string>();

function onPrepare() {
  const elementOffset = offset(dialogRef.current);

  setTransformOrigin(
    mousePosition
      ? `${mousePosition.x - elementOffset.left}px ${mousePosition.y - elementOffset.top}px`
      : '',
  );
}

onPrepare 会根据 content 的位置和 mousePosition,计算出一个 transfromOrigin 的值,它会被附加到 dialog 的 content 元素上。

通过在 onAppearPrepareonEnterPrepare 两个 props,将 onPrepare 传给 CSSMotion 后,rc-dialog 这个仓库做的事情也就到此为止了。

最关键的魔法都在 CSSMotion 实现,那接下来肯定是要接着去看 CSSMotion 的来源 rc-motion 这个仓库!

rc-motion

首先看看这个仓库的简介:

⛷ CSS Animation for React

很明显这是基于 react 的一个动画库。

CSSMotion 是一个 render-props 组件,关键代码:

children(
        {
          ...mergedProps,
          className: classNames(getTransitionName(motionName, status), {
            [getTransitionName(
              motionName,
              `${status}-${statusSuffix}`,
            )]: statusSuffix,
            [motionName as string]: typeof motionName === 'string',
          }),
          style: statusStyle,
        },
        setNodeRef,
      );
    }

其实就是根据 motionName + status ,计算并给 children 提供一些类名和样式。而之前分析的 transform-origin 是干啥的呢?根据 MDN 可知,它可以在 dom 元素 transform 变换的过程中,改变变形的远点。

往上追溯到 按,可知 antdModal 组件默认的动画是 zoom ,而在 动画相关的样式文件 中,zoomIn 动画的定义包含 scale(0.2) → scale(1)

总结

看完三个仓库之后,答案就很清晰了,antd 实际上是通过 transform-origin 改变了 scale 变形的原点,从而在变形过程中,给视觉带来弹窗从点击处弹起的反馈~

支持我

小魔法技巧你 get 了,那我能有一个赞嘛?233