├── .DS_Store ├── .gitignore ├── Readme.md ├── assets ├── .DS_Store ├── classic-watch.glb ├── logo.svg └── scene.glb ├── index.html ├── package.json ├── src ├── index.ts └── styles.css └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yvesvinckier/threejs-scrollable-page/746adbe6d6daa3aeea957b71d511940122169f1f/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | 106 | # Docusaurus cache and generated files 107 | .docusaurus 108 | 109 | # Serverless directories 110 | .serverless/ 111 | 112 | # FuseBox cache 113 | .fusebox/ 114 | 115 | # DynamoDB Local files 116 | .dynamodb/ 117 | 118 | # TernJS port file 119 | .tern-port 120 | 121 | # Stores VSCode versions used for testing VSCode extensions 122 | .vscode-test 123 | 124 | # yarn v2 125 | .yarn/cache 126 | .yarn/unplugged 127 | .yarn/build-state.yml 128 | .yarn/install-state.gz 129 | .pnp.* 130 | 131 | package-lock.json 132 | yarn.lock 133 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # WebGi starter project 2 | A template for a vanilla(no ui-framework) project with webgi engine in typescript using parcel bundler. 3 | 4 | About webgi: [https://webgi.xyz/](https://webgi.xyz/) 5 | 6 | ## Running 7 | First install the dependencies: 8 | ```bash 9 | npm install 10 | ``` 11 | 12 | To run the project in development mode: 13 | ```bash 14 | npm start 15 | ``` 16 | Then navigate to [http://localhost:1234/index.html](http://localhost:1234/index.html) in a web browser to see the default scene in a viewer. 17 | 18 | The assets are stored in the `assets` directory. 19 | 20 | To build the project for production: 21 | ```bash 22 | npm run build 23 | ``` 24 | 25 | ## Updates 26 | Check the [webgi manual](https://webgi.xyz/docs/manual/#sdk-links) for the latest version. 27 | To use the different version: 28 | * Update the version number in `package.json` file for both `webgi` and `@types/webgi`. 29 | * Run `npm install` to update the dependencies. 30 | * Delete `.cache` folder created by parcel bundler: `rm -rf .cache` 31 | * Run `npm start` or `npm run build` to run or build the project. 32 | 33 | ## Documentation 34 | For the latest version and documentation: [WebGi Docs](https://webgi.xyz/docs/). 35 | 36 | ## License 37 | For license and terms of use, see the [SDK License](https://webgi.xyz/docs/license). 38 | -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yvesvinckier/threejs-scrollable-page/746adbe6d6daa3aeea957b71d511940122169f1f/assets/.DS_Store -------------------------------------------------------------------------------- /assets/classic-watch.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yvesvinckier/threejs-scrollable-page/746adbe6d6daa3aeea957b71d511940122169f1f/assets/classic-watch.glb -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 20 | 21 | 29 | 30 | 31 | 35 | 39 | 40 | 41 | 42 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /assets/scene.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yvesvinckier/threejs-scrollable-page/746adbe6d6daa3aeea957b71d511940122169f1f/assets/scene.glb -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threejs scrollable page 5 | 6 | 7 | 11 | 12 | 13 | 17 | 21 | 22 | 23 | 24 | 28 | 29 |
30 |
31 |
32 |

Doris Armchair

33 |

34 | Before they sold out salvia aesthetic, hexagon disrupt sustainable 35 | vaporware crucifix succulents kale chips. Selvage knausgaard 36 | scenester. 37 |

38 | 39 |
40 |
41 | 42 |
43 |
44 |

Smooth design

45 |

46 | Humblebrag pickled listicle yes plz williamsburg shoreditch tumblr, 47 | put a bird on it cred knausgaard snackwave scenester. Before they 48 | sold out salvia aesthetic. 49 |

50 |
51 |
52 | 53 |
54 |
55 |

Softness of the upholstery

56 |

