前言
最近在用 antd 的时候发现一个小细节:
打开 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 元素上。
通过在 onAppearPrepare 和 onEnterPrepare 两个 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 变换的过程中,改变变形的远点。
往上追溯到 按,可知 antd 的 Modal 组件默认的动画是 zoom ,而在 动画相关的样式文件 中,zoomIn 动画的定义包含 scale(0.2) → scale(1)。
总结
看完三个仓库之后,答案就很清晰了,antd 实际上是通过 transform-origin 改变了 scale 变形的原点,从而在变形过程中,给视觉带来弹窗从点击处弹起的反馈~
支持我
小魔法技巧你 get 了,那我能有一个赞嘛?233