├── public ├── i1.jpg ├── i2.jpg ├── i3.jpg ├── i4.jpg ├── i5.jpg ├── i6.jpg ├── i7.jpg ├── i8.jpg └── vite.svg ├── utils └── gsap-bonus.tgz ├── vite.config.js ├── src ├── components │ └── scroll.ts ├── perspective-gallery.scss ├── style.css ├── parallax-sections.css └── main.ts ├── .gitignore ├── package.json ├── tsconfig.json └── index.html /public/i1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i1.jpg -------------------------------------------------------------------------------- /public/i2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i2.jpg -------------------------------------------------------------------------------- /public/i3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i3.jpg -------------------------------------------------------------------------------- /public/i4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i4.jpg -------------------------------------------------------------------------------- /public/i5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i5.jpg -------------------------------------------------------------------------------- /public/i6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i6.jpg -------------------------------------------------------------------------------- /public/i7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i7.jpg -------------------------------------------------------------------------------- /public/i8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/public/i8.jpg -------------------------------------------------------------------------------- /utils/gsap-bonus.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/J0SUKE/gsap-scrolltrigger/HEAD/utils/gsap-bonus.tgz -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { defineConfig } from 'vite' 3 | 4 | export default defineConfig({ 5 | server: { 6 | host: true, 7 | }, 8 | }) 9 | -------------------------------------------------------------------------------- /src/components/scroll.ts: -------------------------------------------------------------------------------- 1 | import gsap from 'gsap' 2 | import ScrollSmoother from 'gsap/dist/ScrollSmoother' 3 | gsap.registerPlugin(ScrollSmoother) 4 | 5 | export default class Scroll { 6 | constructor() { 7 | ScrollSmoother.create({ 8 | smooth: 1, 9 | effects: true, 10 | normalizeScroll: true, 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parallax-sections", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^5.5.3", 13 | "vite": "^5.4.0" 14 | }, 15 | "dependencies": { 16 | "gsap": "file:utils/gsap-bonus.tgz", 17 | "sass": "^1.77.8" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true 21 | }, 22 | "include": ["src"] 23 | } 24 | -------------------------------------------------------------------------------- /src/perspective-gallery.scss: -------------------------------------------------------------------------------- 1 | .perspective-gallery 2 | { 3 | height: 300dvh; 4 | } 5 | 6 | 7 | .perspective-gallery-container { 8 | height: 100dvh; 9 | overflow: hidden; 10 | box-sizing: border-box; 11 | position: relative; 12 | perspective: 100dvh; 13 | } 14 | 15 | .perspective-gallery__media-wrapper { 16 | position: absolute; 17 | inset: 0; 18 | height: 100dvh; 19 | width: 100%; 20 | } 21 | .perspective-gallery-wrapper 22 | { 23 | position: absolute; 24 | inset: 0; 25 | height: 100dvh; 26 | width: 100%; 27 | } 28 | 29 | 30 | .perspective-gallery__media{ 31 | 32 | img { 33 | max-width: 100%; 34 | object-fit: cover; 35 | } 36 | 37 | position: absolute; 38 | 39 | &__1 40 | { 41 | width: 20%; 42 | left: 15%; 43 | top: 8%; 44 | } 45 | &__2 46 | { 47 | width: 20.8%; 48 | right: 14%; 49 | top: 35%; 50 | } 51 | &__3 52 | { 53 | width: 20%; 54 | left: 20%; 55 | top: 69%; 56 | 57 | } 58 | &__4 59 | { 60 | width: 20%; 61 | right: 37.5%; 62 | top: 69%; 63 | } 64 | &__5 65 | { 66 | width: 25%; 67 | left: 50%; 68 | top: 50%; 69 | transform: translate(-50%,-50%); 70 | } 71 | &__6 72 | { 73 | width: 34%; 74 | left: 37.5%; 75 | top: 8%; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | img, 28 | ins, 29 | kbd, 30 | q, 31 | s, 32 | samp, 33 | small, 34 | strike, 35 | strong, 36 | sub, 37 | sup, 38 | tt, 39 | var, 40 | b, 41 | u, 42 | i, 43 | center, 44 | dl, 45 | dt, 46 | dd, 47 | ol, 48 | ul, 49 | li, 50 | fieldset, 51 | form, 52 | label, 53 | legend, 54 | table, 55 | caption, 56 | tbody, 57 | tfoot, 58 | thead, 59 | tr, 60 | th, 61 | td, 62 | article, 63 | aside, 64 | canvas, 65 | details, 66 | embed, 67 | figure, 68 | figcaption, 69 | footer, 70 | header, 71 | hgroup, 72 | menu, 73 | nav, 74 | output, 75 | ruby, 76 | section, 77 | summary, 78 | time, 79 | mark, 80 | audio, 81 | video { 82 | margin: 0; 83 | padding: 0; 84 | border: 0; 85 | font-size: 100%; 86 | font: inherit; 87 | vertical-align: baseline; 88 | } 89 | /* HTML5 display-role reset for older browsers */ 90 | article, 91 | aside, 92 | details, 93 | figcaption, 94 | figure, 95 | footer, 96 | header, 97 | hgroup, 98 | menu, 99 | nav, 100 | section { 101 | display: block; 102 | } 103 | body { 104 | line-height: 1; 105 | } 106 | ol, 107 | ul { 108 | list-style: none; 109 | } 110 | blockquote, 111 | q { 112 | quotes: none; 113 | } 114 | blockquote:before, 115 | blockquote:after, 116 | q:before, 117 | q:after { 118 | content: ''; 119 | content: none; 120 | } 121 | table { 122 | border-collapse: collapse; 123 | border-spacing: 0; 124 | } 125 | 126 | #app { 127 | position: relative; 128 | z-index: 1; 129 | font-family: Helvetica; 130 | display: flex; 131 | flex-direction: column; 132 | } 133 | 134 | header, 135 | footer { 136 | font-size: 2vmax; 137 | display: flex; 138 | justify-content: center; 139 | align-items: center; 140 | height: 100dvh; 141 | } 142 | 143 | .spacer { 144 | height: 100vh; 145 | } 146 | -------------------------------------------------------------------------------- /src/parallax-sections.css: -------------------------------------------------------------------------------- 1 | .parallax-sections-wrapper { 2 | height: 400vh; 3 | } 4 | 5 | .parallax-sections { 6 | position: relative; 7 | width: 100%; 8 | height: 100dvh; 9 | overflow: hidden; 10 | color: white; 11 | } 12 | 13 | .pinned-section { 14 | position: absolute; 15 | overflow: hidden; 16 | inset: 0; 17 | display: flex; 18 | max-width: 100%; 19 | height: 100dvh; 20 | } 21 | 22 | .pinned-section:nth-of-type(1) { 23 | z-index: 3; 24 | } 25 | .pinned-section:nth-of-type(2) { 26 | z-index: 2; 27 | } 28 | .pinned-section:nth-of-type(3) { 29 | z-index: 1; 30 | } 31 | 32 | .pinned-section__background { 33 | position: absolute; 34 | inset: 0; 35 | z-index: 0; 36 | overflow: hidden; 37 | } 38 | 39 | .pinned-section__background img { 40 | width: 100%; 41 | height: 100%; 42 | object-fit: cover; 43 | display: block; 44 | filter: blur(12px); 45 | transform: scale(1.1); 46 | } 47 | 48 | .pinned-section h3 { 49 | font-size: 3vmax; 50 | } 51 | 52 | .pinned-section ul { 53 | display: flex; 54 | flex-direction: column; 55 | gap: 0.75vmax; 56 | text-transform: uppercase; 57 | font-size: 1.1vmax; 58 | letter-spacing: 0lvw; 59 | } 60 | 61 | .pinned-section__right { 62 | display: flex; 63 | position: relative; 64 | z-index: 1; 65 | width: 50%; 66 | margin-left: 50%; 67 | flex-direction: column; 68 | gap: 6vmax; 69 | justify-content: center; 70 | } 71 | 72 | .before, 73 | .after { 74 | height: 100dvh; 75 | display: flex; 76 | justify-content: center; 77 | align-items: center; 78 | color: black; 79 | font-size: 2vmax; 80 | } 81 | 82 | .images-container { 83 | position: absolute; 84 | z-index: 10; 85 | width: 35%; 86 | aspect-ratio: 1/1; 87 | left: 7.5%; 88 | top: 50%; 89 | transform: translateY(-50%); 90 | overflow: hidden; 91 | } 92 | 93 | .images-container .img { 94 | position: absolute; 95 | inset: 0; 96 | } 97 | 98 | .images-container .img:nth-last-of-type(1) { 99 | z-index: 0; 100 | } 101 | .images-container .img:nth-last-of-type(2) { 102 | z-index: 1; 103 | } 104 | .images-container .img:nth-last-of-type(3) { 105 | z-index: 2; 106 | } 107 | 108 | .images-container img { 109 | width: 100%; 110 | height: 100%; 111 | display: block; 112 | object-fit: cover; 113 | } 114 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import './parallax-sections.css' 3 | import './perspective-gallery.scss' 4 | import Scroll from './components/scroll' 5 | import gsap from 'gsap' 6 | import { ScrollTrigger } from 'gsap/dist/ScrollTrigger' 7 | 8 | gsap.registerPlugin(ScrollTrigger) 9 | 10 | class App { 11 | scroll: Scroll 12 | 13 | constructor() { 14 | this.scroll = new Scroll() 15 | 16 | this.setupPerspectiveGallery() 17 | this.setupParallaxSections() 18 | } 19 | 20 | setupPerspectiveGallery() { 21 | const container = document.querySelector('.perspective-gallery') 22 | const perspectiveWrapper = document.querySelector('.perspective-gallery-wrapper') 23 | const medias = [...document.querySelectorAll('.perspective-gallery__media-wrapper')] as HTMLElement[] 24 | const scales: number[] = [] 25 | medias.forEach((media) => { 26 | scales.push(parseFloat(media.dataset.endScale as string)) 27 | }) 28 | 29 | const tl = gsap.timeline({ 30 | scrollTrigger: { 31 | trigger: container, 32 | start: 'top top', 33 | end: () => '+' + window.innerHeight * 3, 34 | scrub: true, 35 | pin: true, 36 | anticipatePin: 1, 37 | }, 38 | }) 39 | 40 | tl.to(perspectiveWrapper, { 41 | z: '50dvh', 42 | }) 43 | 44 | medias.forEach((media, index) => { 45 | tl.to( 46 | media, 47 | { 48 | scale: scales[index], 49 | }, 50 | '<=' 51 | ) 52 | }) 53 | } 54 | 55 | setupParallaxSections() { 56 | const sections = [...document.querySelectorAll('.pinned-section')] as HTMLElement[] 57 | const container = document.querySelector('.parallax-sections') 58 | const images = [...document.querySelectorAll('.img')] 59 | 60 | gsap.set(sections[0], { clipPath: 'polygon(0 0, 0 100%, 100% 100%, 100% 0)' }) 61 | gsap.set(sections[1], { clipPath: 'polygon(0 0, 0 100%, 100% 100%, 100% 0)' }) 62 | 63 | gsap.set(images[0], { clipPath: 'polygon(0 0, 0 100%, 100% 100%, 100% 0)' }) 64 | gsap.set(images[1], { clipPath: 'polygon(0 0, 0 100%, 100% 100%, 100% 0)' }) 65 | 66 | const tl = gsap.timeline({ 67 | scrollTrigger: { 68 | trigger: container, 69 | start: 'top top', 70 | end: () => '+' + window.innerHeight * 3, 71 | scrub: true, 72 | pin: true, 73 | anticipatePin: 1, 74 | }, 75 | }) 76 | 77 | tl.to(sections[0], { 78 | clipPath: 'polygon(0 0, 0 0%, 100% 0%, 100% 0)', 79 | ease: 'none', 80 | }).to(sections[1], { 81 | clipPath: 'polygon(0 0, 0 0%, 100% 0%, 100% 0)', 82 | ease: 'none', 83 | }) 84 | 85 | const imagesTl = gsap.timeline({ 86 | scrollTrigger: { 87 | trigger: container, 88 | start: 'top top', 89 | end: () => `+` + window.innerHeight * 3, 90 | scrub: 1, 91 | }, 92 | }) 93 | 94 | imagesTl.to(images[0], { 95 | clipPath: 'polygon(0 0, 0 0%, 100% 0%, 100% 0)', 96 | ease: 'none', 97 | }) 98 | 99 | imagesTl.to(images[1], { 100 | clipPath: 'polygon(0 0, 0 0%, 100% 0%, 100% 0)', 101 | ease: 'none', 102 | }) 103 | } 104 | } 105 | 106 | export default new App() 107 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + TS 8 | 9 | 10 |
11 |
scroll down
12 | 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 60 |
61 |
62 |

Teodor Kuduschiev

63 |
    64 |
  • Osaka, Japan
  • 65 |
  • Publiée il y a 7 jours
  • 66 |
  • FUJIFILM, X-T1
  • 67 |
68 |
69 |
70 |
71 |
72 | 73 |
74 |
75 |

Vadim Berg

76 |
    77 |
  • Warsaw, Poland
  • 78 |
  • Publiée il y a 3 jours
  • 79 |
  • Canon, EOS 6D Mark II
  • 80 |
81 |
82 |
83 |
84 |
85 | 86 |
87 |
88 |

Tiago Ferreira

89 |
    90 |
  • Lisboa, Portugal
  • 91 |
  • Publiée il y a 18 jours
  • 92 |
  • SONY, ILCE-7M3
  • 93 |
94 |
95 |
96 |
97 |
98 | 99 |
100 | 101 | 102 | 103 | --------------------------------------------------------------------------------