├── .eslintrc.json ├── public ├── favicon.ico └── parallax │ ├── mountain-3.svg │ ├── cloud-bottom.svg │ ├── clouds-bottom.svg │ ├── clouds-left.svg │ ├── clouds-right.svg │ ├── sun.svg │ ├── mountain-2.svg │ ├── mountain-1.svg │ └── stars.svg ├── jsconfig.json ├── next.config.js ├── src ├── pages │ ├── _app.js │ ├── _document.js │ └── index.js ├── styles │ └── globals.css └── components │ └── Parallax.js ├── README.md ├── package.json └── .gitignore /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanSmiths/tutorial-next-gsap-parallax/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css' 2 | 3 | export default function App({ Component, pageProps }) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Next.js Parallax Effect With Gsap ScrollTrigger 2 | 3 | Repository of the [YouTube video](https://youtu.be/alGnk3iMaYE) on how to make a parallax effect using Next.js and Gsap ScrollTrigger. 4 | 5 | ## Stack used 6 | 7 | - Next.js 8 | - Gsap ScrollTrigger 9 | - CSS 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tutorial-next-gsap-parallax", 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 | "eslint": "8.36.0", 13 | "eslint-config-next": "13.2.4", 14 | "gsap": "^3.11.5", 15 | "next": "13.2.4", 16 | "react": "18.2.0", 17 | "react-dom": "18.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /public/parallax/mountain-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/_document.js: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /public/parallax/cloud-bottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/parallax/clouds-bottom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import Parallax from '@/components/Parallax' 2 | 3 | function Home() { 4 | return ( 5 | <> 6 | 7 |
8 |

9 | Lorem ipsum 10 |

11 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Sit laborum ipsam corrupti asperiores magnam quos cumque animi tempore vero repellendus, harum odio neque quis, non temporibus. Inventore asperiores repudiandae praesentium ut, fugit quo esse, placeat ullam quibusdam perspiciatis delectus ducimus nihil. Dolorum nam veniam aperiam sapiente corporis! Quisquam, veritatis repellendus?

