我如何使用 preact 创建页面转换

how can i create a page transition with preact

我正在尝试使用 preact-router 进行页面转换,我已经尝试使用 preact-transition-group 包和 preact-css-transition-group 包,这给了我一个错误,无论如何这是我的基本设置:

import { h, FunctionComponent } from 'preact';
import Router from 'preact-router';

const App: FunctionComponent = () => {
  return (
    <Router>
      <div path="/1" style="padding: 50px">
        page 1 <a href="/2">page 2</a>
      </div>
      <div path="/2" style="padding: 50px">
        page 2 <a href="/1">page 1</a>
      </div>
    </Router>
  );
};

打字稿 and/or preact-router 解决方案是可取的,但不是必需的。

创建动画的最佳方法是引入另一个 HOC - 包装您的页面并将其用作路由器目标的高阶组件。它会是这样的:

import { h, FunctionComponent } from 'preact';
import Router from 'preact-router';

const App: FunctionComponent = () => {
  return (
    <Router>
      <PageAnimation path="/1">
          <div style="padding: 50px">
            page 1 <a href="/2">page 2</a>
          </div>
      </PageAnimation>
      <PageAnimation path="/2">
        <div style="padding: 50px">
          page 2 <a href="/1">page 1</a>
        </div>
      </PageAnimation>
    </Router>
  );
};

您的实现代码类似于 - 如何制作转场动画.

Note: that this approach probably dosn't work with nested routes.

使用这种方法,您需要用 class 将每条路由包装在 div 中,这将使路由具有动画效果,并将键和路径属性设置为路由的路径,如下所示:

function wrapRoute(route: h.JSX.Element): h.JSX.Element {
  return ( // the in class is the animation and the page class is makes sure that the old and new routes overlap
    <div path={route.props.path} class="in page" key={route.props.path}>
      {route}
    </div>
  );
}

然后你需要像这样添加两个反应变量:

const [previousEl, setPreviousEl] = useState<ComponentChildren | null>(null);
const [outEl, setOutEl] = useState<JSX.Element | null>(null);

之后你可以监听 preact-routeronChange 事件并将 outEl 设置为 div 包装 previousEl 并且有一个class 将动画化路由的出口,然后添加一个 onAnimationEnd 侦听器,以便您可以将 outEl 设置为 null 出口动画已经完成,最后一件事需要将 previousEl 设置为 e.current.props.children,您的路由器组件侦听器应如下所示:

<Router onChange={(e) => {
  if (previousEl) {
    setOutEl(
      <div class="out page" key={e.previous} onAnimationEnd={() => setOutEl(null)}>
        {previousEl}
      </div>
    );
  }
  if (e.current) {
    setPreviousEl(e.current.props.children);
  }
}}
>
{routes} // this is an array containing all the wrapped routes.
</Router>

您需要做的最后一件事是创建动画,请查看下面的 app.sass 示例。

这里是完整的例子:

app.tsx

import { h, FunctionComponent, JSX, ComponentChildren, Fragment } from 'preact';
import Router from 'preact-router';
import { useState } from 'preact/hooks';
import './app.sass'

const routes = [
  <div style="padding: 50px; background: red" path="/1">
    page 1 <a href="/2">page 2</a> <a href="/3">page 3</a>
  </div>,
  <div style="padding: 50px; background: blue" path="/2">
    page 2 <a href="/1">page 1</a> <a href="/3">page 3</a>
  </div>,
  <div style="padding: 50px; background: green" path="/3">
    page 2 <a href="/1">page 1</a> <a href="/2">page 2</a>
  </div>,
].map((route) => wrapRoute(route));

function wrapRoute(route: h.JSX.Element): h.JSX.Element {
  return (
    <div path={route.props.path} class="in page" key={route.props.path}>
      {route}
    </div>
  );
}

const App: FunctionComponent = () => {
  const [previousEl, setPreviousEl] = useState<ComponentChildren | null>(null);
  const [outEl, setOutEl] = useState<JSX.Element | null>(null);

  return (
    <Fragment>
      <Router
        onChange={(e) => {
          if (previousEl) {
            setOutEl(
              <div class="out page" key={e.previous} onAnimationEnd={() => setOutEl(null)}>
                {previousEl}
              </div>
            );
          }
          if (e.current) {
            setPreviousEl(e.current.props.children);
          }
        }}
      >
        {routes}
      </Router>
      {outEl}
    </Fragment>
  );
};

export default App;

app.sass

.page
  position: absolute
  top: 0
  bottom: 0
  left: 0
  right: 0

.out
  animation: out 200ms forwards

@keyframes out
  0%
    opacity: 1
    transform: translateX(0)
  100%
    opacity: 0
    transform: translateX(100vw)

.in
  animation: in 200ms forwards

@keyframes in
  0%
    opacity: 0
    transform: translateX(-100vw)
  100%
    opacity: 1
    transform: translateX(0)