Page Transition

Animated change between pages/views.

Page A
Page B

fade transition · 900ms between routes

html

<div class="mot-pgt-stage" data-style="fade">
  <div class="mot-pgt-page mot-pgt-page--a mot-pgt-page--cycle" style="background:#BFD9F2;">Page A</div>
  <div class="mot-pgt-page mot-pgt-page--b mot-pgt-page--cycle">Page B</div>
  <p class="mot-pgt-caption">fade transition &middot; 900ms between routes</p>
</div>
<style>
.mot-pgt-stage { position: relative; width: 100%; max-width: 320px; height: 100px; background: #FAF7F2; border: 1px solid #24143022; border-radius: 12px; overflow: hidden; font: 13px system-ui, sans-serif; color: #241430aa; }
.mot-pgt-page { position: absolute; inset: 0; display: grid; place-items: center; font-weight: 700; color: #241430; }
.mot-pgt-page--b { background: #FAF7F2; border: 2px dashed #24143033; opacity: 0; }
.mot-pgt-caption { position: absolute; bottom: -26px; left: 0; }
@media (prefers-reduced-motion: no-preference) {
  [data-style="fade"] .mot-pgt-page--cycle.mot-pgt-page--a { animation: mot-pgt-fade-out 900ms ease-in-out infinite; }
  [data-style="fade"] .mot-pgt-page--cycle.mot-pgt-page--b { animation: mot-pgt-fade-in 900ms ease-in-out infinite; }
  [data-style="slide"] .mot-pgt-page--cycle.mot-pgt-page--a { animation: mot-pgt-slide-out 900ms ease-in-out infinite; }
  [data-style="slide"] .mot-pgt-page--cycle.mot-pgt-page--b { animation: mot-pgt-slide-in 900ms ease-in-out infinite; }
  [data-style="scale"] .mot-pgt-page--cycle.mot-pgt-page--a { animation: mot-pgt-scale-out 900ms ease-in-out infinite; }
  [data-style="scale"] .mot-pgt-page--cycle.mot-pgt-page--b { animation: mot-pgt-scale-in 900ms ease-in-out infinite; }
  @keyframes mot-pgt-fade-out { 0%,45% { opacity: 1; } 50%,100% { opacity: 0; } }
  @keyframes mot-pgt-fade-in { 0%,45% { opacity: 0; } 50%,95% { opacity: 1; } 100% { opacity: 0; } }
  @keyframes mot-pgt-slide-out { 0%,45% { transform: translateX(0); } 50%,100% { transform: translateX(-100%); } }
  @keyframes mot-pgt-slide-in { 0%,45% { transform: translateX(100%); opacity: 1; } 50%,95% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(100%); } }
  @keyframes mot-pgt-scale-out { 0%,45% { opacity: 1; transform: scale(1); } 50%,100% { opacity: 0; transform: scale(1.15); } }
  @keyframes mot-pgt-scale-in { 0%,45% { opacity: 0; transform: scale(.85); } 50%,95% { opacity: 1; transform: scale(1); } 100% { opacity: 0; } }
}
</style>

tailwind

<div class="relative w-full max-w-[320px] h-[100px] bg-[#FAF7F2] border border-[#24143022] rounded-xl overflow-hidden font-sans text-sm text-[#241430]/70" data-style="fade">
  <div class="tw-pgt-a tw-pgt-cycle absolute inset-0 grid place-items-center font-bold text-[#241430]" style="background:#BFD9F2;">Page A</div>
  <div class="tw-pgt-b tw-pgt-cycle absolute inset-0 grid place-items-center font-bold text-[#241430] bg-[#FAF7F2] border-2 border-dashed border-[#24143033] opacity-0">Page B</div>
  <p class="absolute -bottom-6 left-0">fade transition &middot; 900ms between routes</p>
</div>
<style>
@media (prefers-reduced-motion: no-preference) {
  [data-style="fade"] .tw-pgt-cycle.tw-pgt-a { animation: mot-pgt-tw-fade-out 900ms ease-in-out infinite; }
  [data-style="fade"] .tw-pgt-cycle.tw-pgt-b { animation: mot-pgt-tw-fade-in 900ms ease-in-out infinite; }
  [data-style="slide"] .tw-pgt-cycle.tw-pgt-a { animation: mot-pgt-tw-slide-out 900ms ease-in-out infinite; }
  [data-style="slide"] .tw-pgt-cycle.tw-pgt-b { animation: mot-pgt-tw-slide-in 900ms ease-in-out infinite; }
  [data-style="scale"] .tw-pgt-cycle.tw-pgt-a { animation: mot-pgt-tw-scale-out 900ms ease-in-out infinite; }
  [data-style="scale"] .tw-pgt-cycle.tw-pgt-b { animation: mot-pgt-tw-scale-in 900ms ease-in-out infinite; }
  @keyframes mot-pgt-tw-fade-out { 0%,45% { opacity: 1; } 50%,100% { opacity: 0; } }
  @keyframes mot-pgt-tw-fade-in { 0%,45% { opacity: 0; } 50%,95% { opacity: 1; } 100% { opacity: 0; } }
  @keyframes mot-pgt-tw-slide-out { 0%,45% { transform: translateX(0); } 50%,100% { transform: translateX(-100%); } }
  @keyframes mot-pgt-tw-slide-in { 0%,45% { transform: translateX(100%); opacity: 1; } 50%,95% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(100%); } }
  @keyframes mot-pgt-tw-scale-out { 0%,45% { opacity: 1; transform: scale(1); } 50%,100% { opacity: 0; transform: scale(1.15); } }
  @keyframes mot-pgt-tw-scale-in { 0%,45% { opacity: 0; transform: scale(.85); } 50%,95% { opacity: 1; transform: scale(1); } 100% { opacity: 0; } }
}
</style>

react

export function PageTransition({
  style = "fade",
  duration = 900,
  loop = true,
  accent = "#BFD9F2",
}) {
  const cls = loop ? "cycle" : "";
  return (
    <div style={{ position: "relative", width: "100%", maxWidth: 320, height: 100, background: "#FAF7F2", border: "1px solid #24143022", borderRadius: 12, overflow: "hidden", fontFamily: "system-ui, sans-serif", fontSize: 13, color: "#241430aa" }}>
      <div className={`page-a ${cls}`} style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", fontWeight: 700, color: "#241430", background: accent }}>Page A</div>
      <div className={`page-b ${cls}`} style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", fontWeight: 700, color: "#241430", background: "#FAF7F2", border: "2px dashed #24143033", opacity: 0 }}>Page B</div>
      <p style={{ position: "absolute", bottom: -26, left: 0 }}>{style} transition &middot; {duration}ms between routes</p>
      <style>{`
        @media (prefers-reduced-motion: no-preference) {
          [data-style="fade"] .page-a.cycle, .fade-a { animation: mot-pgt-fade-out ${duration}ms ease-in-out infinite; }
          .page-a.cycle { animation: ${style === "fade" ? "mot-pgt-fade-out" : style === "slide" ? "mot-pgt-slide-out" : "mot-pgt-scale-out"} ${duration}ms ease-in-out infinite; }
          .page-b.cycle { animation: ${style === "fade" ? "mot-pgt-fade-in" : style === "slide" ? "mot-pgt-slide-in" : "mot-pgt-scale-in"} ${duration}ms ease-in-out infinite; }
          @keyframes mot-pgt-fade-out { 0%,45% { opacity: 1; } 50%,100% { opacity: 0; } }
          @keyframes mot-pgt-fade-in { 0%,45% { opacity: 0; } 50%,95% { opacity: 1; } 100% { opacity: 0; } }
          @keyframes mot-pgt-slide-out { 0%,45% { transform: translateX(0); } 50%,100% { transform: translateX(-100%); } }
          @keyframes mot-pgt-slide-in { 0%,45% { transform: translateX(100%); opacity: 1; } 50%,95% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(100%); } }
          @keyframes mot-pgt-scale-out { 0%,45% { opacity: 1; transform: scale(1); } 50%,100% { opacity: 0; transform: scale(1.15); } }
          @keyframes mot-pgt-scale-in { 0%,45% { opacity: 0; transform: scale(.85); } 50%,95% { opacity: 1; transform: scale(1); } 100% { opacity: 0; } }
        }
      `}</style>
    </div>
  );
}