├── .codesandbox
└── workspace.json
├── .gitignore
├── LICENSE
├── README.md
├── build
├── asset-manifest.json
├── favicon.ico
├── img
│ ├── 1.jpg
│ ├── 10.jpg
│ ├── 11.jpg
│ ├── 12.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ ├── 4.jpg
│ ├── 5.jpg
│ ├── 6.jpg
│ ├── 7.jpg
│ ├── 8.jpg
│ └── 9.jpg
├── index.html
└── static
│ ├── css
│ ├── main.05199dc0.css
│ └── main.05199dc0.css.map
│ └── js
│ ├── main.cdd68f98.js
│ ├── main.cdd68f98.js.LICENSE.txt
│ └── main.cdd68f98.js.map
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── img
│ ├── 1.jpg
│ ├── 10.jpg
│ ├── 11.jpg
│ ├── 12.jpg
│ ├── 2.jpg
│ ├── 3.jpg
│ ├── 4.jpg
│ ├── 5.jpg
│ ├── 6.jpg
│ ├── 7.jpg
│ ├── 8.jpg
│ └── 9.jpg
└── index.html
└── src
├── App.js
├── data
└── images.js
├── index.js
├── style
└── styles.css
├── three
├── Carousel.js
├── CarouselItem.js
├── Plane.js
└── PostProcessing.js
└── utils
└── index.js
/.codesandbox/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "responsive-preview": {
3 | "Mobile": [
4 | 320,
5 | 675
6 | ],
7 | "Tablet": [
8 | 1024,
9 | 765
10 | ],
11 | "Desktop": [
12 | 1400,
13 | 800
14 | ],
15 | "Desktop HD": [
16 | 1920,
17 | 1080
18 | ]
19 | }
20 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2009 - 2022 [Codrops](https://tympanus.net/codrops)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WebGL Carousel
2 |
3 | A recreation of a carousel similar to the one seen on alcre.co.kr, created by Eum Ray, using WebGL, react-three-fiber, and GSAP.
4 |
5 | 
6 |
7 | [Article on Codrops](https://tympanus.net/codrops/?p=71727)
8 |
9 | [Demo](http://tympanus.net/Development/WebGLCarousel/)
10 |
11 | ## Installation
12 |
13 | Install dependencies:
14 |
15 | ```
16 | npm install
17 | ```
18 |
19 | Compile the code for development and start a local server:
20 |
21 | ```
22 | npm start
23 | ```
24 |
25 | Create the build:
26 |
27 | ```
28 | npm run build
29 | ```
30 |
31 | ## Credits
32 |
33 | - Images generated with [Midjourney](https://midjourney.com/)
34 |
35 | ## Misc
36 |
37 | Follow Fabio: [Twitter](https://twitter.com/supahfunk), [Instagram](https://www.instagram.com/supahfunk/), [Codepen](https://codepen.io/supah), [LinkedIn](https://www.linkedin.com/in/fabio-ottaviani-82b0776/)
38 |
39 | Follow Codrops: [Twitter](http://www.twitter.com/codrops), [Facebook](http://www.facebook.com/codrops), [GitHub](https://github.com/codrops), [Instagram](https://www.instagram.com/codropsss/)
40 |
41 | ## License
42 | [MIT](LICENSE)
43 |
44 | Made with :blue_heart: by [Codrops](http://www.codrops.com)
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.css": "./static/css/main.05199dc0.css",
4 | "main.js": "./static/js/main.cdd68f98.js",
5 | "index.html": "./index.html",
6 | "main.05199dc0.css.map": "./static/css/main.05199dc0.css.map",
7 | "main.cdd68f98.js.map": "./static/js/main.cdd68f98.js.map"
8 | },
9 | "entrypoints": [
10 | "static/css/main.05199dc0.css",
11 | "static/js/main.cdd68f98.js"
12 | ]
13 | }
--------------------------------------------------------------------------------
/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/favicon.ico
--------------------------------------------------------------------------------
/build/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/1.jpg
--------------------------------------------------------------------------------
/build/img/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/10.jpg
--------------------------------------------------------------------------------
/build/img/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/11.jpg
--------------------------------------------------------------------------------
/build/img/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/12.jpg
--------------------------------------------------------------------------------
/build/img/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/2.jpg
--------------------------------------------------------------------------------
/build/img/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/3.jpg
--------------------------------------------------------------------------------
/build/img/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/4.jpg
--------------------------------------------------------------------------------
/build/img/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/5.jpg
--------------------------------------------------------------------------------
/build/img/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/6.jpg
--------------------------------------------------------------------------------
/build/img/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/7.jpg
--------------------------------------------------------------------------------
/build/img/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/8.jpg
--------------------------------------------------------------------------------
/build/img/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/build/img/9.jpg
--------------------------------------------------------------------------------
/build/index.html:
--------------------------------------------------------------------------------
1 |
WebGL Carousel | Codrops
--------------------------------------------------------------------------------
/build/static/css/main.05199dc0.css:
--------------------------------------------------------------------------------
1 | *,:after,:before{box-sizing:border-box}:root{--color-text:#343131;--color-bg:#d5d5d5;--color-link:#868282;--color-link-hover:#000;font-size:13px}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#d5d5d5;background-color:var(--color-bg);color:#343131;color:var(--color-text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;height:100vh;margin:0;overflow:hidden;scroll-behavior:none;-webkit-user-select:none;user-select:none}.js .loading:after,.js .loading:before{content:"";position:fixed;z-index:1000}.js .loading:before{background:#d5d5d5;background:var(--color-bg);height:100%;left:0;top:0;width:100%}.js .loading:after{-webkit-animation:loaderAnim .7s linear infinite alternate forwards;animation:loaderAnim .7s linear infinite alternate forwards;background:#868282;background:var(--color-link);border-radius:50%;height:60px;left:50%;margin:-30px 0 0 -30px;opacity:.4;top:50%;width:60px}@-webkit-keyframes loaderAnim{to{opacity:1;-webkit-transform:scale3d(.5,.5,1);transform:scale3d(.5,.5,1)}}@keyframes loaderAnim{to{opacity:1;-webkit-transform:scale3d(.5,.5,1);transform:scale3d(.5,.5,1)}}a{color:#868282;color:var(--color-link);cursor:pointer;text-decoration:none}a,a:hover{outline:none}a:hover{color:#000;color:var(--color-link-hover)}a:focus{background:#d3d3d3;outline:none}a:focus:not(:focus-visible){background:transparent}a:focus-visible{background:transparent;outline:2px solid red}.unbutton{background:none;border:0;cursor:pointer;font:inherit;margin:0;padding:0}.unbutton:focus{outline:none}.frame{grid-gap:.5rem;align-items:center;align-self:start;display:grid;grid-area:main;grid-template-areas:"title" "back" "prev" "sponsor";grid-template-columns:100%;justify-items:start;padding:1.5rem;pointer-events:none;position:relative;text-transform:uppercase;width:100%;z-index:1000}.frame,body #cdawrap{justify-self:start}.frame a{overflow:hidden;pointer-events:auto;position:relative;white-space:nowrap}.frame a:before{background:currentColor;content:"";height:1px;position:absolute;top:90%;-webkit-transform-origin:0 50%;transform-origin:0 50%;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;width:100%}.frame a:hover:before{-webkit-transform:scaleX(0);transform:scaleX(0);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.frame__title{font-size:inherit;font-weight:inherit;grid-area:title;margin:0}.frame__back{align-items:flex-end;display:flex;grid-area:back}.frame__prev{grid-area:prev}canvas{height:100vh}@media screen and (min-width:53em){.frame{grid-gap:2rem;align-content:space-between;grid-template-areas:"title back ..." "prev ... sponsor";grid-template-columns:auto auto 1fr;grid-template-rows:auto auto;height:100vh;justify-items:start;padding:.75rem;position:fixed}body #cdawrap{justify-self:end}}
2 | /*# sourceMappingURL=main.05199dc0.css.map*/
--------------------------------------------------------------------------------
/build/static/css/main.05199dc0.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"static/css/main.05199dc0.css","mappings":"AAAA,iBAGE,qBACF,CAEA,MAEE,oBAAqB,CACrB,kBAAmB,CACnB,oBAAqB,CACrB,uBAAwB,CAJxB,cAKF,CAEA,KAKE,kCAAmC,CACnC,iCAAkC,CAHlC,wBAAiC,CAAjC,gCAAiC,CADjC,aAAwB,CAAxB,uBAAwB,CAExB,gFAAsF,CAKtF,YAAa,CARb,QAAS,CAMT,eAAgB,CAChB,oBAAqB,CAErB,wBAAiB,CAAjB,gBACF,CAGA,uCAEE,UAAW,CACX,cAAe,CACf,YACF,CAEA,oBAKE,kBAA2B,CAA3B,0BAA2B,CAD3B,WAAY,CAFZ,MAAO,CADP,KAAM,CAEN,UAGF,CAEA,mBASE,mEAA6D,CAA7D,2DAA6D,CAD7D,kBAA6B,CAA7B,4BAA6B,CAF7B,iBAAkB,CAFlB,WAAY,CAFZ,QAAS,CAGT,sBAAuB,CAEvB,UAAY,CANZ,OAAQ,CAER,UAQF,CAEA,8BACE,GACE,SAAU,CACV,kCAA6B,CAA7B,0BACF,CACF,CALA,sBACE,GACE,SAAU,CACV,kCAA6B,CAA7B,0BACF,CACF,CAEA,EAEE,aAAwB,CAAxB,uBAAwB,CAExB,cAAe,CAHf,oBAIF,CAEA,UAJE,YAOF,CAHA,QACE,UAA8B,CAA9B,6BAEF,CAGA,QAIE,kBAAqB,CADrB,YAEF,CAEA,4BAGE,sBACF,CAEA,gBAKE,sBAAuB,CADvB,qBAEF,CAEA,UACE,eAAgB,CAChB,QAAS,CAIT,cAAe,CADf,YAAa,CADb,QAAS,CADT,SAIF,CAEA,gBACE,YACF,CAEA,OASE,cAAgB,CAKhB,kBAAmB,CAHnB,gBAAiB,CALjB,YAAa,CALb,cAAe,CAOf,mDAAoD,CADpD,0BAA2B,CAG3B,mBAAoB,CALpB,cAAe,CAQf,mBAAoB,CAVpB,iBAAkB,CAYlB,wBAAyB,CAXzB,UAAW,CAFX,YAcF,CAEA,qBANE,kBAQF,CAEA,SAGE,eAAgB,CAFhB,mBAAoB,CAGpB,iBAAkB,CAFlB,kBAGF,CAEA,gBAIE,uBAAwB,CAHxB,UAAW,CACX,UAAW,CAGX,iBAAkB,CAClB,OAAQ,CAER,8BAAwB,CAAxB,sBAAwB,CADxB,gCAA0B,CAA1B,wBAA0B,CAA1B,8CAA0B,CAJ1B,UAMF,CAEA,sBACE,2BAAoB,CAApB,mBAAoB,CACpB,iCAA0B,CAA1B,yBACF,CAEA,cAEE,iBAAkB,CAElB,mBAAoB,CAHpB,eAAgB,CAEhB,QAEF,CAEA,aAGE,oBAAqB,CADrB,YAAa,CADb,cAGF,CAEA,aACE,cACF,CAEA,OACE,YACF,CAEA,mCACE,OAQE,aAAc,CAFd,2BAA4B,CAD5B,uDAAwD,CAFxD,mCAAoC,CACpC,4BAA6B,CAF7B,YAAa,CAKb,mBAAoB,CAEpB,cAAgB,CARhB,cASF,CACA,cACI,gBACJ,CACF","sources":["style/styles.css"],"sourcesContent":["*,\n*::after,\n*::before {\n box-sizing: border-box;\n}\n\n:root {\n font-size: 13px;\n --color-text: #343131;\n --color-bg: #d5d5d5;\n --color-link: #868282;\n --color-link-hover: #000;\n}\n\nbody {\n margin: 0;\n color: var(--color-text);\n background-color: var(--color-bg);\n font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n overflow: hidden;\n scroll-behavior: none;\n height: 100vh;\n user-select: none;\n}\n\n/* Page Loader */\n.js .loading::before,\n.js .loading::after {\n content: '';\n position: fixed;\n z-index: 1000;\n}\n\n.js .loading::before {\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: var(--color-bg);\n}\n\n.js .loading::after {\n top: 50%;\n left: 50%;\n width: 60px;\n height: 60px;\n margin: -30px 0 0 -30px;\n border-radius: 50%;\n opacity: 0.4;\n background: var(--color-link);\n animation: loaderAnim 0.7s linear infinite alternate forwards;\n\n}\n\n@keyframes loaderAnim {\n to {\n opacity: 1;\n transform: scale3d(0.5,0.5,1);\n }\n}\n\na {\n text-decoration: none;\n color: var(--color-link);\n outline: none;\n cursor: pointer;\n}\n\na:hover {\n color: var(--color-link-hover);\n outline: none;\n}\n\n/* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */\na:focus {\n /* Provide a fallback style for browsers\n that don't support :focus-visible */\n outline: none;\n background: lightgrey;\n}\n\na:focus:not(:focus-visible) {\n /* Remove the focus indicator on mouse-focus for browsers\n that do support :focus-visible */\n background: transparent;\n}\n\na:focus-visible {\n /* Draw a very noticeable focus style for\n keyboard-focus on browsers that do support\n :focus-visible */\n outline: 2px solid red;\n background: transparent;\n}\n\n.unbutton {\n background: none;\n border: 0;\n padding: 0;\n margin: 0;\n font: inherit;\n cursor: pointer;\n}\n\n.unbutton:focus {\n outline: none;\n}\n\n.frame {\n grid-area: main;\n z-index: 1000;\n position: relative;\n width: 100%;\n padding: 1.5rem;\n display: grid;\n grid-template-columns: 100%;\n grid-template-areas: 'title' 'back' 'prev' 'sponsor';\n grid-gap: 0.5rem;\n justify-items: start;\n align-self: start;\n justify-self: start;\n pointer-events: none;\n align-items: center;\n text-transform: uppercase;\n}\n\nbody #cdawrap {\n justify-self: start;\n}\n\n.frame a {\n pointer-events: auto;\n white-space: nowrap;\n overflow: hidden;\n position: relative;\n}\n\n.frame a::before {\n content: '';\n height: 1px;\n width: 100%;\n background: currentColor;\n position: absolute;\n top: 90%;\n transition: transform 0.3s;\n transform-origin: 0% 50%;\n}\n\n.frame a:hover::before {\n transform: scaleX(0);\n transform-origin: 100% 50%;\n}\n\n.frame__title {\n grid-area: title;\n font-size: inherit;\n margin: 0;\n font-weight: inherit;\n}\n\n.frame__back {\n grid-area: back;\n display: flex;\n align-items: flex-end;\n}\n\n.frame__prev {\n grid-area: prev;\n}\n\ncanvas {\n height: 100vh;\n}\n\n@media screen and (min-width: 53em) {\n .frame {\n position: fixed;\n height: 100vh;\n grid-template-columns: auto auto 1fr;\n grid-template-rows: auto auto;\n grid-template-areas: 'title back ...' 'prev ... sponsor';\n align-content: space-between;\n justify-items: start;\n grid-gap: 2rem;\n padding: 0.75rem;\n }\n body #cdawrap {\n justify-self: end;\n }\n}\n"],"names":[],"sourceRoot":""}
--------------------------------------------------------------------------------
/build/static/js/main.cdd68f98.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * GSAP 3.11.5
3 | * https://greensock.com
4 | *
5 | * @license Copyright 2008-2023, GreenSock. All rights reserved.
6 | * Subject to the terms at https://greensock.com/standard-license or for
7 | * Club GreenSock members, the agreement issued with that membership.
8 | * @author: Jack Doyle, jack@greensock.com
9 | */
10 |
11 | /*!
12 | * assign-symbols
13 | *
14 | * Copyright (c) 2015, Jon Schlinkert.
15 | * Licensed under the MIT License.
16 | */
17 |
18 | /*!
19 | * for-in
20 | *
21 | * Copyright (c) 2014-2017, Jon Schlinkert.
22 | * Released under the MIT License.
23 | */
24 |
25 | /*!
26 | * get-value
27 | *
28 | * Copyright (c) 2014-2015, Jon Schlinkert.
29 | * Licensed under the MIT License.
30 | */
31 |
32 | /*!
33 | * is-extendable
34 | *
35 | * Copyright (c) 2015, Jon Schlinkert.
36 | * Licensed under the MIT License.
37 | */
38 |
39 | /*!
40 | * is-extendable
41 | *
42 | * Copyright (c) 2015-2017, Jon Schlinkert.
43 | * Released under the MIT License.
44 | */
45 |
46 | /*!
47 | * is-plain-object
48 | *
49 | * Copyright (c) 2014-2017, Jon Schlinkert.
50 | * Released under the MIT License.
51 | */
52 |
53 | /*!
54 | * isobject
55 | *
56 | * Copyright (c) 2014-2017, Jon Schlinkert.
57 | * Released under the MIT License.
58 | */
59 |
60 | /*!
61 | * set-value
62 | *
63 | * Copyright (c) 2014-2015, 2017, Jon Schlinkert.
64 | * Released under the MIT License.
65 | */
66 |
67 | /*!
68 | * split-string
69 | *
70 | * Copyright (c) 2015-2017, Jon Schlinkert.
71 | * Released under the MIT License.
72 | */
73 |
74 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
75 |
76 | /**
77 | * @license
78 | * Copyright 2010-2023 Three.js Authors
79 | * SPDX-License-Identifier: MIT
80 | */
81 |
82 | /**
83 | * @license React
84 | * react-dom.production.min.js
85 | *
86 | * Copyright (c) Facebook, Inc. and its affiliates.
87 | *
88 | * This source code is licensed under the MIT license found in the
89 | * LICENSE file in the root directory of this source tree.
90 | */
91 |
92 | /**
93 | * @license React
94 | * react-jsx-runtime.production.min.js
95 | *
96 | * Copyright (c) Facebook, Inc. and its affiliates.
97 | *
98 | * This source code is licensed under the MIT license found in the
99 | * LICENSE file in the root directory of this source tree.
100 | */
101 |
102 | /**
103 | * @license React
104 | * react-reconciler-constants.production.min.js
105 | *
106 | * Copyright (c) Facebook, Inc. and its affiliates.
107 | *
108 | * This source code is licensed under the MIT license found in the
109 | * LICENSE file in the root directory of this source tree.
110 | */
111 |
112 | /**
113 | * @license React
114 | * react-reconciler.production.min.js
115 | *
116 | * Copyright (c) Facebook, Inc. and its affiliates.
117 | *
118 | * This source code is licensed under the MIT license found in the
119 | * LICENSE file in the root directory of this source tree.
120 | */
121 |
122 | /**
123 | * @license React
124 | * react.production.min.js
125 | *
126 | * Copyright (c) Facebook, Inc. and its affiliates.
127 | *
128 | * This source code is licensed under the MIT license found in the
129 | * LICENSE file in the root directory of this source tree.
130 | */
131 |
132 | /**
133 | * @license React
134 | * scheduler.production.min.js
135 | *
136 | * Copyright (c) Facebook, Inc. and its affiliates.
137 | *
138 | * This source code is licensed under the MIT license found in the
139 | * LICENSE file in the root directory of this source tree.
140 | */
141 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webgl-carousel-postprocessing",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "homepage": "./",
7 | "main": "src/index.js",
8 | "dependencies": {
9 | "@react-three/drei": "9.65.3",
10 | "@react-three/fiber": "8.12.1",
11 | "gsap": "3.11.5",
12 | "leva": "0.9.34",
13 | "loader-utils": "3.2.1",
14 | "react": "18.2.0",
15 | "react-dom": "18.2.0",
16 | "react-scripts": "5.0.1",
17 | "react-use": "17.4.0",
18 | "three": "0.151.3"
19 | },
20 | "devDependencies": {
21 | "@babel/runtime": "7.13.8",
22 | "typescript": "4.1.3"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test --env=jsdom",
28 | "eject": "react-scripts eject"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ]
36 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/1.jpg
--------------------------------------------------------------------------------
/public/img/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/10.jpg
--------------------------------------------------------------------------------
/public/img/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/11.jpg
--------------------------------------------------------------------------------
/public/img/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/12.jpg
--------------------------------------------------------------------------------
/public/img/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/2.jpg
--------------------------------------------------------------------------------
/public/img/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/3.jpg
--------------------------------------------------------------------------------
/public/img/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/4.jpg
--------------------------------------------------------------------------------
/public/img/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/5.jpg
--------------------------------------------------------------------------------
/public/img/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/6.jpg
--------------------------------------------------------------------------------
/public/img/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/7.jpg
--------------------------------------------------------------------------------
/public/img/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/8.jpg
--------------------------------------------------------------------------------
/public/img/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supahfunk/webgl-carousel/d6f72f2c194dda9d1777bccfe52928825393652f/public/img/9.jpg
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | WebGL Carousel | Codrops
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Suspense } from 'react'
2 | import { Canvas } from '@react-three/fiber'
3 | import Carousel from './three/Carousel'
4 | import './style/styles.css'
5 |
6 | export default function App() {
7 | return (
8 |
9 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/data/images.js:
--------------------------------------------------------------------------------
1 | const images = [
2 | {
3 | image:
4 | 'img/1.jpg'
5 | },
6 | {
7 | image:
8 | 'img/2.jpg'
9 | },
10 | {
11 | image:
12 | 'img/3.jpg'
13 | },
14 | {
15 | image:
16 | 'img/4.jpg'
17 | },
18 | {
19 | image:
20 | 'img/5.jpg'
21 | },
22 | {
23 | image:
24 | 'img/6.jpg'
25 | },
26 | {
27 | image:
28 | 'img/7.jpg'
29 | },
30 | {
31 | image:
32 | 'img/8.jpg'
33 | },
34 | {
35 | image:
36 | 'img/9.jpg'
37 | },
38 | {
39 | image:
40 | 'img/10.jpg'
41 | },
42 | {
43 | image:
44 | 'img/11.jpg'
45 | },
46 | {
47 | image:
48 | 'img/12.jpg'
49 | }
50 | ]
51 |
52 | export default images
53 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 |
4 | import App from "./App";
5 |
6 | const rootElement = document.getElementById("root");
7 | const root = createRoot(rootElement);
8 |
9 | root.render(
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/src/style/styles.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | font-size: 13px;
9 | --color-text: #343131;
10 | --color-bg: #d5d5d5;
11 | --color-link: #868282;
12 | --color-link-hover: #000;
13 | }
14 |
15 | body {
16 | margin: 0;
17 | color: var(--color-text);
18 | background-color: var(--color-bg);
19 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
20 | -webkit-font-smoothing: antialiased;
21 | -moz-osx-font-smoothing: grayscale;
22 | overflow: hidden;
23 | scroll-behavior: none;
24 | height: 100vh;
25 | user-select: none;
26 | }
27 |
28 | /* Page Loader */
29 | .js .loading::before,
30 | .js .loading::after {
31 | content: '';
32 | position: fixed;
33 | z-index: 1000;
34 | }
35 |
36 | .js .loading::before {
37 | top: 0;
38 | left: 0;
39 | width: 100%;
40 | height: 100%;
41 | background: var(--color-bg);
42 | }
43 |
44 | .js .loading::after {
45 | top: 50%;
46 | left: 50%;
47 | width: 60px;
48 | height: 60px;
49 | margin: -30px 0 0 -30px;
50 | border-radius: 50%;
51 | opacity: 0.4;
52 | background: var(--color-link);
53 | animation: loaderAnim 0.7s linear infinite alternate forwards;
54 |
55 | }
56 |
57 | @keyframes loaderAnim {
58 | to {
59 | opacity: 1;
60 | transform: scale3d(0.5,0.5,1);
61 | }
62 | }
63 |
64 | a {
65 | text-decoration: none;
66 | color: var(--color-link);
67 | outline: none;
68 | cursor: pointer;
69 | }
70 |
71 | a:hover {
72 | color: var(--color-link-hover);
73 | outline: none;
74 | }
75 |
76 | /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
77 | a:focus {
78 | /* Provide a fallback style for browsers
79 | that don't support :focus-visible */
80 | outline: none;
81 | background: lightgrey;
82 | }
83 |
84 | a:focus:not(:focus-visible) {
85 | /* Remove the focus indicator on mouse-focus for browsers
86 | that do support :focus-visible */
87 | background: transparent;
88 | }
89 |
90 | a:focus-visible {
91 | /* Draw a very noticeable focus style for
92 | keyboard-focus on browsers that do support
93 | :focus-visible */
94 | outline: 2px solid red;
95 | background: transparent;
96 | }
97 |
98 | .unbutton {
99 | background: none;
100 | border: 0;
101 | padding: 0;
102 | margin: 0;
103 | font: inherit;
104 | cursor: pointer;
105 | }
106 |
107 | .unbutton:focus {
108 | outline: none;
109 | }
110 |
111 | .frame {
112 | grid-area: main;
113 | z-index: 1000;
114 | position: relative;
115 | width: 100%;
116 | padding: 1.5rem;
117 | display: grid;
118 | grid-template-columns: 100%;
119 | grid-template-areas: 'title' 'back' 'prev' 'sponsor';
120 | grid-gap: 0.5rem;
121 | justify-items: start;
122 | align-self: start;
123 | justify-self: start;
124 | pointer-events: none;
125 | align-items: center;
126 | text-transform: uppercase;
127 | }
128 |
129 | body #cdawrap {
130 | justify-self: start;
131 | }
132 |
133 | .frame a {
134 | pointer-events: auto;
135 | white-space: nowrap;
136 | overflow: hidden;
137 | position: relative;
138 | }
139 |
140 | .frame a::before {
141 | content: '';
142 | height: 1px;
143 | width: 100%;
144 | background: currentColor;
145 | position: absolute;
146 | top: 90%;
147 | transition: transform 0.3s;
148 | transform-origin: 0% 50%;
149 | }
150 |
151 | .frame a:hover::before {
152 | transform: scaleX(0);
153 | transform-origin: 100% 50%;
154 | }
155 |
156 | .frame__title {
157 | grid-area: title;
158 | font-size: inherit;
159 | margin: 0;
160 | font-weight: inherit;
161 | }
162 |
163 | .frame__back {
164 | grid-area: back;
165 | display: flex;
166 | align-items: flex-end;
167 | }
168 |
169 | .frame__prev {
170 | grid-area: prev;
171 | }
172 |
173 | canvas {
174 | height: 100vh;
175 | }
176 |
177 | @media screen and (min-width: 53em) {
178 | .frame {
179 | position: fixed;
180 | height: 100vh;
181 | grid-template-columns: auto auto 1fr;
182 | grid-template-rows: auto auto;
183 | grid-template-areas: 'title back ...' 'prev ... sponsor';
184 | align-content: space-between;
185 | justify-items: start;
186 | grid-gap: 2rem;
187 | padding: 0.75rem;
188 | }
189 | body #cdawrap {
190 | justify-self: end;
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/three/Carousel.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState, useMemo } from 'react'
2 | import { useFrame, useThree } from '@react-three/fiber'
3 | import { usePrevious } from 'react-use'
4 | import gsap from 'gsap'
5 | import PostProcessing from './PostProcessing'
6 | import CarouselItem from './CarouselItem'
7 | import { lerp, getPiramidalIndex } from '../utils'
8 | import images from '../data/images'
9 |
10 | /*------------------------------
11 | Plane Settings
12 | ------------------------------*/
13 | const planeSettings = {
14 | width: 1,
15 | height: 2.5,
16 | gap: 0.1
17 | }
18 |
19 | /*------------------------------
20 | Gsap Defaults
21 | ------------------------------*/
22 | gsap.defaults({
23 | duration: 2.5,
24 | ease: 'power3.out'
25 | })
26 |
27 | /*------------------------------
28 | Carousel
29 | ------------------------------*/
30 | const Carousel = () => {
31 | const [$root, setRoot] = useState()
32 | const $post = useRef()
33 |
34 | const [activePlane, setActivePlane] = useState(null)
35 | const prevActivePlane = usePrevious(activePlane)
36 | const { viewport } = useThree()
37 |
38 | /*--------------------
39 | Vars
40 | --------------------*/
41 | const progress = useRef(0)
42 | const startX = useRef(0)
43 | const isDown = useRef(false)
44 | const speedWheel = 0.02
45 | const speedDrag = -0.3
46 | const oldProgress = useRef(0)
47 | const speed = useRef(0)
48 | const $items = useMemo(() => {
49 | if ($root) return $root.children
50 | }, [$root])
51 |
52 | /*--------------------
53 | Diaplay Items
54 | --------------------*/
55 | const displayItems = (item, index, active) => {
56 | const piramidalIndex = getPiramidalIndex($items, active)[index]
57 | gsap.to(item.position, {
58 | x: (index - active) * (planeSettings.width + planeSettings.gap),
59 | y: $items.length * -0.1 + piramidalIndex * 0.1
60 | })
61 | }
62 |
63 | /*--------------------
64 | RAF
65 | --------------------*/
66 | useFrame(() => {
67 | progress.current = Math.max(0, Math.min(progress.current, 100))
68 |
69 | const active = Math.floor((progress.current / 100) * ($items.length - 1))
70 | $items.forEach((item, index) => displayItems(item, index, active))
71 | speed.current = lerp(
72 | speed.current,
73 | Math.abs(oldProgress.current - progress.current),
74 | 0.1
75 | )
76 |
77 | oldProgress.current = lerp(oldProgress.current, progress.current, 0.1)
78 |
79 | if ($post.current) {
80 | $post.current.thickness = speed.current
81 | }
82 | })
83 |
84 | /*--------------------
85 | Handle Wheel
86 | --------------------*/
87 | const handleWheel = (e) => {
88 | if (activePlane !== null) return
89 | const isVerticalScroll = Math.abs(e.deltaY) > Math.abs(e.deltaX)
90 | const wheelProgress = isVerticalScroll ? e.deltaY : e.deltaX
91 | progress.current = progress.current + wheelProgress * speedWheel
92 | }
93 |
94 | /*--------------------
95 | Handle Down
96 | --------------------*/
97 | const handleDown = (e) => {
98 | if (activePlane !== null) return
99 | isDown.current = true
100 | startX.current = e.clientX || (e.touches && e.touches[0].clientX) || 0
101 | }
102 |
103 | /*--------------------
104 | Handle Up
105 | --------------------*/
106 | const handleUp = () => {
107 | isDown.current = false
108 | }
109 |
110 | /*--------------------
111 | Handle Move
112 | --------------------*/
113 | const handleMove = (e) => {
114 | if (activePlane !== null || !isDown.current) return
115 | const x = e.clientX || (e.touches && e.touches[0].clientX) || 0
116 | const mouseProgress = (x - startX.current) * speedDrag
117 | progress.current = progress.current + mouseProgress
118 | startX.current = x
119 | }
120 |
121 | /*--------------------
122 | Click
123 | --------------------*/
124 | useEffect(() => {
125 | if (!$items) return
126 | if (activePlane !== null && prevActivePlane === null) {
127 | progress.current = (activePlane / ($items.length - 1)) * 100 // Calculate the progress.current based on activePlane
128 | }
129 | }, [activePlane, $items])
130 |
131 | /*--------------------
132 | Render Plane Events
133 | --------------------*/
134 | const renderPlaneEvents = () => {
135 | return (
136 |
145 |
146 |
147 |
148 | )
149 | }
150 |
151 | /*--------------------
152 | Render Slider
153 | --------------------*/
154 | const renderSlider = () => {
155 | return (
156 |
157 | {images.map((item, i) => (
158 |
167 | ))}
168 |
169 | )
170 | }
171 |
172 | return (
173 |
174 | {renderPlaneEvents()}
175 | {renderSlider()}
176 |
177 |
178 | )
179 | }
180 |
181 | export default Carousel
182 |
--------------------------------------------------------------------------------
/src/three/CarouselItem.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react'
2 | import { useThree } from '@react-three/fiber'
3 | import gsap from 'gsap'
4 | import Plane from './Plane'
5 |
6 | const CarouselItem = ({
7 | index,
8 | width,
9 | height,
10 | setActivePlane,
11 | activePlane,
12 | item
13 | }) => {
14 | const $root = useRef()
15 | const [hover, setHover] = useState(false)
16 | const [isActive, setIsActive] = useState(false)
17 | const [isCloseActive, setCloseActive] = useState(false)
18 | const { viewport } = useThree()
19 | const timeoutID = useRef()
20 |
21 | useEffect(() => {
22 | if (activePlane === index) {
23 | setIsActive(activePlane === index)
24 | setCloseActive(true)
25 | } else {
26 | setIsActive(null)
27 | }
28 | }, [activePlane])
29 |
30 | useEffect(() => {
31 | gsap.killTweensOf($root.current.position)
32 | gsap.to($root.current.position, {
33 | z: isActive ? 0 : -0.01,
34 | duration: 0.2,
35 | ease: 'power3.out',
36 | delay: isActive ? 0 : 2
37 | })
38 | }, [isActive])
39 |
40 | /*------------------------------
41 | Hover effect
42 | ------------------------------*/
43 | useEffect(() => {
44 | const hoverScale = hover && !isActive ? 1.1 : 1
45 | gsap.to($root.current.scale, {
46 | x: hoverScale,
47 | y: hoverScale,
48 | duration: 0.5,
49 | ease: 'power3.out'
50 | })
51 | }, [hover, isActive])
52 |
53 | const handleClose = (e) => {
54 | e.stopPropagation()
55 | if (!isActive) return
56 | setActivePlane(null)
57 | setHover(false)
58 | clearTimeout(timeoutID.current)
59 | timeoutID.current = setTimeout(() => {
60 | setCloseActive(false)
61 | }, 1500) // The duration of this timer depends on the duration of the plane's closing animation.
62 | }
63 |
64 | return (
65 | {
68 | setActivePlane(index)
69 | }}
70 | onPointerEnter={() => setHover(true)}
71 | onPointerLeave={() => setHover(false)}
72 | >
73 |
79 |
80 | {isCloseActive ? (
81 |
82 |
83 |
84 |
85 | ) : null}
86 |
87 | )
88 | }
89 |
90 | export default CarouselItem
91 |
--------------------------------------------------------------------------------
/src/three/Plane.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo, useRef } from 'react'
2 | import { useThree } from '@react-three/fiber'
3 | import { useTexture } from '@react-three/drei'
4 | import gsap from 'gsap'
5 |
6 | const Plane = ({ texture, width, height, active, ...props }) => {
7 | const $mesh = useRef()
8 | const { viewport } = useThree()
9 | const tex = useTexture(texture)
10 |
11 | useEffect(() => {
12 | if ($mesh.current.material) {
13 | // Setting the 'uZoomScale' uniform in the 'Plane' component to resize the texture proportionally to the dimensions of the viewport.
14 | $mesh.current.material.uniforms.uZoomScale.value.x =
15 | viewport.width / width
16 | $mesh.current.material.uniforms.uZoomScale.value.y =
17 | viewport.height / height
18 |
19 | gsap.to($mesh.current.material.uniforms.uProgress, {
20 | value: active ? 1 : 0
21 | })
22 |
23 | gsap.to($mesh.current.material.uniforms.uRes.value, {
24 | x: active ? viewport.width : width,
25 | y: active ? viewport.height : height
26 | })
27 | }
28 | }, [viewport, active])
29 |
30 | const shaderArgs = useMemo(
31 | () => ({
32 | uniforms: {
33 | uProgress: { value: 0 },
34 | uZoomScale: { value: { x: 1, y: 1 } },
35 | uTex: { value: tex },
36 | uRes: { value: { x: 1, y: 1 } },
37 | uImageRes: {
38 | value: { x: tex.source.data.width, y: tex.source.data.height }
39 | }
40 | },
41 | vertexShader: /* glsl */ `
42 | varying vec2 vUv;
43 | uniform float uProgress;
44 | uniform vec2 uZoomScale;
45 |
46 | void main() {
47 | vUv = uv;
48 | vec3 pos = position;
49 | float angle = uProgress * 3.14159265 / 2.;
50 | float wave = cos(angle);
51 | float c = sin(length(uv - .5) * 15. + uProgress * 12.) * .5 + .5;
52 | pos.x *= mix(1., uZoomScale.x + wave * c, uProgress);
53 | pos.y *= mix(1., uZoomScale.y + wave * c, uProgress);
54 |
55 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 );
56 | }
57 | `,
58 | fragmentShader: /* glsl */ `
59 | uniform sampler2D uTex;
60 | uniform vec2 uRes;
61 | uniform vec2 uZoomScale;
62 | uniform vec2 uImageRes;
63 |
64 | /*------------------------------
65 | Background Cover UV
66 | --------------------------------
67 | u = basic UV
68 | s = screensize
69 | i = image size
70 | ------------------------------*/
71 | vec2 CoverUV(vec2 u, vec2 s, vec2 i) {
72 | float rs = s.x / s.y; // Aspect screen size
73 | float ri = i.x / i.y; // Aspect image size
74 | vec2 st = rs < ri ? vec2(i.x * s.y / i.y, s.y) : vec2(s.x, i.y * s.x / i.x); // New st
75 | vec2 o = (rs < ri ? vec2((st.x - s.x) / 2.0, 0.0) : vec2(0.0, (st.y - s.y) / 2.0)) / st; // Offset
76 | return u * s / st + o;
77 | }
78 |
79 | varying vec2 vUv;
80 | void main() {
81 | vec2 uv = CoverUV(vUv, uRes, uImageRes);
82 | vec3 tex = texture2D(uTex, uv).rgb;
83 | gl_FragColor = vec4( tex, 1.0 );
84 | }
85 | `
86 | }),
87 | [tex]
88 | )
89 |
90 | return (
91 |
92 |
93 |
94 |
95 | )
96 | }
97 |
98 | export default Plane
99 |
--------------------------------------------------------------------------------
/src/three/PostProcessing.js:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react'
2 | import { useThree } from '@react-three/fiber'
3 | import { MeshTransmissionMaterial } from '@react-three/drei'
4 | import { Color } from 'three'
5 | import { useControls } from 'leva'
6 |
7 | const PostProcessing = forwardRef((_, ref) => {
8 | const { viewport } = useThree()
9 |
10 | const { active, ior } = useControls({
11 | active: {
12 | value: true
13 | },
14 | ior: {
15 | value: 0.9,
16 | min: 0.8,
17 | max: 1.2
18 | }
19 | })
20 |
21 | return active ? (
22 |
23 |
24 |
34 |
35 | ) : null
36 | })
37 |
38 | export default PostProcessing
39 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /*------------------------------
2 | Lerp
3 | ------------------------------*/
4 | export const lerp = (v0, v1, t) => v0 * (1 - t) + v1 * t
5 |
6 | /*--------------------
7 | Get Piramidal Index
8 | --------------------*/
9 | // Returns an array of decreasing index values in a pyramid shape, starting from the specified index with the highest value. These indices are often used to create overlapping effects among elements.
10 | export const getPiramidalIndex = (array, index) =>
11 | array.map((_, i) =>
12 | index === i ? array.length : array.length - Math.abs(index - i)
13 | )
14 |
--------------------------------------------------------------------------------