57 | Humblebrag pickled listicle yes plz williamsburg shoreditch tumblr, 58 | put a bird on it cred knausgaard snackwave scenester. Before they 59 | sold out salvia aesthetic. 60 |

61 | 62 |
63 |
64 | 65 | 76 |
77 | 78 |
79 | 80 | 81 |
82 | 83 |
84 | 89 |
90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgi-vanilla-typescript", 3 | "version": "1.0.0", 4 | "description": "WebGi + TypeScript example starter project", 5 | "main": "index.html", 6 | "scripts": { 7 | "start": "parcel index.html", 8 | "build": "parcel build index.html --no-minify --public-url ./" 9 | }, 10 | "devDependencies": { 11 | "@types/three": "^0.139.0", 12 | "@types/webgi": "https://storage.googleapis.com/dist.pixotronics.com/webgi/runtime/bundle-types-0.5.8.tgz", 13 | "parcel-bundler": "^1.6.1", 14 | "parcel-plugin-static-files-copy": "^2.6.0", 15 | "typescript": "4.4.4", 16 | "webgi": "https://storage.googleapis.com/dist.pixotronics.com/webgi/runtime/bundle-0.5.8.tgz" 17 | }, 18 | "resolutions": { 19 | "@babel/preset-env": "7.13.8" 20 | }, 21 | "keywords": [ 22 | "typescript", 23 | "javascript" 24 | ], 25 | "staticFiles": { 26 | "staticPath": [ 27 | { 28 | "staticPath": "assets", 29 | "staticOutDir": "assets" 30 | } 31 | ] 32 | }, 33 | "dependencies": { 34 | "gsap": "^3.11.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ViewerApp, 3 | AssetManagerPlugin, 4 | GBufferPlugin, 5 | ProgressivePlugin, 6 | TonemapPlugin, 7 | SSRPlugin, 8 | SSAOPlugin, 9 | BloomPlugin, 10 | GammaCorrectionPlugin, 11 | MeshBasicMaterial2, 12 | Color, 13 | // Color, // Import THREE.js internals 14 | // Texture, // Import THREE.js internals 15 | } from "webgi"; 16 | import "./styles.css"; 17 | 18 | import gsap from "gsap"; 19 | import { ScrollTrigger } from "gsap/ScrollTrigger"; 20 | 21 | gsap.registerPlugin(ScrollTrigger); 22 | 23 | async function setupViewer() { 24 | // Initialize the viewer 25 | const viewer = new ViewerApp({ 26 | canvas: document.getElementById("webgi-canvas") as HTMLCanvasElement, 27 | // isAntialiased: true, 28 | }); 29 | 30 | // Add some plugins 31 | const manager = await viewer.addPlugin(AssetManagerPlugin); 32 | const camera = viewer.scene.activeCamera; 33 | const position = camera.position; 34 | const target = camera.target; 35 | const exitButton = document.querySelector(".button--exit") as HTMLElement; 36 | const customizerInterface = document.querySelector( 37 | ".customizer--container" 38 | ) as HTMLElement; 39 | 40 | // Add plugins individually. 41 | await viewer.addPlugin(GBufferPlugin); 42 | await viewer.addPlugin(new ProgressivePlugin(32)); 43 | // await viewer.addPlugin(new TonemapPlugin(true)); 44 | await viewer.addPlugin(new TonemapPlugin(!viewer.useRgbm)); 45 | await viewer.addPlugin(GammaCorrectionPlugin); 46 | await viewer.addPlugin(SSRPlugin); 47 | await viewer.addPlugin(SSAOPlugin); 48 | await viewer.addPlugin(BloomPlugin); 49 | 50 | // This must be called once after all plugins are added. 51 | viewer.renderer.refreshPipeline(); 52 | 53 | await manager.addFromPath("./assets/scene.glb"); 54 | 55 | const drillMaterial = manager.materials!.findMaterialsByName( 56 | "FABRIC" 57 | )[0] as MeshBasicMaterial2; 58 | 59 | // viewer.getPlugin(TonemapPlugin)!.config!.clipBackground = true; // in case its set to false in the glb 60 | 61 | viewer.scene.activeCamera.setCameraOptions({ controlsEnabled: false }); 62 | 63 | onUpdate(); 64 | 65 | function setupScrollanimation() { 66 | const tl = gsap.timeline(); 67 | 68 | // First section 69 | tl.to(position, { 70 | x: 0.0, 71 | y: 0.0, 72 | z: 3.25, 73 | scrollTrigger: { 74 | trigger: ".second", 75 | start: "top bottom", 76 | end: "top top", 77 | scrub: true, 78 | immediateRender: false, 79 | }, 80 | onUpdate, 81 | }) 82 | .to(".section--one--container", { 83 | yPercent: "-150", 84 | opacity: 0, 85 | scrollTrigger: { 86 | trigger: ".second", 87 | start: "top bottom", 88 | end: "top top", 89 | scrub: 1, 90 | immediateRender: false, 91 | }, 92 | }) 93 | .to(target, { 94 | x: 0, 95 | y: 0, 96 | z: 0, 97 | scrollTrigger: { 98 | trigger: ".second", 99 | start: "top bottom", 100 | end: "top top", 101 | scrub: true, 102 | immediateRender: false, 103 | }, 104 | }) 105 | // Last section 106 | 107 | .to(position, { 108 | x: -3.0, 109 | y: 0.0, 110 | z: 0.0, 111 | scrollTrigger: { 112 | trigger: ".third", 113 | start: "top bottom", 114 | end: "top top", 115 | scrub: true, 116 | immediateRender: false, 117 | }, 118 | onUpdate, 119 | }) 120 | .to(target, { 121 | x: 0, 122 | y: 0, 123 | z: 0, 124 | scrollTrigger: { 125 | trigger: ".third", 126 | start: "top bottom", 127 | end: "top top", 128 | scrub: true, 129 | immediateRender: false, 130 | }, 131 | }); 132 | } 133 | 134 | setupScrollanimation(); 135 | 136 | // WEBBI UPDATE 137 | let needsUpdate = true; 138 | 139 | function onUpdate() { 140 | needsUpdate = true; 141 | viewer.renderer.resetShadows(); 142 | } 143 | 144 | viewer.addEventListener("preFrame", () => { 145 | if (needsUpdate) { 146 | camera.positionTargetUpdated(true); 147 | needsUpdate = false; 148 | } 149 | }); 150 | 151 | // KNOW MORE EVENT 152 | document.querySelector(".button--hero")?.addEventListener("click", () => { 153 | const element = document.querySelector(".second"); 154 | window.scrollTo({ 155 | top: element?.getBoundingClientRect().top, 156 | left: 0, 157 | behavior: "smooth", 158 | }); 159 | }); 160 | 161 | // SCROLL TO TOP 162 | document.querySelectorAll(".button--footer")?.forEach((item) => { 163 | item.addEventListener("click", () => { 164 | window.scrollTo({ top: 0, left: 0, behavior: "smooth" }); 165 | }); 166 | }); 167 | 168 | // CUSTOMIZE 169 | const sections = document.querySelector(".container") as HTMLElement; 170 | const webgiCanvasContainer = document.getElementById( 171 | "webgi-canvas-container" 172 | ) as HTMLElement; 173 | document 174 | .querySelector(".button--customize") 175 | ?.addEventListener("click", () => { 176 | sections.style.visibility = "hidden"; 177 | webgiCanvasContainer.style.pointerEvents = "all"; 178 | document.body.style.cursor = "grab"; 179 | gsap.to(position, { 180 | x: -1.95, 181 | y: 1.54, 182 | z: 2.29, 183 | duration: 2, 184 | ease: "power3.inOut", 185 | onUpdate, 186 | }); 187 | gsap.to(target, { 188 | x: 0, 189 | y: 0, 190 | z: 0, 191 | duration: 2, 192 | ease: "power3.inOut", 193 | onUpdate, 194 | onComplete: enableControlers, 195 | }); 196 | }); 197 | 198 | function enableControlers() { 199 | exitButton.style.visibility = "visible"; 200 | customizerInterface.style.visibility = "visible"; 201 | viewer.scene.activeCamera.setCameraOptions({ controlsEnabled: true }); 202 | } 203 | 204 | // EXIT CUSTOMIZE 205 | exitButton.addEventListener("click", () => { 206 | gsap.to(position, { 207 | x: -3.0, 208 | y: 0.0, 209 | z: 0.0, 210 | duration: 1, 211 | ease: "power3.inOut", 212 | onUpdate, 213 | }); 214 | gsap.to(target, { 215 | x: 0, 216 | y: 0, 217 | z: 0, 218 | duration: 1, 219 | ease: "power3.inOut", 220 | onUpdate, 221 | }); 222 | viewer.scene.activeCamera.setCameraOptions({ controlsEnabled: false }); 223 | sections.style.visibility = "visible"; 224 | webgiCanvasContainer.style.pointerEvents = "none"; 225 | document.body.style.cursor = "default"; 226 | exitButton.style.visibility = "hidden"; 227 | customizerInterface.style.visibility = "hidden"; 228 | }); 229 | 230 | document 231 | .querySelector(".button--colors.violet") 232 | ?.addEventListener("click", () => { 233 | changeColor(new Color(0xaa83aa).convertSRGBToLinear()); 234 | }); 235 | 236 | document 237 | .querySelector(".button--colors.red") 238 | ?.addEventListener("click", () => { 239 | changeColor(new Color(0xa57d7d).convertSRGBToLinear()); 240 | }); 241 | 242 | document 243 | .querySelector(".button--colors.green") 244 | ?.addEventListener("click", () => { 245 | changeColor(new Color(0x96b2a1).convertSRGBToLinear()); 246 | }); 247 | 248 | function changeColor(_colorToBeChanged: Color) { 249 | drillMaterial.color = _colorToBeChanged; 250 | viewer.scene.setDirty(); 251 | } 252 | } 253 | 254 | setupViewer(); 255 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "DM Sans", sans-serif; 3 | overflow-x: hidden; 4 | margin: 0; 5 | padding: 0; 6 | overscroll-behavior: none; 7 | } 8 | 9 | .nav--bar { 10 | position: absolute; 11 | display: flex; 12 | align-content: space-between; 13 | align-items: center; 14 | width: 100%; 15 | justify-content: space-between; 16 | margin-top: 30px; 17 | z-index: 1; 18 | } 19 | 20 | .logo { 21 | margin-left: 40px; 22 | height: 80px; 23 | } 24 | 25 | .section { 26 | display: flex; 27 | width: 100vw; 28 | height: 100vh; 29 | align-items: center; 30 | position: relative; 31 | z-index: 1; 32 | } 33 | 34 | .section--one--container { 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | width: 100%; 39 | } 40 | 41 | .first h1 { 42 | font-size: 8rem; 43 | line-height: 8rem; 44 | letter-spacing: -0.3rem; 45 | margin: 0 0 30px -10px; 46 | text-align: center; 47 | } 48 | 49 | .first p { 50 | max-width: 920px; 51 | margin: 0 auto; 52 | text-align: center; 53 | font-family: "Roboto Mono", monospace; 54 | } 55 | 56 | .button--hero { 57 | background: #000000; 58 | color: #ffffff; 59 | border: none; 60 | font-weight: 600; 61 | border-radius: 50px; 62 | padding: 15px 30px; 63 | margin-top: 24px; 64 | margin-right: 40px; 65 | font-size: 0.6rem; 66 | cursor: pointer; 67 | transition: all 0.8s ease; 68 | } 69 | 70 | .button--customize { 71 | background: #000000; 72 | color: #ffffffff; 73 | border: none; 74 | font-weight: 600; 75 | border-radius: 50px; 76 | padding: 15px 30px; 77 | margin-top: 24px; 78 | margin-right: 40px; 79 | font-size: 0.6rem; 80 | cursor: pointer; 81 | transition: all 0.8s ease; 82 | } 83 | .button--hero:hover { 84 | background: black; 85 | color: white; 86 | } 87 | 88 | .button--navbar { 89 | background: black; 90 | color: white; 91 | border: none; 92 | border-radius: 50px; 93 | padding: 10px 30px; 94 | margin-right: 40px; 95 | font-size: 0.6rem; 96 | height: 42px; 97 | } 98 | svg { 99 | fill: #ffffff; 100 | } 101 | 102 | .second { 103 | justify-content: flex-end; 104 | position: relative; 105 | } 106 | 107 | .section--two--container { 108 | width: 360px; 109 | margin-right: 10%; 110 | z-index: 1; 111 | } 112 | 113 | p { 114 | font-family: "Roboto Mono", monospace; 115 | } 116 | 117 | .section--third--container { 118 | width: 360px; 119 | margin-left: 10%; 120 | z-index: 1; 121 | } 122 | 123 | h2 { 124 | font-size: 3rem; 125 | letter-spacing: -2px; 126 | line-height: 2.5rem; 127 | } 128 | 129 | footer { 130 | background: black; 131 | padding: 70px; 132 | display: flex; 133 | flex-direction: column; 134 | justify-content: center; 135 | align-items: center; 136 | position: relative; 137 | z-index: 1; 138 | } 139 | footer p { 140 | padding-top: 40px; 141 | text-align: center; 142 | } 143 | 144 | .button--footer { 145 | background: white; 146 | color: black; 147 | border: none; 148 | border-radius: 50px; 149 | padding: 10px 30px; 150 | font-size: 0.6rem; 151 | cursor: pointer; 152 | } 153 | 154 | #webgi-canvas { 155 | width: 100%; 156 | height: 100%; 157 | position: fixed; 158 | top: 0; 159 | } 160 | 161 | #webgi-canvas-container { 162 | width: 100vw; 163 | height: 100vh; 164 | pointer-events: none; 165 | position: fixed; 166 | display: flex; 167 | justify-content: flex-end; 168 | flex-direction: column; 169 | align-items: center; 170 | } 171 | 172 | .button--exit { 173 | width: 90px; 174 | margin-bottom: 5%; 175 | z-index: 5; 176 | position: fixed; 177 | bottom: 0; 178 | visibility: hidden; 179 | } 180 | 181 | .customizer--container { 182 | position: fixed; 183 | top: 50%; 184 | visibility: hidden; 185 | } 186 | 187 | .strap--colors { 188 | list-style: none; 189 | } 190 | 191 | .button--colors { 192 | width: 30px; 193 | height: 30px; 194 | border-radius: 50px; 195 | padding: 0; 196 | margin-bottom: 10px; 197 | cursor: pointer; 198 | transition: all 0.8s ease; 199 | } 200 | 201 | .button--colors:hover { 202 | filter: drop-shadow(0 0 0.4rem rgba(79, 79, 79, 0.704)); 203 | transform: scale(1.1); 204 | } 205 | 206 | .green { 207 | background-color: rgb(150, 178, 161); 208 | } 209 | /* 0x96b2a1 */ 210 | 211 | .red { 212 | background-color: rgb(165, 125, 125); 213 | } 214 | /* 0xa57d7d */ 215 | 216 | .violet { 217 | background-color: rgb(170, 131, 170); 218 | } 219 | /* 0xaa83aa */ 220 | 221 | .credit { 222 | color: #ffffff; 223 | font-size: 0.6rem; 224 | } 225 | 226 | .credit a { 227 | color: #ffffff; 228 | } 229 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "jsx": "preserve", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "allowJs": true, 9 | "lib": [ 10 | "es6", 11 | "dom" 12 | ], 13 | "rootDir": "src", 14 | "moduleResolution": "node" 15 | } 16 | } --------------------------------------------------------------------------------