有些组件需要一些简单的淡入淡出css动画效果 通过visible为触发点 主要就是在visible切换true/false的时候做出简单动画 enter类型和appear类型不让一起出现

CSS animation 与 CSS transition 有何区别?

https://www.zhihu.com/question/19749045

Transition 强调过渡,Transition + Transform = 两个关键帧的Animation
Animation 强调流程与控制,Duration + TransformLib + Control = 多个关键帧的Animation
如果只有两个关键帧我会选择Transition + Transform
[

](https://ant.design/components/alert-cn/)

rc-motion做的事情

实际功能例子

https://ant.design/components/alert-cn/

在css中已经预先写好各个动画的类名和动画名

// relative path
components/alert/style/index.less

  1. // transition
  2. .transition {
  3. transition: background 0.3s, height 1.3s, opacity 1.3s;
  4. // transition: all 5s!important;
  5. &.transition-appear,
  6. &.transition-enter {
  7. opacity: 0;
  8. }
  9. ...
  10. }
  11. // animation
  12. .animation {
  13. animation-duration: 1.3s;
  14. animation-fill-mode: both;
  15. &.animation-appear,
  16. &.animation-enter {
  17. animation-name: enter;
  18. animation-fill-mode: both;
  19. animation-play-state: paused;
  20. }
  21. ...
  22. }
  23. // keyframes
  24. @keyframes enter {
  25. from {
  26. transform: scale(0);
  27. opacity: 0;
  28. }
  29. to {
  30. transform: scale(1);
  31. opacity: 1;
  32. }
  33. }

通过控制status来获取对应钩子,可产生对应的样式或者执行用户自定义操作,控制类名添加和删除

用户各个阶段传入的钩子

  1. // 动画状态
  2. export const STATUS_NONE = 'none' as const;
  3. export const STATUS_APPEAR = 'appear' as const;
  4. export const STATUS_ENTER = 'enter' as const;
  5. export const STATUS_LEAVE = 'leave' as const;
  6. // visible变化 判断属于哪个status
  7. useIsomorphicLayoutEffect(() => {
  8. let nextStatus: MotionStatus;
  9. if (!isMounted && visible && motionAppear) {
  10. nextStatus = STATUS_APPEAR;
  11. }
  12. if (isMounted && visible && motionEnter) {
  13. nextStatus = STATUS_ENTER;
  14. }
  15. if (
  16. (isMounted && !visible && motionLeave) ||
  17. (!isMounted && motionLeaveImmediately && !visible && motionLeave)
  18. ) {
  19. nextStatus = STATUS_LEAVE;
  20. }
  21. if (nextStatus) {
  22. setStatus(nextStatus);
  23. startStep();
  24. }
  25. }, [visible]);
  26. // 通过status匹配钩子
  27. const eventHandlers = React.useMemo<{
  28. [STEP_PREPARE]?: MotionPrepareEventHandler;
  29. [STEP_START]?: MotionEventHandler;
  30. [STEP_ACTIVE]?: MotionEventHandler;
  31. }>(() => {
  32. switch (status) {
  33. case STATUS_APPEAR:
  34. return {
  35. [STEP_PREPARE]: onAppearPrepare,
  36. [STEP_START]: onAppearStart,
  37. [STEP_ACTIVE]: onAppearActive,
  38. };
  39. case STATUS_ENTER:
  40. return {
  41. [STEP_PREPARE]: onEnterPrepare,
  42. [STEP_START]: onEnterStart,
  43. [STEP_ACTIVE]: onEnterActive,
  44. };
  45. case STATUS_LEAVE:
  46. return {
  47. [STEP_PREPARE]: onLeavePrepare,
  48. [STEP_START]: onLeaveStart,
  49. [STEP_ACTIVE]: onLeaveActive,
  50. };
  51. default:
  52. return {};
  53. }
  54. }, [status]);
  55. // 传入钩子返回值可作为最新样式
  56. onLeaveActive={() => { background: 'green' }}

step控制请求浏览器下一帧空闲执行动画且更新需要的动态样式
  1. if (eventHandlers[STEP_PREPARE] && step === STEP_START) {
  2. mergedStyle = {
  3. transition: 'none',
  4. ...mergedStyle,
  5. };
  6. }

useDomMotionEvents

绑定和解绑动画结束事件
一般都是用户只关心动画什么时候结束
为什么不用监听开始?

  1. // 动画名称 需要考虑兼容性
  2. animationend | transitionend
  3. // 绑定和解绑
  4. addEventListener
  5. removeEventListener

motion

  1. // 构建浏览器动画样式前缀 兼容性 作为事件监听名称的兼容
  2. prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
  3. prefixes[`Webkit${styleProp}`] = `webkit${eventName}`;
  4. prefixes[`Moz${styleProp}`] = `moz${eventName}`;
  5. prefixes[`ms${styleProp}`] = `MS${eventName}`;
  6. prefixes[`O${styleProp}`] = `o${eventName.toLowerCase()}`;
  7. // 组合两个关键字
  8. animationend | transitionend