├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── css └── base.css ├── dist ├── base.e3190653.css ├── base.e3190653.css.map ├── favicon.2e5f6236.ico ├── index.html ├── index2.8108f201.js ├── index2.8108f201.js.map ├── index2.html ├── index3.51e7060d.js ├── index3.51e7060d.js.map ├── index3.html ├── noise.0c992c6e.png ├── scripts.3bcec604.js └── scripts.3bcec604.js.map ├── favicon.ico ├── img └── noise.png ├── index.html ├── index2.html ├── index3.html ├── package.json └── scripts ├── gl ├── Blob.js ├── index.js └── shaders │ ├── fragment.glsl │ └── vertex.glsl ├── index.js ├── index2.js └── index3.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009 - 2020 [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 | # Blobs with Three.js 2 | 3 | Demos for the tutorial on how to deform and color spheres in Three.js. By [Mario Carrillo](https://twitter.com/marioecg). 4 | 5 | ![Image](https://tympanus.net/codrops/wp-content/uploads/2021/01/blob.jpg) 6 | 7 | [Article on Codrops](https://tympanus.net/codrops/?p=52932) 8 | 9 | [Demo](http://tympanus.net/Tutorials/WebGLBlobs/) 10 | 11 | 12 | ## Installation 13 | 14 | Install dependencies: 15 | 16 | ``` 17 | npm install 18 | ``` 19 | 20 | Compile the code for development and start a local server: 21 | 22 | ``` 23 | npm start 24 | ``` 25 | 26 | Create the build: 27 | 28 | ``` 29 | npm run build 30 | ``` 31 | 32 | ## Misc 33 | 34 | 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/) 35 | 36 | ## License 37 | [MIT](LICENSE) 38 | 39 | Made with :blue_heart: by [Codrops](http://www.codrops.com) 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::after, 3 | *::before { 4 | box-sizing: border-box; 5 | } 6 | 7 | :root { 8 | font-size: 15px; 9 | } 10 | 11 | body { 12 | margin: 0; 13 | --color-text: #d8d8d8; 14 | --color-bg: #060606; 15 | --color-link: #fff; 16 | --color-link-hover: #fff; 17 | --color-line: rgba(82,77,73,0.38); 18 | color: var(--color-text); 19 | background-color: var(--color-bg); 20 | font-family: termina, sans-serif; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | .demo-2 { 26 | --color-text: #000000; 27 | --color-bg: #c7c7c7; 28 | } 29 | 30 | /* Page Loader */ 31 | .js .loading::before, 32 | .js .loading::after { 33 | content: ''; 34 | position: fixed; 35 | z-index: 1000; 36 | } 37 | 38 | .js .loading::before { 39 | top: 0; 40 | left: 0; 41 | width: 100%; 42 | height: 100%; 43 | background: var(--color-bg); 44 | } 45 | 46 | .js .loading::after { 47 | top: 50%; 48 | left: 50%; 49 | width: 60px; 50 | height: 60px; 51 | margin: -30px 0 0 -30px; 52 | border-radius: 50%; 53 | opacity: 0.4; 54 | background: var(--color-link); 55 | animation: loaderAnim 0.7s linear infinite alternate forwards; 56 | 57 | } 58 | 59 | @keyframes loaderAnim { 60 | to { 61 | opacity: 1; 62 | transform: scale3d(0.5,0.5,1); 63 | } 64 | } 65 | 66 | a { 67 | text-decoration: underline; 68 | color: var(--color-link); 69 | outline: none; 70 | } 71 | 72 | a:hover, 73 | a:focus { 74 | color: var(--color-link-hover); 75 | outline: none; 76 | text-decoration: none; 77 | } 78 | 79 | main { 80 | text-align: center; 81 | padding: 2rem; 82 | } 83 | 84 | /* Grainy texture animation by Geoff Graham https://css-tricks.com/snippets/css/animated-grainy-texture/ */ 85 | 86 | main::before { 87 | animation: grain 8s steps(10) infinite; 88 | background-image: url(../img/noise.png); 89 | content: ''; 90 | height: 300%; 91 | left: -50%; 92 | opacity: 0.5; 93 | position: fixed; 94 | top: -100%; 95 | width: 300%; 96 | pointer-events: none; 97 | } 98 | 99 | @keyframes grain { 100 | 0%, 100% { transform:translate(0, 0); } 101 | 10% { transform:translate(-5%, -10%); } 102 | 20% { transform:translate(-15%, 5%); } 103 | 30% { transform:translate(7%, -25%); } 104 | 40% { transform:translate(-5%, 25%); } 105 | 50% { transform:translate(-15%, 10%); } 106 | 60% { transform:translate(15%, 0%); } 107 | 70% { transform:translate(0%, 15%); } 108 | 80% { transform:translate(3%, 35%); } 109 | 90% { transform:translate(-10%, 10%); } 110 | } 111 | 112 | .logo { 113 | grid-area: logo; 114 | text-decoration: none; 115 | font-size: 3rem; 116 | font-weight: 700; 117 | align-self: center; 118 | justify-self: center; 119 | } 120 | 121 | .page-title { 122 | grid-area: pagetitle; 123 | margin: 0; 124 | font-weight: 600; 125 | font-size: 1rem; 126 | padding: 1rem 0; 127 | } 128 | 129 | .page-title div { 130 | clip-path: polygon(0 0, var(--clip) 0, var(--clip) 100%, 0% 100%); 131 | } 132 | 133 | .demos { 134 | grid-area: demos; 135 | } 136 | 137 | .links { 138 | grid-area: links; 139 | padding-top: 1rem; 140 | } 141 | 142 | .demos div:not(:first-child), 143 | .links div:not(:first-child) { 144 | margin-left: 1rem; 145 | } 146 | 147 | .frame__demo { 148 | display: inline-block; 149 | } 150 | 151 | .frame__demo--current { 152 | opacity: 0.8; 153 | text-decoration: none; 154 | } 155 | 156 | .title { 157 | grid-area: title; 158 | font-family: dystopian, sans-serif; 159 | font-weight: 700; 160 | font-size: 11.25vw; 161 | margin: 0; 162 | line-height: 0.9; 163 | text-indent: -0.9vw; 164 | } 165 | 166 | .subtitle { 167 | grid-area: subtitle; 168 | font-size: 4vw; 169 | margin: 0.5rem 0 0 0; 170 | line-height: 1; 171 | font-weight: 200; 172 | text-indent: -0.4vw; 173 | } 174 | 175 | .menu { 176 | grid-area: menu; 177 | align-self: start; 178 | } 179 | 180 | .menu__inner { 181 | font-family: dystopian, sans-serif; 182 | font-weight: 700; 183 | line-height: 1; 184 | font-size: 1.5rem; 185 | padding: 1rem 0; 186 | display: block; 187 | } 188 | 189 | .content { 190 | margin: 0; 191 | font-size: 1.15rem; 192 | font-size: clamp(1rem, 2vh, 3rem); 193 | grid-area: content; 194 | padding: 2rem 0; 195 | } 196 | 197 | .content span { 198 | clip-path: polygon(0 0, 100% 0, 100% var(--clip), 0% var(--clip)); 199 | } 200 | 201 | .play { 202 | grid-area: play; 203 | font-size: 12vw; 204 | align-self: center; 205 | justify-self: center; 206 | cursor: default; 207 | display: block; 208 | } 209 | 210 | .year { 211 | grid-area: year; 212 | } 213 | 214 | .credits--site { 215 | grid-area: credits-1; 216 | font-weight: 600; 217 | text-decoration: none; 218 | } 219 | 220 | .credits--author { 221 | grid-area: credits-2; 222 | } 223 | 224 | .credits--author div { 225 | clip-path: polygon(0 0, var(--clip) 0, var(--clip) 100%, 0% 100%); 226 | } 227 | 228 | @media screen and (min-width: 60em) { 229 | main { 230 | text-align: left; 231 | padding: 0; 232 | overflow: hidden; 233 | height: 100vh; 234 | display: grid; 235 | grid-template-columns: 8rem 12vh 9rem 1fr 1fr; 236 | grid-template-rows: 4rem 4rem min-content min-content 1fr 2.5rem; 237 | grid-template-areas: 'logo ... ... pagetitle links' 238 | 'logo ... ... demos ...' 239 | '... ... ... title title' 240 | '... ... ... ... subtitle' 241 | '... menu ... content play' 242 | 'year ... credits-1 credits-2 ...'; 243 | } 244 | .line { 245 | position: relative; 246 | } 247 | 248 | .line::before { 249 | content: ''; 250 | position: absolute; 251 | background: var(--color-line); 252 | } 253 | 254 | .line--vertical::before { 255 | left: 0; 256 | width: 1px; 257 | height: 500vh; 258 | top: -250vh; 259 | } 260 | 261 | .line--horizontal::before { 262 | left: -250vw; 263 | width: 500vw; 264 | top: 0; 265 | height: 1px; 266 | } 267 | 268 | .content { 269 | width: 90%; 270 | padding: 0; 271 | } 272 | 273 | .menu__inner { 274 | writing-mode: vertical-lr; 275 | transform: rotate(180deg); 276 | font-size: 12vh; 277 | padding: 0; 278 | } 279 | 280 | } 281 | 282 | .webgl { 283 | position: fixed; 284 | top: 0; 285 | left: 0; 286 | z-index: -1; 287 | } 288 | 289 | /* Style class utilities */ 290 | .oh { 291 | overflow: hidden; 292 | } 293 | 294 | .dib { 295 | display: inline-block; 296 | } 297 | 298 | .db { 299 | display: block; 300 | } -------------------------------------------------------------------------------- /dist/base.e3190653.css: -------------------------------------------------------------------------------- 1 | *,:after,:before{box-sizing:border-box}:root{font-size:15px}body{margin:0;--color-text:#d8d8d8;--color-bg:#060606;--color-link:#fff;--color-link-hover:#fff;--color-line:rgba(82,77,73,0.38);color:var(--color-text);background-color:var(--color-bg);font-family:termina,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.demo-2{--color-text:#000;--color-bg:#c7c7c7}.js .loading:after,.js .loading:before{content:"";position:fixed;z-index:1000}.js .loading:before{top:0;left:0;width:100%;height:100%;background:var(--color-bg)}.js .loading:after{top:50%;left:50%;width:60px;height:60px;margin:-30px 0 0 -30px;border-radius:50%;opacity:.4;background:var(--color-link);animation:loaderAnim .7s linear infinite alternate forwards}@keyframes loaderAnim{to{opacity:1;transform:scale3d(.5,.5,1)}}a{text-decoration:underline;color:var(--color-link);outline:none}a:focus,a:hover{color:var(--color-link-hover);outline:none;text-decoration:none}main{text-align:center;padding:2rem}main:before{animation:grain 8s steps(10) infinite;background-image:url(/noise.0c992c6e.png);content:"";height:300%;left:-50%;opacity:.5;position:fixed;top:-100%;width:300%;pointer-events:none}@keyframes grain{0%,to{transform:translate(0)}10%{transform:translate(-5%,-10%)}20%{transform:translate(-15%,5%)}30%{transform:translate(7%,-25%)}40%{transform:translate(-5%,25%)}50%{transform:translate(-15%,10%)}60%{transform:translate(15%)}70%{transform:translateY(15%)}80%{transform:translate(3%,35%)}90%{transform:translate(-10%,10%)}}.logo{grid-area:logo;text-decoration:none;font-size:3rem;font-weight:700;align-self:center;justify-self:center}.page-title{grid-area:pagetitle;margin:0;font-weight:600;font-size:1rem;padding:1rem 0}.page-title div{clip-path:polygon(0 0,var(--clip) 0,var(--clip) 100%,0 100%)}.demos{grid-area:demos}.links{grid-area:links;padding-top:1rem}.demos div:not(:first-child),.links div:not(:first-child){margin-left:1rem}.frame__demo{display:inline-block}.frame__demo--current{opacity:.8;text-decoration:none}.title{grid-area:title;font-family:dystopian,sans-serif;font-weight:700;font-size:11.25vw;margin:0;line-height:.9;text-indent:-.9vw}.subtitle{grid-area:subtitle;font-size:4vw;margin:.5rem 0 0;line-height:1;font-weight:200;text-indent:-.4vw}.menu{grid-area:menu;align-self:start}.menu__inner{font-family:dystopian,sans-serif;font-weight:700;line-height:1;font-size:1.5rem;padding:1rem 0;display:block}.content{margin:0;font-size:1.15rem;font-size:clamp(1rem,2vh,3rem);grid-area:content;padding:2rem 0}.content span{clip-path:polygon(0 0,100% 0,100% var(--clip),0 var(--clip))}.play{grid-area:play;font-size:12vw;align-self:center;justify-self:center;cursor:default;display:block}.year{grid-area:year}.credits--site{grid-area:credits-1;font-weight:600;text-decoration:none}.credits--author{grid-area:credits-2}.credits--author div{clip-path:polygon(0 0,var(--clip) 0,var(--clip) 100%,0 100%)}@media screen and (min-width:60em){main{text-align:left;padding:0;overflow:hidden;height:100vh;display:grid;grid-template-columns:8rem 12vh 9rem 1fr 1fr;grid-template-rows:4rem 4rem min-content min-content 1fr 2.5rem;grid-template-areas:"logo ... ... pagetitle links" "logo ... ... demos ..." "... ... ... title title" "... ... ... ... subtitle" "... menu ... content play" "year ... credits-1 credits-2 ..."}.line{position:relative}.line:before{content:"";position:absolute;background:var(--color-line)}.line--vertical:before{left:0;width:1px;height:500vh;top:-250vh}.line--horizontal:before{left:-250vw;width:500vw;top:0;height:1px}.content{width:90%;padding:0}.menu__inner{writing-mode:vertical-lr;transform:rotate(180deg);font-size:12vh;padding:0}}.webgl{position:fixed;top:0;left:0;z-index:-1}.oh{overflow:hidden}.dib{display:inline-block}.db{display:block} 2 | /*# sourceMappingURL=/base.e3190653.css.map */ -------------------------------------------------------------------------------- /dist/base.e3190653.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["base.css"],"names":[],"mappings":"AAAA,iBAGC,qBACD,CAEA,MACC,cACD,CAEA,KACC,QAAS,CACT,oBAAqB,CACrB,kBAAmB,CACnB,iBAAkB,CAClB,uBAAwB,CACxB,gCAAiC,CACjC,uBAAwB,CACxB,gCAAiC,CACjC,8BAAgC,CAChC,kCAAmC,CACnC,iCACD,CAEA,QACC,iBAAqB,CAClB,kBACJ,CAGA,uCAEC,UAAW,CACX,cAAe,CACf,YACD,CAEA,oBACC,KAAM,CACN,MAAO,CACP,UAAW,CACX,WAAY,CACZ,0BACD,CAEA,mBACC,OAAQ,CACR,QAAS,CACT,UAAW,CACX,WAAY,CACZ,sBAAuB,CACvB,iBAAkB,CAClB,UAAY,CACZ,4BAA6B,CAC7B,2DAED,CAEA,sBACC,GACC,SAAU,CACV,0BACD,CACD,CAEA,EACC,yBAA0B,CAC1B,uBAAwB,CACxB,YACD,CAEA,gBAEC,6BAA8B,CAC9B,YAAa,CACb,oBACD,CAEA,KACC,iBAAkB,CAClB,YACD,CAIA,YACC,qCAAsC,CACtC,2DAAuC,CACvC,UAAW,CACX,WAAY,CACZ,SAAU,CACV,UAAY,CACZ,cAAe,CACf,SAAU,CACV,UAAW,CACX,mBACD,CAEA,iBACE,MAAW,sBAA2B,CACtC,IAAM,6BAAgC,CACtC,IAAM,4BAA+B,CACrC,IAAM,4BAA+B,CACrC,IAAM,4BAA+B,CACrC,IAAM,6BAAgC,CACtC,IAAM,wBAA8B,CACpC,IAAM,yBAA8B,CACpC,IAAM,2BAA8B,CACpC,IAAM,6BAAgC,CACxC,CAEA,MACC,cAAe,CACf,oBAAqB,CACrB,cAAe,CACf,eAAgB,CAChB,iBAAkB,CAClB,mBACD,CAEA,YACC,mBAAoB,CACpB,QAAS,CACT,eAAgB,CAChB,cAAe,CACf,cACD,CAEA,gBACC,4DACD,CAEA,OACC,eACD,CAEA,OACC,eAAgB,CAChB,gBACD,CAEA,0DAEC,gBACD,CAEA,aACC,oBACD,CAEA,sBACC,UAAY,CACZ,oBACD,CAEA,OACC,eAAgB,CAChB,gCAAkC,CAClC,eAAgB,CAChB,iBAAkB,CAClB,QAAS,CACT,cAAgB,CAChB,iBACD,CAEA,UACC,kBAAmB,CACnB,aAAc,CACd,gBAAoB,CACpB,aAAc,CACd,eAAgB,CAChB,iBACD,CAEA,MACC,cAAe,CACf,gBACD,CAEA,aACC,gCAAkC,CAClC,eAAgB,CACb,aAAc,CACd,gBAAiB,CACjB,cAAe,CACf,aACJ,CAEA,SACC,QAAS,CACT,iBAAkB,CAClB,8BAAiC,CACjC,iBAAkB,CAClB,cACD,CAEA,cACC,4DACD,CAEA,MACC,cAAe,CACf,cAAe,CACf,iBAAkB,CAClB,mBAAoB,CACpB,cAAe,CACf,aACD,CAEA,MACC,cACD,CAEA,eACC,mBAAoB,CACpB,eAAgB,CAChB,oBACD,CAEA,iBACC,mBACD,CAEA,qBACC,4DACD,CAEA,mCACC,KACC,eAAgB,CAChB,SAAU,CACV,eAAgB,CAChB,YAAa,CACb,YAAa,CACb,4CAA6C,CAC7C,+DAAgE,CAChE,+LAMD,CACA,MACC,iBACD,CAEA,aACC,UAAW,CACX,iBAAkB,CAClB,4BACD,CAEA,uBACC,MAAO,CACP,SAAU,CACV,YAAa,CACb,UACD,CAEA,yBACC,WAAY,CACZ,WAAY,CACZ,KAAM,CACN,UACD,CAEA,SACC,SAAU,CACV,SACD,CAEA,aACC,wBAAyB,CACzB,wBAAyB,CACzB,cAAe,CACf,SACD,CAED,CAEA,OACC,cAAe,CACf,KAAM,CACN,MAAO,CACP,UACD,CAGA,IACC,eACD,CAEA,KACC,oBACD,CAEA,IACC,aACD","file":"base.e3190653.css","sourceRoot":"..","sourcesContent":["*,\n*::after,\n*::before {\n\tbox-sizing: border-box;\n}\n\n:root {\n\tfont-size: 15px;\n}\n\nbody {\n\tmargin: 0;\n\t--color-text: #d8d8d8;\n\t--color-bg: #060606;\n\t--color-link: #fff;\n\t--color-link-hover: #fff;\n\t--color-line: rgba(82,77,73,0.38);\n\tcolor: var(--color-text);\n\tbackground-color: var(--color-bg);\n\tfont-family: termina, sans-serif;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.demo-2 {\n\t--color-text: #000000;\n --color-bg: #c7c7c7;\n}\n\n/* Page Loader */\n.js .loading::before,\n.js .loading::after {\n\tcontent: '';\n\tposition: fixed;\n\tz-index: 1000;\n}\n\n.js .loading::before {\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tbackground: var(--color-bg);\n}\n\n.js .loading::after {\n\ttop: 50%;\n\tleft: 50%;\n\twidth: 60px;\n\theight: 60px;\n\tmargin: -30px 0 0 -30px;\n\tborder-radius: 50%;\n\topacity: 0.4;\n\tbackground: var(--color-link);\n\tanimation: loaderAnim 0.7s linear infinite alternate forwards;\n\n}\n\n@keyframes loaderAnim {\n\tto {\n\t\topacity: 1;\n\t\ttransform: scale3d(0.5,0.5,1);\n\t}\n}\n\na {\n\ttext-decoration: underline;\n\tcolor: var(--color-link);\n\toutline: none;\n}\n\na:hover,\na:focus {\n\tcolor: var(--color-link-hover);\n\toutline: none;\n\ttext-decoration: none;\n}\n\nmain {\n\ttext-align: center;\n\tpadding: 2rem;\n}\n\n/* Grainy texture animation by Geoff Graham https://css-tricks.com/snippets/css/animated-grainy-texture/ */\n\nmain::before {\n\tanimation: grain 8s steps(10) infinite;\n\tbackground-image: url(../img/noise.png);\n\tcontent: '';\n\theight: 300%;\n\tleft: -50%;\n\topacity: 0.5;\n\tposition: fixed;\n\ttop: -100%;\n\twidth: 300%;\n\tpointer-events: none;\n}\n\n@keyframes grain {\n 0%, 100% { transform:translate(0, 0); }\n 10% { transform:translate(-5%, -10%); }\n 20% { transform:translate(-15%, 5%); }\n 30% { transform:translate(7%, -25%); }\n 40% { transform:translate(-5%, 25%); }\n 50% { transform:translate(-15%, 10%); }\n 60% { transform:translate(15%, 0%); }\n 70% { transform:translate(0%, 15%); }\n 80% { transform:translate(3%, 35%); }\n 90% { transform:translate(-10%, 10%); }\n}\n\n.logo {\n\tgrid-area: logo;\n\ttext-decoration: none;\n\tfont-size: 3rem;\n\tfont-weight: 700;\n\talign-self: center;\n\tjustify-self: center;\n}\n\n.page-title {\n\tgrid-area: pagetitle;\n\tmargin: 0;\n\tfont-weight: 600;\n\tfont-size: 1rem;\n\tpadding: 1rem 0;\n}\n\n.page-title div {\n\tclip-path: polygon(0 0, var(--clip) 0, var(--clip) 100%, 0% 100%);\n}\n\n.demos {\n\tgrid-area: demos;\n}\n\n.links {\n\tgrid-area: links;\n\tpadding-top: 1rem;\n}\n\n.demos div:not(:first-child),\n.links div:not(:first-child) {\n\tmargin-left: 1rem;\n}\n\n.frame__demo {\n\tdisplay: inline-block;\n}\n\n.frame__demo--current {\n\topacity: 0.8;\n\ttext-decoration: none;\n}\n\n.title {\n\tgrid-area: title;\n\tfont-family: dystopian, sans-serif;\n\tfont-weight: 700;\n\tfont-size: 11.25vw;\n\tmargin: 0;\n\tline-height: 0.9;\n\ttext-indent: -0.9vw;\n}\n\n.subtitle {\n\tgrid-area: subtitle;\n\tfont-size: 4vw;\n\tmargin: 0.5rem 0 0 0;\n\tline-height: 1;\n\tfont-weight: 200;\n\ttext-indent: -0.4vw;\n}\n\n.menu {\n\tgrid-area: menu;\n\talign-self: start;\n}\n\n.menu__inner {\n\tfont-family: dystopian, sans-serif;\n\tfont-weight: 700;\n line-height: 1;\n font-size: 1.5rem;\n padding: 1rem 0;\n display: block;\n}\n\n.content {\n\tmargin: 0;\n\tfont-size: 1.15rem;\n\tfont-size: clamp(1rem, 2vh, 3rem);\n\tgrid-area: content;\n\tpadding: 2rem 0;\n}\n\n.content span {\n\tclip-path: polygon(0 0, 100% 0, 100% var(--clip), 0% var(--clip));\n}\n\n.play {\n\tgrid-area: play;\n\tfont-size: 12vw;\n\talign-self: center;\n\tjustify-self: center;\n\tcursor: default;\n\tdisplay: block;\n}\n\n.year {\n\tgrid-area: year;\n}\n\n.credits--site {\n\tgrid-area: credits-1;\n\tfont-weight: 600;\n\ttext-decoration: none;\n}\n\n.credits--author {\n\tgrid-area: credits-2;\n}\n\n.credits--author div {\n\tclip-path: polygon(0 0, var(--clip) 0, var(--clip) 100%, 0% 100%);\t\n}\n\n@media screen and (min-width: 60em) {\n\tmain {\n\t\ttext-align: left;\n\t\tpadding: 0;\n\t\toverflow: hidden;\n\t\theight: 100vh;\n\t\tdisplay: grid;\n\t\tgrid-template-columns: 8rem 12vh 9rem 1fr 1fr;\n\t\tgrid-template-rows: 4rem 4rem min-content min-content 1fr 2.5rem;\n\t\tgrid-template-areas: 'logo ... ... pagetitle links'\n\t\t\t\t\t'logo ... ... demos ...'\n\t\t\t\t\t'... ... ... title title'\n\t\t\t\t\t'... ... ... ... subtitle'\n\t\t\t\t\t'... menu ... content play'\n\t\t\t\t\t'year ... credits-1 credits-2 ...';\n\t}\n\t.line {\n\t\tposition: relative;\n\t}\n\n\t.line::before {\n\t\tcontent: '';\n\t\tposition: absolute;\n\t\tbackground: var(--color-line);\n\t}\n\n\t.line--vertical::before {\n\t\tleft: 0;\n\t\twidth: 1px;\n\t\theight: 500vh;\n\t\ttop: -250vh;\n\t}\n\n\t.line--horizontal::before {\n\t\tleft: -250vw;\n\t\twidth: 500vw;\n\t\ttop: 0;\n\t\theight: 1px;\n\t}\n\n\t.content {\n\t\twidth: 90%;\n\t\tpadding: 0;\n\t}\n\n\t.menu__inner {\n\t\twriting-mode: vertical-lr;\n\t\ttransform: rotate(180deg);\n\t\tfont-size: 12vh;\n\t\tpadding: 0;\n\t}\n\t\n}\n\n.webgl {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\tz-index: -1;\n}\n\n/* Style class utilities */\n.oh {\n\toverflow: hidden;\n}\n\n.dib {\n\tdisplay: inline-block;\n}\n\n.db {\n\tdisplay: block;\n}"]} -------------------------------------------------------------------------------- /dist/favicon.2e5f6236.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/WebGLBlobs/613a5baea7c2169ab42e5a335887d5475ad96079/dist/favicon.2e5f6236.ico -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | Creative WebGL Blobs | Demo 1 | Codrops

Creative WebGL Blobs

Insomnia

records

The main reliance, however, in the Emmanuel treatment is on faith, reinforced first by hetero-suggestion and then by patient and persistent auto-suggestion. The man who would be permanently free from insomnia must be an optimist. He must have a philosophy of life wholesome enough to keep him buoyant, cheerful, and serene amid all the changes and the chances of this mortal life.

@codrops
2021
-------------------------------------------------------------------------------- /dist/index2.html: -------------------------------------------------------------------------------- 1 | Creative WebGL Blobs | Demo 2 | Codrops

Creative WebGL Blobs

Insomnia

records

The main reliance, however, in the Emmanuel treatment is on faith, reinforced first by hetero-suggestion and then by patient and persistent auto-suggestion. The man who would be permanently free from insomnia must be an optimist. He must have a philosophy of life wholesome enough to keep him buoyant, cheerful, and serene amid all the changes and the chances of this mortal life.

@codrops
2021
-------------------------------------------------------------------------------- /dist/index3.html: -------------------------------------------------------------------------------- 1 | Creative WebGL Blobs | Demo 3 | Codrops

Creative WebGL Blobs

Insomnia

records

The main reliance, however, in the Emmanuel treatment is on faith, reinforced first by hetero-suggestion and then by patient and persistent auto-suggestion. The man who would be permanently free from insomnia must be an optimist. He must have a philosophy of life wholesome enough to keep him buoyant, cheerful, and serene amid all the changes and the chances of this mortal life.

@codrops
2021
-------------------------------------------------------------------------------- /dist/noise.0c992c6e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/WebGLBlobs/613a5baea7c2169ab42e5a335887d5475ad96079/dist/noise.0c992c6e.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/WebGLBlobs/613a5baea7c2169ab42e5a335887d5475ad96079/favicon.ico -------------------------------------------------------------------------------- /img/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codrops/WebGLBlobs/613a5baea7c2169ab42e5a335887d5475ad96079/img/noise.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Creative WebGL Blobs | Demo 1 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |

Creative WebGL Blobs

19 | 24 | 29 | 30 |

Insomnia

31 |
records
32 |

33 | The main reliance, however, in the Emmanuel treatment is on faith, reinforced first by hetero-suggestion and then by patient and persistent auto-suggestion. The man who would be permanently free from insomnia must be an optimist. He must have a philosophy of life wholesome enough to keep him buoyant, cheerful, and serene amid all the changes and the chances of this mortal life. 34 |

35 | 36 |
@codrops
37 | 38 |
2021
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Creative WebGL Blobs | Demo 2 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |

Creative WebGL Blobs

19 | 24 | 29 | 30 |

Insomnia

31 |
records
32 |

33 | The main reliance, however, in the Emmanuel treatment is on faith, reinforced first by hetero-suggestion and then by patient and persistent auto-suggestion. The man who would be permanently free from insomnia must be an optimist. He must have a philosophy of life wholesome enough to keep him buoyant, cheerful, and serene amid all the changes and the chances of this mortal life. 34 |

35 | 36 |
@codrops
37 | 38 |
2021
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /index3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Creative WebGL Blobs | Demo 3 | Codrops 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |

Creative WebGL Blobs

19 | 24 | 29 | 30 |

Insomnia

31 |
records
32 |

33 | The main reliance, however, in the Emmanuel treatment is on faith, reinforced first by hetero-suggestion and then by patient and persistent auto-suggestion. The man who would be permanently free from insomnia must be an optimist. He must have a philosophy of life wholesome enough to keep him buoyant, cheerful, and serene amid all the changes and the chances of this mortal life. 34 |

35 | 36 |
@codrops
37 | 38 |
2021
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WebGLBlobs", 3 | "version": "1.0.0", 4 | "description": "Distorted blob spheres using Three.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rm -rf dist", 8 | "start": "parcel *.html", 9 | "build": "parcel build *.html" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/codrops/WebGLBlobs.git" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/codrops/WebGLBlobs/issues" 20 | }, 21 | "homepage": "https://github.com/codrops/WebGLBlobs#readme", 22 | "dependencies": { 23 | "dat.gui": "^0.7.7", 24 | "glsl-noise": "^0.0.0", 25 | "glsl-rotate": "^1.1.0", 26 | "gsap": "^3.5.1", 27 | "parcel-bundler": "^1.12.4", 28 | "three": "^0.123.0" 29 | }, 30 | "devDependencies": { 31 | "glslify-bundle": "^5.1.1", 32 | "glslify-deps": "^1.3.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scripts/gl/Blob.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import vertexShader from './shaders/vertex.glsl'; 3 | import fragmentShader from './shaders/fragment.glsl'; 4 | 5 | export default class Blob extends THREE.Object3D { 6 | constructor(size, speed, color, density, strength, offset) { 7 | super(); 8 | 9 | this.geometry = new THREE.IcosahedronGeometry(size, 64); 10 | this.material = new THREE.ShaderMaterial({ 11 | vertexShader, 12 | fragmentShader, 13 | uniforms: { 14 | uTime: { value: 0 }, 15 | uSpeed: { value: speed }, 16 | uNoiseDensity: { value: density }, 17 | uNoiseStrength: { value: strength }, 18 | uFreq: { value: 3 }, 19 | uAmp: { value: 6 }, 20 | uHue: { value: color }, 21 | uOffset: { value: offset }, 22 | red: { value: 0 }, 23 | green: { value: 0 }, 24 | blue: { value: 0 }, 25 | uAlpha: { value: 1.0 }, 26 | }, 27 | defines: { 28 | PI: Math.PI 29 | }, 30 | // wireframe: true, 31 | // side: THREE.DoubleSide 32 | transparent: true, 33 | }); 34 | 35 | this.mesh = new THREE.Mesh(this.geometry, this.material); 36 | 37 | this.add(this.mesh); 38 | } 39 | } -------------------------------------------------------------------------------- /scripts/gl/index.js: -------------------------------------------------------------------------------- 1 | import * as THREE from 'three'; 2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 3 | import Blob from './Blob'; 4 | import gsap from 'gsap'; 5 | 6 | export default new class Gl { 7 | constructor() { 8 | this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); 9 | this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); 10 | this.renderer.setSize(window.innerWidth, window.innerHeight); 11 | this.renderer.setClearColor( 0xffffff, 0 ); 12 | 13 | this.camera = new THREE.PerspectiveCamera( 14 | 45, 15 | window.innerWidth / window.innerHeight, 16 | 0.1, 17 | 1000 18 | ); 19 | this.camera.position.set(0, 0, 18); 20 | 21 | this.scene = new THREE.Scene(); 22 | 23 | 24 | // this.controls = new OrbitControls(this.camera, this.renderer.domElement); 25 | 26 | this.clock = new THREE.Clock(); 27 | 28 | this.mouse = new THREE.Vector2(); 29 | this.mouseTarget = new THREE.Vector2(); 30 | 31 | this.init(); 32 | this.animate(); 33 | } 34 | 35 | init() { 36 | this.addCanvas(); 37 | this.addEvents(); 38 | } 39 | 40 | addCanvas() { 41 | const canvas = this.renderer.domElement; 42 | canvas.classList.add('webgl'); 43 | document.body.appendChild(canvas); 44 | } 45 | 46 | addEvents() { 47 | window.addEventListener('resize', this.resize.bind(this)); 48 | window.addEventListener('mousemove', this.mouseMove.bind(this)); 49 | } 50 | 51 | resize() { 52 | let width = window.innerWidth; 53 | let height = window.innerHeight; 54 | 55 | this.camera.aspect = width / height; 56 | this.renderer.setSize(width, height); 57 | 58 | this.camera.updateProjectionMatrix(); 59 | } 60 | 61 | mouseMove(e) { 62 | // Calculate mouse position in normalized device coordinates 63 | // (-1 to +1) for both components 64 | this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1; 65 | this.mouse.y = - (e.clientY / window.innerHeight) * 2 + 1; 66 | } 67 | 68 | animate() { 69 | requestAnimationFrame(this.animate.bind(this)); 70 | this.render(); 71 | } 72 | 73 | render() { 74 | // this.controls.update(); 75 | 76 | // Remove loading class when scene has objects 77 | if (this.scene.children.length > 0) { 78 | document.body.classList.remove('loading'); 79 | } 80 | 81 | // Update uniforms 82 | this.scene.children.forEach(mesh => { 83 | mesh.material.uniforms.uTime.value = this.clock.getElapsedTime(); 84 | }); 85 | 86 | // Lerp movement 87 | this.mouseTarget.x = gsap.utils.interpolate(this.mouseTarget.x, this.mouse.x, 0.03); 88 | this.mouseTarget.y = gsap.utils.interpolate(this.mouseTarget.y, this.mouse.y, 0.03); 89 | 90 | this.scene.rotation.set( 91 | this.mouseTarget.y * 0.25, 92 | this.mouseTarget.x * 0.25, 93 | 0 94 | ); 95 | 96 | this.renderer.render(this.scene, this.camera); 97 | } 98 | } -------------------------------------------------------------------------------- /scripts/gl/shaders/fragment.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying float vDistort; 3 | 4 | uniform float uTime; 5 | uniform float uHue; 6 | uniform float uAlpha; 7 | 8 | vec3 cosPalette(float t, vec3 a, vec3 b, vec3 c, vec3 d) { 9 | return a + b * cos(6.28318 * (c * t + d)); 10 | } 11 | 12 | void main() { 13 | float distort = vDistort * 2.0; 14 | 15 | vec3 brightness = vec3(0.5, 0.5, 0.5); 16 | vec3 contrast = vec3(0.5, 0.5, 0.5); 17 | vec3 oscilation = vec3(1.0, 1.0, 1.0); 18 | vec3 phase = vec3(0.0, 0.1, 0.2); 19 | 20 | vec3 color = cosPalette(uHue + distort, brightness, contrast, oscilation, phase); 21 | 22 | gl_FragColor = vec4(color, uAlpha); 23 | } -------------------------------------------------------------------------------- /scripts/gl/shaders/vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying float vDistort; 3 | 4 | uniform float uTime; 5 | uniform float uSpeed; 6 | uniform float uNoiseStrength; 7 | uniform float uNoiseDensity; 8 | uniform float uFreq; 9 | uniform float uAmp; 10 | uniform float uOffset; 11 | 12 | #pragma glslify: noise = require(glsl-noise/classic/3d) 13 | #pragma glslify: pnoise = require(glsl-noise/periodic/3d) 14 | #pragma glslify: rotateY = require(glsl-rotate/rotateY) 15 | 16 | float map(float value, float inMin, float inMax, float outMin, float outMax) { 17 | return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin); 18 | } 19 | 20 | void main() { 21 | vUv = uv; 22 | 23 | float t = uTime * uSpeed; 24 | float distortion = pnoise((normal + t) * uNoiseDensity, vec3(10.0)) * uNoiseStrength; 25 | 26 | vec3 pos = position + (normal * distortion); 27 | float angle = sin(uv.y * uFreq + t) * uAmp; 28 | pos = rotateY(pos, angle); 29 | 30 | pos *= map(sin(uTime + uOffset), -1.0, 1.0, 1.0, 1.2); 31 | 32 | vDistort = distortion; 33 | 34 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.); 35 | } -------------------------------------------------------------------------------- /scripts/index.js: -------------------------------------------------------------------------------- 1 | import Gl from './gl'; 2 | import Blob from './gl/Blob'; 3 | 4 | import gsap from 'gsap'; 5 | 6 | class App { 7 | constructor() { 8 | this.blobs = []; 9 | this.addBlobs(); 10 | 11 | // Main animation tl 12 | this.tl = gsap.timeline({ 13 | delay: 0.25, 14 | }); 15 | 16 | this.tl 17 | .add(this.article()) 18 | .add(this.animBlobs(), '-=1.5'); 19 | } 20 | 21 | addBlobs() { 22 | // size, speed, color, freq, density, strength, offset 23 | const blob1 = new Blob(1.75, 0.3, 0.5, 1.5, 0.12, Math.PI * 1); 24 | const blob2 = new Blob(6.0, 0.15, 0.4, 2.0, 0.3, Math.PI * 2); 25 | const blob3 = new Blob(0.8, 0.5, 0.1, 2.0, 0.05, Math.PI * 0.5); 26 | 27 | blob1.position.set(-8.5, 3.25, 2); 28 | blob2.position.set(11, -3, -10); 29 | blob3.position.set(-1, -4, 4); 30 | 31 | blob1.rotation.set(-0.4, 0, 0.5); 32 | blob2.rotation.set(0.4, 1.0, -0.4); 33 | blob3.rotation.set(0, 0, 0); 34 | 35 | this.blobs = [blob1, blob2, blob3]; 36 | 37 | Gl.scene.add(...this.blobs); 38 | } 39 | 40 | article() { 41 | // Main content 42 | const tl = gsap.timeline({ 43 | defaults: { 44 | ease: 'power3.inOut', 45 | } 46 | }); 47 | 48 | // Content clip 49 | const content = document.querySelector('.content span'); 50 | const contentClip = { x: 0 }; 51 | 52 | tl 53 | .from('.title div, .subtitle div', { 54 | duration: 2, 55 | xPercent: -100, 56 | // stagger: 0.1, 57 | }) 58 | .from('.menu__inner-translate', { 59 | duration: 1.5, 60 | yPercent: -100, 61 | }, '-=1.5') 62 | .to(contentClip, { 63 | duration: 1.5, 64 | x: 100, 65 | onUpdate: () => { 66 | content.style.setProperty('--clip', `${contentClip.x}%`); 67 | }, 68 | }, '-=1.25') 69 | .from('.play', { 70 | duration: 1, 71 | scale: 0, 72 | rotate: '-62deg', 73 | }, '-=1.5'); 74 | 75 | return tl; 76 | } 77 | 78 | animBlobs() { 79 | // Move Threejs Blobs 80 | const tl = gsap.timeline({ 81 | defaults: { 82 | duration: 2, 83 | ease: 'power3.inOut' 84 | }, 85 | }); 86 | 87 | const uniformAlphas = [ 88 | this.blobs[0].mesh.material.uniforms.uAlpha, 89 | this.blobs[1].mesh.material.uniforms.uAlpha, 90 | this.blobs[2].mesh.material.uniforms.uAlpha, 91 | ]; 92 | 93 | tl 94 | .from(this.blobs[0].position, { z: -5 }) 95 | .from(this.blobs[1].position, { z: -30 }, '-=1.75') 96 | .from(this.blobs[2].position, { z: 12 }, '-=1.75') 97 | .from(uniformAlphas, { 98 | value: 0, 99 | stagger: 0.2, 100 | ease: 'power3.inOut' 101 | }, 0); 102 | 103 | return tl; 104 | } 105 | } 106 | 107 | new App(); -------------------------------------------------------------------------------- /scripts/index2.js: -------------------------------------------------------------------------------- 1 | import Gl from './gl'; 2 | import Blob from './gl/Blob'; 3 | 4 | import gsap from 'gsap'; 5 | 6 | class App { 7 | constructor() { 8 | this.blob = null; 9 | this.addBlobs(); 10 | 11 | // Main animation tl 12 | this.tl = gsap.timeline({ 13 | delay: 0.25, 14 | }); 15 | 16 | this.tl 17 | .add(this.article()) 18 | .add(this.animBlobs(), '-=1'); 19 | } 20 | 21 | addBlobs() { 22 | // size, speed, color, freq, density, strength, offset 23 | this.blob = new Blob(4.5, 0.15, 1.0, 2.0, 0.3, Math.PI * 2); 24 | this.blob.position.set(0, 0, 0); 25 | this.blob.rotation.set(0, 0, 0); 26 | 27 | Gl.scene.add(this.blob); 28 | } 29 | 30 | article() { 31 | // Main content 32 | const tl = gsap.timeline({ 33 | defaults: { 34 | ease: 'power3.inOut', 35 | } 36 | }); 37 | 38 | // Content clip 39 | const content = document.querySelector('.content span'); 40 | const contentClip = { x: 0 }; 41 | 42 | tl 43 | .from('.title div, .subtitle div', { 44 | duration: 2, 45 | xPercent: -100, 46 | // stagger: 0.1, 47 | }) 48 | .from('.menu__inner-translate', { 49 | duration: 1.5, 50 | yPercent: -100, 51 | }, '-=1.5') 52 | .to(contentClip, { 53 | duration: 1.5, 54 | x: 100, 55 | onUpdate: () => { 56 | content.style.setProperty('--clip', `${contentClip.x}%`); 57 | }, 58 | }, '-=1.25') 59 | .from('.play', { 60 | duration: 1, 61 | scale: 0, 62 | rotate: '-62deg', 63 | }, '-=1.5'); 64 | 65 | return tl; 66 | } 67 | 68 | animBlobs() { 69 | // Move Threejs Blobs 70 | const tl = gsap.timeline({ 71 | defaults: { 72 | duration: 2, 73 | ease: 'power3.inOut' 74 | }, 75 | }); 76 | 77 | tl 78 | .from(this.blob.position, { z: 5, }) 79 | .from(this.blob.mesh.material.uniforms.uAlpha, { 80 | value: 0, 81 | stagger: 0.2, 82 | }, 0); 83 | 84 | return tl; 85 | } 86 | } 87 | 88 | new App(); -------------------------------------------------------------------------------- /scripts/index3.js: -------------------------------------------------------------------------------- 1 | import Gl from './gl'; 2 | import Blob from './gl/Blob'; 3 | 4 | import gsap from 'gsap'; 5 | 6 | class App { 7 | constructor() { 8 | this.blobs = []; 9 | this.addBlobs(); 10 | 11 | // Main animation tl 12 | this.tl = gsap.timeline({ 13 | delay: 0.25, 14 | }); 15 | 16 | this.tl 17 | .add(this.article()) 18 | .add(this.animBlobs(), '-=1.5'); 19 | } 20 | 21 | addBlobs() { 22 | // size, speed, color, freq, density, strength, offset 23 | const blob1 = new Blob(3, 0.3, 0.25, 2.0, 0.15, Math.PI * 1); 24 | const blob2 = new Blob(3, 0.25, 0.5, 1.5, 0.12, Math.PI * 0); 25 | blob1.position.set(-5, 0, 0); 26 | blob2.position.set(5, 0, 0); 27 | 28 | this.blobs = [blob1, blob2]; 29 | 30 | Gl.scene.add(...this.blobs); 31 | } 32 | 33 | article() { 34 | // Main content 35 | const tl = gsap.timeline({ 36 | defaults: { 37 | ease: 'power3.inOut', 38 | } 39 | }); 40 | 41 | // Content clip 42 | const content = document.querySelector('.content span'); 43 | const contentClip = { x: 0 }; 44 | 45 | tl 46 | .from('.title div, .subtitle div', { 47 | duration: 2, 48 | xPercent: -100, 49 | // stagger: 0.1, 50 | }) 51 | .from('.menu__inner-translate', { 52 | duration: 1.5, 53 | yPercent: -100, 54 | }, '-=1.5') 55 | .to(contentClip, { 56 | duration: 1.5, 57 | x: 100, 58 | onUpdate: () => { 59 | content.style.setProperty('--clip', `${contentClip.x}%`); 60 | }, 61 | }, '-=1.25') 62 | .from('.play', { 63 | duration: 1, 64 | scale: 0, 65 | rotate: '-62deg', 66 | }, '-=1.5'); 67 | 68 | return tl; 69 | } 70 | 71 | animBlobs() { 72 | // Move Threejs Blobs 73 | const tl = gsap.timeline(); 74 | 75 | const scales = [ 76 | this.blobs[0].scale, 77 | this.blobs[1].scale, 78 | ]; 79 | 80 | tl 81 | .from(scales, { 82 | duration: 2, 83 | x: 0, 84 | y: 0, 85 | z: 0, 86 | ease: 'power3.inOut', 87 | stagger: 0.2, 88 | }); 89 | 90 | return tl; 91 | } 92 | } 93 | 94 | new App(); --------------------------------------------------------------------------------