├── .eslintrc.json
├── .gitignore
├── README.md
├── jsconfig.json
├── next.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── next.svg
├── thirteen.svg
└── vercel.svg
└── src
├── components
├── Footer.js
├── Hero.js
└── ScrollSection.js
├── pages
├── _app.js
├── _document.js
├── about.js
├── api
│ └── hello.js
└── index.js
└── styles
└── globals.css
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Next.js Horizontal Scroll With Gsap ScrollTrigger
2 |
3 | Repository of the [YouTube video](https://youtu.be/PeFqGrEHnp0) on how to develop a horizontal scroll section using Next.js and Gsap ScrollTrigger.
4 |
5 | ## Stack used
6 |
7 | - Next.js
8 | - Gsap Scrolltrigger
9 | - CSS
10 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./src/*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-gsap-scroll",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next/font": "13.1.6",
13 | "eslint": "8.34.0",
14 | "eslint-config-next": "13.1.6",
15 | "gsap": "^3.11.4",
16 | "next": "13.1.6",
17 | "react": "18.2.0",
18 | "react-dom": "18.2.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IvanSmiths/next-gsap-scroll/6f657937f66497d7d89d3993c4c5a8cdbf238ad4/public/favicon.ico
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Footer() {
4 | return (
5 |
8 | );
9 | }
10 |
11 | export default Footer;
12 |
--------------------------------------------------------------------------------
/src/components/Hero.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Hero() {
4 | return (
5 |
6 | John Doe
7 | Frontend Developer
8 |
9 | );
10 | }
11 |
12 | export default Hero;
13 |
--------------------------------------------------------------------------------
/src/components/ScrollSection.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from "react";
2 | import { gsap } from "gsap";
3 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
4 |
5 |
6 | function ScrollSection() {
7 | const sectionRef = useRef(null);
8 | const triggerRef = useRef(null);
9 |
10 | gsap.registerPlugin(ScrollTrigger);
11 |
12 | useEffect(() => {
13 | const pin = gsap.fromTo(
14 | sectionRef.current,
15 | {
16 | translateX: 0,
17 | },
18 | {
19 | translateX: "-300vw",
20 | ease: "none",
21 | duration: 1,
22 | scrollTrigger: {
23 | trigger: triggerRef.current,
24 | start: "top top",
25 | end: "2000 top",
26 | scrub: 0.6,
27 | pin: true,
28 | },
29 | }
30 | );
31 | return () => {
32 | {/* A return function for killing the animation on component unmount */ }
33 | pin.kill();
34 | };
35 | }, []);
36 |
37 | return (
38 |
39 | {/* The section up act just as a wrapper. If the trigger (below) is the
40 | first jsx element in the component, you get an error on route change */}
41 |
42 | {/* The div below act just as a trigger. As the doc suggests, the trigger and
43 | the animation should alway be two separated refs */}
44 |
45 |
46 |
47 |
Section 1
48 |
49 |
50 |
Section 2
51 |
52 |
53 |
Section 3
54 |
55 |
56 |
Section 4
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
64 | export default ScrollSection;
65 |
--------------------------------------------------------------------------------
/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '@/styles/globals.css'
2 |
3 | export default function App({ Component, pageProps }) {
4 | return
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from 'next/document'
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function about() {
4 | return about
;
5 | }
6 |
7 | export default about;
8 |
--------------------------------------------------------------------------------
/src/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Footer from "@/components/Footer";
2 | import Hero from "@/components/Hero";
3 | import ScrollSection from "@/components/ScrollSection";
4 |
5 | export default function Home() {
6 | return (
7 | <>
8 |
9 |
10 |
11 | >
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | * {
4 | padding: 0;
5 | margin: 0;
6 | box-sizing: border-box;
7 | font-family: "Montserrat", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
8 | Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
9 | }
10 |
11 | :root {
12 | --backgroundColor: #292929;
13 | --primaryColor: #0aa1ff;
14 | --primaryTextColor: #e6e6e6;
15 |
16 | --headlineBig: clamp(5rem, 9.9vw, 15rem);
17 | --headlineMedium: clamp(1rem, 2vw, 4rem);
18 | --headlineSmall: clamp(0.6rem, 0.75vw, 1.3rem);
19 | --spacing: clamp(0.9rem, 0.75vw, 1.3rem);
20 | }
21 |
22 | body {
23 | background-color: var(--backgroundColor);
24 | }
25 |
26 | .hero__header {
27 | height: 100vh;
28 | display: flex;
29 | text-align: center;
30 | flex-direction: column;
31 | justify-content: center;
32 | align-items: center;
33 | gap: 2rem;
34 | }
35 |
36 | .hero__header h1 {
37 | font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
38 | font-size: var(--headlineBig);
39 | color: var(--primaryTextColor);
40 | }
41 |
42 | .hero__header h2 {
43 | letter-spacing: var(--headlineSmall);
44 | font-size: var(--headlineSmall);
45 | text-transform: uppercase;
46 | color: var(--primaryColor);
47 | }
48 |
49 | .scroll-section-outer {
50 | overflow: hidden;
51 | }
52 |
53 | .scroll-section-inner {
54 | height: 100vh;
55 | width: 400vw;
56 | display: flex;
57 | flex-direction: row;
58 | position: relative;
59 | }
60 |
61 | .scroll-section {
62 | height: 100vh;
63 | width: 100vw;
64 | display: flex;
65 | justify-content: center;
66 | align-items: center;
67 | }
68 |
69 | .scroll-section h3 {
70 | color: var(--primaryTextColor);
71 | letter-spacing: var(--headlineMedium);
72 | font-size: var(--headlineMedium);
73 | text-transform: uppercase;
74 | }
75 |
76 | .footer {
77 | height: 100vh;
78 | display: flex;
79 | justify-content: center;
80 | align-items: center;
81 | }
82 |
83 | .footer span {
84 | font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
85 | font-size: var(--headlineBig);
86 | color: var(--primaryTextColor);
87 | }
--------------------------------------------------------------------------------