javascriptreactjsnext.jsframer-motion

Next JS - motion whileInView is not being triggered on soft navigation


I'm making a portfolio site using Next JS and motion, for the projects section I'm animating project cards using whileInView. it is working fine but when I do soft navigation and move to the more projects screen it is not being triggered. It also works when I do a hard refresh.

See this in action here

projects.tsx

import { cn } from "@/lib/utils";
import Wrapper from "@/components/shared/page";
import { ProjectCard } from "./project-card";
import { Button } from "@/components/ui/button";
import { ArrowRight } from "lucide-react";
import Link from "next/link";
import { ROUTES } from "@/lib/constants/routes";

export function Projects({
  className,
  showMoreButton
}: {
  className?: string;
  showMoreButton?: boolean;
}) {
  return (
    <section className={cn("py-24 sm:px-8 bg-accent my-6", className)}>
      <Wrapper className="px-4 space-y-20">
        ...
        <ProjectCard
          title={"Wall of wonder"}
          imgUrl={"/images/project-1.webp"}
          skipMargin
        />
        
        ... {/* without skipMargin */}
      </Wrapper>
    </section>
  );
}

project-card.tsx

"use client";
import { cn } from "@/lib/utils";
import { motion } from "motion/react";
import Image from "next/image";
import { useRef } from "react";

export const ProjectCard = ({
  reverse,
  imgUrl,
  title,
  skipMargin
}: {
  title: string;
  reverse?: boolean;
  imgUrl: string;
  skipMargin?: boolean;
}) => {
  const scrollRef = useRef<HTMLDivElement>(null);

  return (
    <article
      ref={scrollRef}
      className="max-sm:space-y-4 overflow-hidden sm:grid sm:grid-cols-10 gap-4"
    >
      <motion.div
        initial={{
          translateX: reverse ? "-20px" : "20px",
          opacity: 0
        }}
        whileInView={{
          translateX: "0px",
          opacity: 1
        }}
        viewport={{
          root: scrollRef,
          margin: skipMargin ? undefined : "-100px",
          once: true
        }}
        transition={{
          ease: "easeInOut",
          duration: "0.5",
          delay: 0.1
        }}
        ...
      >
        ...
        
      </motion.div>
      <motion.div
        ...
        initial={{
          translateX: reverse ? "20px" : "-20px",
          opacity: 0
        }}
        whileInView={{
          translateX: "0px",

          opacity: 1
        }}
        viewport={{
          root: scrollRef,
          margin: skipMargin ? undefined : "-100px",
          once: true
        }}
        transition={{
          ease: "easeInOut",
          duration: "0.5",
          delay: 0.1
        }}
      >
        <Image
          src={imgUrl}
          ...
        />
      </motion.div>
    </article>
  );
};

Pages:

// Home

const Home = () => ... <Projects /> ...;
const Projects = ()=> <Projects />;

Solution

  • Working solution from the GitHub:

    After some further testing, it appears to only happen when running NextJS in dev mode (with or without turbopack) If I build the site and run it it appears to work as intended. So annoying during development, but at least the actual build works correctly.

    CaseyBlackburn