how can i create a page transition with preact

1.4k views Asked by At

I'm trying to get a page transition using preact-router, i already tried the preact-transition-group package with the preact-css-transition-group package, that gives me an error, anyways here is my basic setup:

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>
  );
};

a typescript and/or preact-router solution is preferable but not necessary.

2

There are 2 answers

0
Syler On BEST ANSWER

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

With this approach you need to wrap every route in a div with a class that will animate the route and set the key and path attribute to the path of the route like this:

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>
  );
}

then you need to add two reactive variables like this:

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

after that you can listen to the onChange event of the preact-router and set the outEl to a div that wraps previousEl and has a class that will animate the exit of the route, then add a onAnimationEnd listener so that you can set the outEl to null one the out animation has finished, last thing you need to set the previousEl to e.current.props.children, your router component listener should look like this:

<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>

last thing you need to do is to create the animations, look at the app.sass example below.

here is the full example:

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)

0
Harshal Patil On

The best way is to create an animation is introducing another HOC - Higher Order Component that wraps your page and use that as a router target. It would be something like:

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>
  );
};

Your implementation code would be something like - How to make transition animation.