12 |
13 | 14 | ) 15 | } 16 | 17 | export default Home -------------------------------------------------------------------------------- /public/parallax/clouds-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/parallax/clouds-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/parallax/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/parallax/mountain-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | *, 2 | html, 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | :root { 10 | --primaryColor: #282a57; 11 | --secondaryColor: #e4e4e4; 12 | } 13 | 14 | body { 15 | font-family: 'Montserrat', sans-serif; 16 | background-color: var(--primaryColor); 17 | color: var(--secondaryColor); 18 | } 19 | 20 | .parallax-outer { 21 | overflow: hidden; 22 | } 23 | 24 | .parallax { 25 | height: 110vh; 26 | width: 100%; 27 | position: relative; 28 | } 29 | 30 | .parallax img { 31 | position: absolute; 32 | } 33 | 34 | 35 | .mountain-3, 36 | .mountain-2, 37 | .mountain-1 { 38 | width: 100%; 39 | bottom: 0; 40 | z-index: 3; 41 | } 42 | 43 | .mountain-2 { 44 | bottom: 20px; 45 | z-index: 2; 46 | } 47 | 48 | .mountain-1 { 49 | bottom: 40px; 50 | z-index: 1; 51 | } 52 | 53 | .sun { 54 | top: 70%; 55 | left: 50%; 56 | transform: translate(-50%, -50%); 57 | width: 40%; 58 | } 59 | 60 | .clouds-left { 61 | left: 0; 62 | width: 20%; 63 | } 64 | 65 | .clouds-right { 66 | right: 0; 67 | width: 20%; 68 | } 69 | 70 | .clouds-bottom { 71 | bottom: 0px; 72 | width: 100%; 73 | } 74 | 75 | .stars { 76 | top: -550px; 77 | left: 0; 78 | width: 100%; 79 | } 80 | 81 | .copy { 82 | position: absolute; 83 | bottom: 0%; 84 | left: 50%; 85 | transform: translate(-50%, -50%); 86 | z-index: 0; 87 | color: var(--secondaryColor); 88 | display: flex; 89 | justify-content: center; 90 | align-items: center; 91 | flex-direction: column; 92 | opacity: 0; 93 | } 94 | 95 | .copy h1 { 96 | font-size: 10rem; 97 | } 98 | 99 | .copy span { 100 | background-color: var(--secondaryColor); 101 | color: var(--primaryColor); 102 | padding: 1rem; 103 | font-weight: 800; 104 | border-radius: 0.5rem; 105 | opacity: 0; 106 | } 107 | 108 | .about { 109 | color: var(--secondaryColor); 110 | padding-left: 10%; 111 | padding-bottom: 20%; 112 | } 113 | 114 | .about h2 { 115 | color: var(--secondaryColor); 116 | font-size: 5rem; 117 | } 118 | 119 | .about p { 120 | margin-top: 20px; 121 | width: 50%; 122 | } -------------------------------------------------------------------------------- /public/parallax/mountain-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/Parallax.js: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useState } from "react"; 2 | import { gsap } from "gsap"; 3 | import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; 4 | 5 | function Parallax() { 6 | 7 | const [background, setBackground] = useState(20) 8 | 9 | const parallaxRef = useRef(null); 10 | const mountain3 = useRef(null); 11 | const mountain2 = useRef(null); 12 | const mountain1 = useRef(null); 13 | const cloudsBottom = useRef(null); 14 | const cloudsLeft = useRef(null); 15 | const cloudsRight = useRef(null); 16 | const stars = useRef(null); 17 | const sun = useRef(null); 18 | const copy = useRef(null); 19 | const btn = useRef(null); 20 | 21 | useEffect(() => { 22 | let ctx = gsap.context(() => { 23 | gsap.registerPlugin(ScrollTrigger); 24 | var tl = gsap.timeline({ 25 | defaults: { duration: 1 }, 26 | scrollTrigger: { 27 | trigger: parallaxRef.current, 28 | start: "top top", 29 | end: "5000 bottom", 30 | scrub: true, 31 | pin: true, 32 | onUpdate: (self) => { 33 | setBackground(Math.ceil(self.progress * 100 + 20)) 34 | }, 35 | }, 36 | }); 37 | tl.to( 38 | mountain3.current, 39 | { 40 | y: "-=80", 41 | }, 42 | 0 43 | ); 44 | tl.to( 45 | mountain2.current, 46 | { 47 | y: "-=30", 48 | }, 49 | 0 50 | ); 51 | tl.to( 52 | mountain1.current, 53 | { 54 | y: "+=50", 55 | }, 56 | 0 57 | ); 58 | tl.to( 59 | stars.current, 60 | { 61 | top: 0, 62 | }, 63 | 0.5 64 | ); 65 | tl.to( 66 | cloudsBottom.current, 67 | { 68 | opacity: 0, 69 | duration: 0.5 70 | }, 71 | 0 72 | ); 73 | tl.to( 74 | cloudsLeft.current, 75 | { 76 | x: "-20%", 77 | opacity: 0, 78 | }, 79 | 0 80 | ); 81 | tl.to( 82 | cloudsRight.current, 83 | { 84 | x: "20%", 85 | opacity: 0, 86 | }, 87 | 0 88 | ); 89 | tl.to( 90 | sun.current, 91 | { 92 | y: "+=210", 93 | }, 94 | 0 95 | ); 96 | tl.to( 97 | copy.current, 98 | { 99 | y: "-250%", 100 | opacity: 1 101 | }, 102 | 0 103 | ); 104 | tl.to( 105 | btn.current, 106 | { 107 | opacity: 1, 108 | }, 109 | 1.5 110 | ); 111 | }); 112 | return () => ctx.revert(); 113 | }, []); 114 | 115 | return ( 116 |
117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
127 |

Journey

128 | Discover more 129 |
130 |
131 |
132 | ) 133 | } 134 | 135 | export default Parallax -------------------------------------------------------------------------------- /public/parallax/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | --------------------------------------------------------------------------------