Full transcript
0:00A couple of months ago, this site called
0:02the Obsidian Assembly picked up site of
0:04the day on Awwwards. It has a lot going
0:06on, but one [music] thing stood out to
0:08me immediately, the page transition.
0:10Click any link in the navigation and
0:12four full-width bars sweep across the
0:14screen from left to right, one after
0:16another, sealing the page shut. The
0:18brand name rises into view, sits there
0:20for a beat, and then everything leaves
0:22the way it came, [music] wiping across
0:23and revealing the new page underneath.
0:25It looks expensive, but strip it back
0:27and this is one of the most reused
0:29transition patterns [music] in modern
0:30web design. You will find it across
0:32award-winning sites all year, dressed up
0:34differently every time, but the
0:35mechanics underneath are always [music]
0:37the same. That is exactly why I thought
0:39this would make a good enough breakdown
0:41to cover on the channel. It is a pattern
0:43you will reach for again and again,
0:44[music] and we have not touched a
0:45Next.js project on the channel in a
0:47while, so I put together a small
0:49three-page experience that recreates
0:51this transition [music] using GSAP and
0:53Next Transition Router. The entire
0:55transition provider comes in at well
0:57under 100 lines. In this video, I will
1:00take [music] you through the full build
1:01from scratch, step-by-step. If you would
1:03like to grab the source code for this
1:05[music] project, along with hundreds of
1:07other micro projects, and a brand new
1:08website template every month, you can
1:10check [music] out the pro membership via
1:12the link in the description. And if you
1:14find this kind of content useful, a like
1:16on the video and a subscribe really do
1:18help the channel grow. All right, let's
1:20get into the code.
1:23To save some time, I have already set up
1:24the three pages we are going to be
1:26working with, [music] Genesis, which is
1:28the home page, Threshold, and Sanctum.
1:30Each one follows the exact same
1:32structure, a section element acting as
1:34the hero, and an H1 with the page name
1:36inside it. Nothing more than that. A
1:38simple heading is enough to keep the
1:40page from looking empty, and the focus
1:42here is entirely on the transition. I
1:44have also put together a simple navbar
1:46component. It imports [music] the link
1:48component from Next.js, so we get
1:50client-side navigation between our
1:52routes. Inside, the brand name sits on
1:54the left, [music] and the three page
1:55links sit on the right. One for the home
1:57page, one for threshold, and one for
1:59Sanctum. Each link [music] is wrapped in
2:01its own div, which gives us something to
2:03hang padding off later. And finally, I
2:06have cleaned up the layout file. I
2:07swapped out the boilerplate fonts for
2:09Instrument Serif and Instrument [music]
2:11Sans, wired them up as CSS variables,
2:13and imported the navbar, so it renders
2:15above the children. Since [music] this
2:17is the root layout, it shows up across
2:19the entire site automatically. As
2:21[music] you can see on this side,
2:22everything is running fine. Before we
2:24get into the styling, we need our
2:26assets. I will head into the public
2:28directory and drop in [music] three
2:29images. These will serve as the
2:31full-screen backgrounds for each page.
2:33With those in place, let's move on to
2:35the CSS. Let's open up the global CSS
2:37[music] file and start building out the
2:39look and feel of the project. First,
2:41I'll define a few variables inside the
2:43root selector, a background color, a
2:45foreground color, and a separate
2:47variable for the transition block color.
2:49Keeping these at the top means we can
2:50adjust the entire palette from one place
2:52later. [music]
2:53Next, a quick reset, removing the
2:55default margins and paddings, and
2:57switching everything over to border-box
2:59sizing. [music] Now, let's style the
3:00navbar. I'll position it fixed at the
3:02top of the screen, so it stays visible
3:04across all pages, and use flexbox to
3:07push the brand name to the left and the
3:08nav links to the right. I'll also give
3:10it a Z index, so it always sits above
3:12the page content. Then the nav links
3:14themselves, I'll lay them [music] out in
3:16a row with some spacing between them,
3:18add a bit of padding to each one, and
3:20style the links using the Instrument
3:21Sans font and our foreground color, so
3:23they stay readable against the dark
3:25backgrounds. Next, the hero sections.
3:28Each one [music] takes up the full
3:29viewport height, and I'll center the
3:30content both horizontally and vertically
3:33using flexbox. I'll also hide any
3:35overflow, so nothing spills outside the
3:37viewport [music] during the transition.
3:38Then I'll assign each page its own
3:40background image using the class we set
3:42up earlier. Genesis gets the first
3:44image, Threshold gets the second, and
3:46Sanctum gets the third. Each one is set
3:48to cover the full section and stay
3:49centered. For the heading, I'll use the
3:52Instrument Serif font. The font size
3:54scales fluidly using clamp, so it looks
3:56good on every screen size, and the line
3:58height is kept at one, so the large text
4:00sits cleanly in the center of the
4:02screen. Then a media query for smaller
4:04screens, I'll stack the nav links
4:06vertically on the right side, so the
4:07layout holds up nicely on mobile as
4:09well. Now the last few things we need
4:11are the styles for the transition
4:13overlay itself. First, the transition
4:15[music] grid. This is the container that
4:17will sit fixed on top of everything
4:19else. It covers the entire viewport,
4:21uses flexbox in a column direction,
4:23ignores pointer events, so it never
4:25blocks any clicks, and hides overflow,
4:27so none of the animated blocks spill
4:29outside the screen. Then the transition
4:31block. Each block inside the grid gets a
4:33flex value [music] of one, which means
4:35all four of them share the container
4:36height equally, no matter what size the
4:38screen is. The background comes from the
4:40transition color variable we defined at
4:42the top. We start each block at a scale
4:44of zero on the x-axis, [music] so they
4:46are invisible by default, and set the
4:48transform origin to the left, which is
4:50what [music] makes them grow outward
4:51from the left edge. We are also adding
4:53will change transform here to hint to
4:55the browser that these elements are
4:57going to be animated, which helps
4:58[music] keep things smooth. Next, the
5:00transition text. This is a separate
5:02layer that sits on top of the blocks
5:04with a higher z-index. It covers the
5:06full viewport, centers the text in the
5:08middle of the screen, and ignores
5:09[music] pointer events as well. Keeping
5:11it outside the grid means it stays
5:13completely unaffected by the block
5:15scaling underneath it. Then the heading
5:17inside it, Instrument Serif again,
5:19scaling fluidly with clamp, and this
5:21time colored with our background
5:22variable. Since the blocks are light and
5:24we want the text to read against them.
5:26And finally, the word class. Every word
5:28inside that heading starts pushed fully
5:30[music] down out of view, and we add
5:32will change transform here as well. And
5:34that is all the styling done. The
5:36project is looking good on the side, and
5:38now we can start working on the
5:40transition provider, which is what
5:41actually handles the page changes and
5:43drives the entire animation. [music]
5:45Before we can touch the transition
5:46logic, we need two packages. So, I'll
5:48open the integrated terminal and pull
5:50them both in at once. The first one is
5:52next transition router. This is what
5:54makes animated page transitions in next
5:56JS actually manageable. It sits between
5:59the link click and the page swap and
6:01says a leave callback and an enter
6:03callback and then waits for us. Whatever
6:05animation we run inside those callbacks,
6:06[music]
6:07it will hold the page change until we
6:09tell it we are done and it does not care
6:11which animation library we use. The
6:13second [music] one is GSAP which is what
6:15we'll use to drive the actual animation.
6:17Once both finish installing, I'll close
6:19the terminal. Now, I'll create a new
6:21folder called providers in the source
6:23directory and inside it a file called
6:26transition provider.jsx.
6:28Everything that controls the transition
6:30is going to live in this one file and it
6:32will sit at the very top of the app
6:34wrapping every single page. Let's get
6:36the basic structure down first. So, I'll
6:38paste this in. Right at the top, we are
6:40marking this as a [music] client
6:41component. We'll be reaching into the
6:43DOM and running animations on it and
6:45neither of those work on the server. So,
6:47this line is not optional. Then, we
6:49bring in the transition router itself
6:51and wrap the children with it. The
6:53important bit here is the auto prop.
6:55With auto enabled, the router watches
6:57for clicks on any next JS link component
6:59and intercepts [music]
7:00them for us. It figures out that a
7:02navigation is happening, pauses it and
7:04fires our callbacks [music]
7:05which means every link in the navbar we
7:07built earlier is already wired into
7:09this. We just have not given it anything
7:11to run yet. Back over in the layout
7:13file, I'll import the provider and wrap
7:15the entire body content with it. The
7:17navbar and the page children both sit
7:19inside now. So, the provider has control
7:21over every route change on the site.
7:23Nothing will look any different [music]
7:24at this point. The plumbing is
7:26connected, but there is no animation on
7:28the other end of it. Let's go build
7:30that. Let's start by bringing in the
7:32imports we need. First, use ref from
7:34React since we are going to be holding
7:36on to the actual DOM elements and
7:38handing them straight to GSAP. Just
7:40above the component, I'll define a
7:41constant for the number of rows and set
7:44it to four. That is how many blocks the
7:46transition is going to have. [music] And
7:47keeping it up here means we can change
7:49the whole feel of the effect from one
7:51line later. Inside the component, I'll
7:53[music] create two refs. The first one,
7:55grid ref, points to the container that
7:57holds all our blocks. The second one,
7:59blocks ref, starts as an empty array and
8:01will store every individual block.
8:03Keeping them in refs means GSAP [music]
8:05can target them directly without React
8:07getting in the way. Now, let's render
8:09the blocks. Inside the transition
8:10router, right before the children, I'll
8:12add a div, attach the grid ref to it,
8:14and give it the transition grid class.
8:16Then I'll loop over our row count and
8:18return a block for each one with the
8:20transition block class and a ref
8:22callback that pushes each element into
8:23the blocks array. And that is the entire
8:26grid. Four divs, because we set flex to
8:28one on the block class earlier, they
8:30automatically split the container height
8:32evenly between them, no matter what size
8:34the screen is. Next, let's bring in GSAP
8:37along with a plugin called CustomEase.
8:39CustomEase lets us define our own easing
8:41curve instead of relying on the built-in
8:43ones. So, I'll register it and then
8:45create a curve called hop. This gives us
8:47a much sharper motion than something
8:48like power four. It accelerates hard out
8:51of the gate and then breaks hard right
8:53at the end. We are creating it outside
8:55the component, so it only ever runs once
8:57and not on every single render. Now, for
8:59the fun part, let's write the leave
9:01animation. I'll define a function called
9:03animateIn, which takes an onComplete
9:05callback. Inside, I'll create a GSAP
9:08timeline and pass that callback straight
9:10to it. So, the timeline fires it
9:11automatically the moment [music] the
9:12animation finishes. First, I'll set the
9:15transform origin on every block to the
9:16left and reset their scale on the x-axis
9:19back to zero. This makes sure we are
9:21always starting from a clean state. Then
9:23I'll animate all four blocks up to a
9:24scale of one. Because the origin is on
9:26the left, each block grows outward from
9:28the left edge, sweeping across the
9:30screen. I'm using our hop ease here and
9:32adding a small stagger. So, instead of
9:34all four moving together, each one
9:36starts a fraction of a second after the
9:38one above it. That is what gives the
9:39effect its cascade. Finally, I return
9:42the timeline so we can kill it later if
9:44we need to. Now, the enter animation,
9:46animate out works exactly the same way,
9:48but in reverse. The key difference is
9:50right here. This time, the transform
9:52origin is set to the right. So, when we
9:54animate the blocks back down to zero,
9:56they do not shrink back the way they
9:57came. They collapse toward the left,
9:59continuing in the same direction they
10:01were already traveling. That one change
10:02is the entire trick behind this effect.
10:05grows from the left and then it
10:06collapses toward the left. Put those two
10:09halves together and it reads as one
10:10continuous sweep moving across the
10:12screen, rather than a bar that grows and
10:14then reverses. Same duration, same ease,
10:17same stagger. Now, let's wire both of
10:19these into the transition router. I'll
10:21add the leave callback and call animate
10:23in, passing it the next function
10:24directly. The router will wait until our
10:27timeline finishes and calls next before
10:29it swaps the page. Then the enter
10:31callback, doing exactly the same thing
10:33with animate out. And in both cases,
10:34[music]
10:35I return a cleanup function that kills
10:37the timeline if the animation needs to
10:39stop early. And with that, the
10:40transition is live. Click any link and
10:43the block sweep in, the page changes
10:44underneath, and the block [music] sweep
10:46back out.
10:50Now, let's add the text. For this, we
10:52need one more plugin, Split Text. I'll
10:54register it alongside custom ease and
10:56[music] then add three more refs,
10:58heading ref for the actual H1 element,
11:00words ref, which will hold each
11:02individual word once it is split, and
11:04split ref [music] to hold on to the
11:05Split Text instance itself, so we can
11:08clean it up later. Down in the return,
11:10below our grid, I'll add another div
11:12with the transition text class and drop
11:14the H1 inside it with our brand name and
11:16the heading ref attached. Remember, this
11:18sits on a higher layer than the blocks,
11:20so it stays completely unaffected by
11:22them scaling underneath. Now, let's
11:24[music] split it. I'll bring in use
11:26effect and inside it create a new Split
11:28Text instance [music] on our heading. We
11:30are splitting by words, giving each word
11:32the word class, and then this line here
11:34is the important one. Mask set to words.
11:36[music]
11:37This tells split text to wrap every
11:39single word in its own container with
11:41overflow hidden, which means we do not
11:43have to write a single line of extra
11:45markup or CSS to get that clipping
11:47effect. It handles it for us. Then I
11:49store the words in our ref and use
11:51[music] GSAP to push all of them fully
11:53down and out of view, so nothing is
11:55visible by default. And I return a
11:57cleanup function that reverts the split,
11:59which stops us from accidentally
12:00splitting the same heading twice on a
12:02refresh. Back in animate [music] in,
12:04I'll reset the words to their hidden
12:06position, right alongside where we reset
12:08the blocks. [music] Then, after the
12:10block animation, I'll bring the words up
12:12into view. And notice this position
12:13parameter at the end. Without it, the
12:15words would sit and wait for the blocks
12:17to completely finish. Instead, this
12:19pulls the animation backwards, so the
12:21words start rising while the last block
12:23is still sweeping across. Those two
12:25motions overlap [music]
12:26and the whole thing feels like one
12:27movement instead of two. I'm also using
12:30a different ease here [music] on
12:31purpose. The blocks get hop, which is
12:33sharp and mechanical. The words get
12:35power four out, so they rise quickly and
12:37settle softly. Now, in animate [music]
12:39out, we do the same thing in reverse,
12:41but the order flips. The words leave
12:43first, dropping back down out of view,
12:46and then [music] the blocks follow,
12:47pulled almost entirely back on top of
12:49the word animation, so the text and the
12:51[music] blocks leave together as one
12:53motion rather than one waiting on the
12:55other. And that is the transition
12:57provider complete. When we leave a page,
12:59the blocks sweep in from the left and
13:01the brand name rises up behind them.
13:03When we enter the new page, the text
13:04drops away and the blocks continue in
13:06the same direction, wiping the screen
13:08clean. The whole thing comes in at under
13:10100 lines, and every part of it is
13:12adjustable. Hope you found the video
13:14helpful. See you in the next one.
13:29>> Mhm.