Full transcript
Intro
0:00It has been a while since we last
0:01explored a fresh page transition concept
0:04here on the channel as I've been
0:05focusing quite a lot on scroll
0:07animations for the past few months.
0:09[music]
0:09Now, for those of you who have been
0:11keeping an eye on things, you might
0:13remember that around two months ago, I
0:15covered this really cool SVG powered
0:18image hover effect and ever since
0:19putting that together, I knew the same
0:22technique had a lot of potential beyond
0:24just hover interactions. So, last
0:26[music] weekend, I took those same SVG
0:28elements and repurposed them into a full
0:31page transition effect using [music]
0:32Next.js and GSAP. As you can see here,
0:35every time I navigate between routes,
0:37the SVG strokes draw in and fill the
0:40entire screen to cover the current page
0:42and then continue forward off the other
0:44side to reveal the new one underneath.
0:46It creates this really fluid vibe that
0:48feels organic and hand-drawn compared to
0:51the typical slide or fade transitions.
0:53In this video, I'm going to walk you
0:55through how to build this step-by-step
0:58using Next Transition Router and GSAP.
1:00We'll set up the transition provider,
1:02wire in the SVG paths, and then animate
1:04them with the stroke dash offset
1:06technique to get this effect working
1:08across all your routes. If you'd like to
1:10access the source code for this project
1:12along with hundreds of other similar
1:14micro projects and a brand new website
1:16template every month, you can check out
1:18the pro membership via the link in the
1:19description. And if you find my work
1:21helpful, make sure you leave a like on
1:23the video and subscribe to the channel
1:25if you haven't already. All right, let's
1:27get into the code. [music]
Project Setup Walkthrough
1:30Just to save some time, I've already set
1:32up a few placeholder pages along with a
1:34navigation bar and some basic styling.
1:36[music] Let me quickly walk you through
1:38what I have so far. There are three
1:40pages: home, about, and contact. Each
1:42one is pretty straightforward, just a
1:44section with a class of hero and an H1
1:47inside it. For the navigation, I have a
1:49navbar component using Next.js link
1:51components instead of regular anchor
1:53tags. The reason for that is link
1:56handles client-side navigation, meaning
1:58the page does not do a full reload when
2:00you click a link. because our transition
2:03effect later will depend on intercepting
2:05that navigation. The navbar itself has a
2:07logo on the left linking back to home
2:10and three page links on the right.
2:11[music] Now, in the root layout, the
2:13navbar is rendered right before
2:15children. Since the layout component
2:17wraps every route in the app, this means
2:19the navbar stays mounted across page
2:21changes and does not unmount and remount
2:23every time I navigate. I've also set up
2:26a few fonts using the Next.js font
2:28module. What this does is it downloads
2:30and self-hosts the font files at build
2:32time, so there are no external requests
2:35to Google Fonts at run time. It exposes
2:38them as [music] CSS custom properties,
2:40which I can then use anywhere in my
2:41style sheet. On the styling side, I've
2:44defined a couple of custom properties on
2:46the root for the background and
2:47foreground colors, so the palette is
2:49controlled from a single place. The
2:51navbar is fixed positioned, so it sticks
2:53to the top of the viewport and flexbox
2:56handles the spacing between the logo and
2:58the links. The hero section is set to
3:00100 viewport height. The content is
3:01centered with flexbox and the H1 uses
3:04clamp for a fluid font size that scales
3:06between mobile and desktop without any
3:08media queries. With all of that in
3:10place, I have the dev server running and
3:12as you can see here, clicking through
3:14the navbar links swaps between pages.
3:17Right now, there is no transition at
3:19all. The pages just cut instantly. So,
3:21let's start building that out. The first
Installing Dependencies
3:24thing we need to do is install the
3:25packages we'll be using. I will open up
3:27the integrated terminal and install GSAP
3:30along with Next Transition Router. GSAP
3:33is what we'll use to animate the SVG
3:34strokes and Next Transition Router is
3:37what lets us hook into Next.js page
3:39navigations and run custom animations
3:41during route changes. Now, with those
Creating the TransitionProvider
3:43installed, we need a provider component
3:46that will handle the page transition, so
3:48I'm going to create a new folder called
3:50providers inside the source directory
3:52and inside that, let's create a new file
3:54called Transition Provider. This
3:56component is going to be responsible for
3:58intercepting page navigations and
4:00running our SVG animation before and
4:02after each route change. Now, first
4:04thing at the top, I need to add the use
4:06client directive. This is required
4:08because the transition logic involves
4:10refs, effects, and browser APIs, which
4:13only run on the client side. Then, I
4:15will import the transition router from
4:17Next Transition Router. This is the
4:19package that does the heavy lifting for
4:21us. It hooks into Next.js client-side
4:23routing and gives us leave and enter
4:25callbacks where we can run whatever
4:27animation we want before the old page
4:29unmounts and after the new page mounts.
4:31Now, for the component itself, I will
4:34export a default function called
4:35Transition Provider that accepts
4:37children as a prop. Inside it, for now,
4:40we'll just return Transition Router with
4:42the auto prop and wrapping children
4:44inside it. The auto prop tells
4:46Transition Router to automatically
4:48intercept any Next.js link navigations
4:50in the app, so we do not have to
4:52manually trigger transitions ourselves.
4:54Children is whatever page content gets
4:56passed through from the layout. Now, we
4:58need to actually use this provider. So,
5:00back in the root layout, we need to
Wiring into Layout
5:02import Transition Provider from the
5:04providers folder and then wrap children
5:06with it right below the navbar. Now, let
5:08me go back to the Transition Provider.
5:10What we need to do next is add the SVG
Adding SVG Paths
5:13that will act as our transition overlay.
5:15This is what will visually cover the
5:16screen during the page change. So, right
5:18above children, I'll create a new div
5:20with the class of transition SVG. This
5:23div is going to be the container that
5:25holds our SVG and sits on top of
5:27everything as a fixed overlay. Inside
5:29that, I placed in an SVG element with
5:32two path elements. These paths are
5:33basically hand-drawn looping bezier
5:35curves that zigzag across the canvas.
5:38The view box is set to match the
5:39coordinate space that the paths were
5:41designed in and I have set preserve
5:43aspect ratio to none, which is important
5:45because it allows the SVG to stretch and
5:47fill the entire viewport regardless of
5:49the screen's aspect ratio. Each path has
5:52a stroke color referencing a CSS custom
5:54property, a stroke width of 200, and
5:57stroke line caps at two round for smooth
5:59ends. Now, let me get the styling done
Styling the SVG Overlay
6:01for this. First of all, I'll define the
6:04two stroke color variables on the root.
6:06I'm setting transition stroke one to
6:07this orange and transition stroke two to
6:10this purple. Having these as custom
6:12properties means I can easily swap the
6:14colors from one place without touching
6:16the component. Then, for the transition
6:18SVG container itself, I'm setting it to
6:21fixed positioning, so it stays locked to
6:23the viewport and does not scroll with
6:25the page content. Top 50% and left 50%
6:29along with the translate minus 50 on
6:31both axes centers it perfectly in the
6:33viewport. The scale 1.5 on the transform
6:36is key here. It over scales the SVG
6:38slightly, so the strokes will be covered
6:40every edge of the screen when they
6:42expand. Width and height are both 100%
6:45to fill the viewport and pointer events
6:47is set to none, so the overlay does not
6:49block any clicks or interactions with
6:51the page underneath. We also set a high
6:53z-index to make sure it sits above
6:56everything else. Next, for the SVG
6:58itself inside the container, I'm just
7:00setting width and height to 100%, so it
7:02fills its parent completely. And
7:05finally, on the path elements, I'm
7:07setting stroke dash offset and stroke
7:09dash array both to a high value. This is
7:11a CSS-only safeguard to make sure the
7:13paths are hidden on the initial page
7:15load. Without this, you might see an
7:17artifact before the JavaScript runs
7:19where the raw SVG strokes would flash on
7:22the screen. Setting a huge dash offset
7:24like this effectively hides them from
7:26the start and then once our use effect
7:29kicks in, it will replace these with the
7:31actual calculated values. With that in
7:33place, in the next step, we'll set up
7:35the JavaScript side to properly measure
7:37the path lengths and wire up the
7:39transition animations. All right, so now
7:41we need to set up the refs and the use
7:43effect that [music] will measure our SVG
7:45paths. To do that, I'll first import use
Measuring Path Lengths
7:48ref and use effect from React. Then,
7:50inside the component, I'll create two
7:52refs. SVG ref is going to hold a
7:54reference to the SVG element itself and
7:57paths ref will store an array of the
7:59individual path elements inside it. The
8:01reason I'm using a ref for the paths
8:03array instead of state is because we do
8:06not need React to re-render when this
8:08value changes. We just need a persistent
8:11reference that GSAP can access later
8:13during the animations. Now, to actually
8:15connect SVG ref to the SVG element, I
8:18will go ahead and set the ref on the SVG
8:21tag. With that in place, we can now set
8:23up the use effect. This is where we
8:25measure the paths and set up the initial
8:27dash values that make the stroke draw
8:29technique work. So, first thing inside,
8:31we are doing a quick guard check. If SVG
8:33ref does not exist for whatever reason,
8:36we just bail out early. After that, we
8:39grab all the path elements from the SVG
8:41using query selector all and convert
8:43that node list into an array, so we can
8:46store it in the paths ref. Now comes the
8:48important part. We loop through each
8:50path and call get total length on it.
8:53This is a native SVG method that returns
8:55the exact pixel length of the particular
8:57path and since both of our paths have
9:00different curves, they will each have a
9:02different length. Once we have that
9:03value, we set both stroke dash array and
9:06stroke dash offset to that length. So,
9:08what this does is it creates a single
9:10dash that spans the entire path, but
9:13offsets it by its full length, which
9:15effectively hides the stroke completely.
9:17And this replaces the large values we
9:19set in CSS earlier with the actual
9:21precise measurements. All right, so now
9:23that the paths are measured and hidden,
Animating the Leave Transition
9:26it is time to bring in the animation.
9:28So, I'll go ahead and import GSAP at the
9:30top. The first animation we need is the
9:33leave transition, which is what runs
9:35when the user clicks a link and the
9:36current page is about to exit. So,
9:39Transition Router gives us a next
9:41callback here and we have to call it
9:43when our animation finishes, so it knows
9:45when to actually swap the page content.
9:47With that in mind, we are creating a
9:49GSAP timeline and setting on complete to
9:51next. That way, the page swap happens
9:54automatically once the animation is
9:56done. Then, we loop through each path
9:58and add a tween to the timeline. What we
10:00are animating here is stroke-dashoffset
10:02down to zero, which draws the stroke in
10:05from fully hidden to fully visible. And
10:07at the same time, we are also animating
10:09stroke width attribute from 200 up to
10:11700 using the attribute object. This is
10:15really the trick that makes the whole
10:17effect work. As the stroke draws in, it
10:19also gets thicker. And because these
10:21paths loop and crisscross across the
10:24entire view box, a stroke width of 700
10:26is more than enough to completely fill
10:28the screen with color. Finally, I'm
10:30returning a cleanup function that kills
10:33the timeline, which just prevents any
10:35issues if the component happens to
10:37unmount while the animation is still
10:38running. Now for the enter transition,
Animating the Enter Transition
10:41which runs right after the new page is
10:42mounted, and we need to reveal it by
10:44clearing the SVG overlay. So, same
10:46structure here. We create a timeline
10:49with on complete calling next. We then
10:51loop through the paths, but this time I
10:53first need to grab each path's total
10:55length again. And here is where it gets
10:57interesting. Instead of animating
10:59stroke-dashoffset back to the positive
11:01length, which would just reverse the
11:03stroke back the way it came in like a
11:05rewind, we need to animate it to a
11:07negative length. What the negative value
11:09does is it pushes the dash forward past
11:12the end of the path. So, visually it
11:14looks like the stroke is continuing its
11:16journey off the other side of the
11:18screen, rather than shrinking back to
11:20where it started. That gives it a much
11:23more fluid and natural feel. Along with
11:25that, I'm bringing stroke width back
11:27down to 200, so the strokes thin out as
11:30they exit. Now, once each path finishes
11:33its animation, we are using GSAP's set
11:35method right away to reset
11:37stroke-dashoffset back to the positive
11:39length. This step is critical because it
11:41puts everything back to its initial
11:43hidden state, ready for the next time
11:45someone navigates. And same as before,
11:48we return a cleanup function to kill the
11:49timeline. And that's pretty much it. As
Final Result
11:52you can see, clicking through the pages
11:54now triggers this SVG stroke wipe that
11:56draws in to cover the old page and then
11:58continues forward to reveal the new one
12:00underneath. Hope you found the video
12:02helpful. Thanks for watching, and I'll
12:04see you in the next one.