React 官方终于支持了这个特性

2,919 阅读3分钟

前言

router-transition.gif

上面是一个路由切换的动画。以前,我们想要实现这个效果,需要很多 hack 的方法。但是现在,不管你使用的是什么框架,什么路由,只需要几行代码就可以实现这个效果。

这种革命性的改变得益于浏览器原生 API 的支持。而在 React 生态中,这个特性的支持更是让人兴奋。

随着 Add ViewTransition Component 的合入, React 官方终于支持了 View Transition API,这是自从 react 宣布 react hooks 以后我最喜欢的一个特性。

为什么需要 View Transition API

在 react 中,想要实现离场动画比入场动画要麻烦很多,因为 react 的组件在卸载的时候,节点会直接从 dom 中移除,而不会等待动画结束再移除。

在以前的 如何实现优雅的离场动画 这篇文章中, 我介绍过使用 framer-motion 来实现离场动画。 主要原理是记录当前需要执行离场动画的组件,在组件卸载阻默认行为,直到离场动画完成以后再卸载组件。

要完成这个过程,需要大量的 JS 代码。而 View Transition API 是 WEB API, 核心原理就是构建了两种新的伪元素

  • ::view-transition-old(name)
  • ::view-transition-new(name)

用来保存当前视图的快照,他们只短暂存在于视图过渡期间,随后立即销毁,我们可以直接使用 CSS 来指定样式。完美解决了以前离场动画的痛点。

使用 View Transition API 实现几个简单的动画

1. 路由切换动画

文章开头的路由切换动画,一共就以下几行代码.

import { unstable_ViewTransition as ViewTransition } from "react";

// 使用 ViewTransition 包裹需要执行动画的元素
<ViewTransition name="page-transition">
  {children}
</ViewTransition>

此时自动就会给路由切换加上一个淡入淡出的动画,我们可以使用 CSS 来指定动画的样式。

::view-transition-old(page-transition) {
  animation: slide-out 0.5s ease-out both;
}

::view-transition-new(page-transition) {
  animation: slide-in 0.5s ease-out both;
  animation-delay: 300ms;
}

2. 元素删除动画

delete-transition.gif

import { unstable_ViewTransition as ViewTransition } from "react";

const initialBoxes = [
  { id: 1, name: "nextjs" },
  { id: 2, name: "nestjs" },
  { id: 3, name: "hono" },
  { id: 4, name: "remix" },
  { id: 5, name: "midway" },
];
function deleteTransition() {
 const [box, setBox] = useState(initialBoxes);
 
 const handleDelete = (id: number) => {
 startTransition(() => {
     setBoxes(boxes.filter((box) => box.id !== id));
  });
 };

return (
    <div className="flex flex-wrap gap-4">
    {boxes.map((box) => (
       <ViewTransition key={box.id} name={box.name}>
        <div className="relative w-32 h-32 border-2 border-blue-500 rounded-lg flex items-center justify-center">
         <button
            onClick={() => handleDelete(box.id)}
            className="absolute top-2 right-2 text-blue-500 w-6 h-6 rounded-full cursor-pointer"
            >
            ×
          </button>
            {box.name}
        </div>
       </ViewTransition>
    ))}
    </div>
  );
}

相比于路由切换的动画,除了 ViewTransition 包裹元素以外,还需要使用 startTransition 来包裹 setState 的调用,就可以完成一个非常平滑的元素删除动画了。

为什么之前 react 不支持 View Transition API

在 react 中是有虚拟 dom 和批量更新机制的,而 View Transition API 则是期望在其回调函数中立即发生 DOM 变化。因此可能在 react 完成页面更新的时候, 生成的快照早已经被销毁了,所以自然就不起作用了。

react 的支持会对以后有什么影响

虽然目前这个特性还处于 experimental 阶段, 你需要使用

 "react": "experimental",
 "react-dom": "experimental",

来体验这个特性。如果你使用的是 TypeScript ,你还需要你的 tsconfig.json 中添加以下这段代码

"types": ["react/experimental"]

目前还有一些问题,离稳定还有一段时间。但是我相信在不久的将来,各大路由库、框架、动画库都会支持这个特性。 我们使用的 react 开发的应用会像原生 APP 应用一样,拥有非常丝滑的动画效果。