<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Wassim SAMAD</title>
    <description>The latest articles on Forem by Wassim SAMAD (@wawasensei).</description>
    <link>https://forem.com/wawasensei</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F306378%2F6a3d71ea-f833-4793-a805-22127b26795c.jpg</url>
      <title>Forem: Wassim SAMAD</title>
      <link>https://forem.com/wawasensei</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/wawasensei"/>
    <language>en</language>
    <item>
      <title>How to Create a VTuber Studio with Three.js, React &amp; VRM</title>
      <dc:creator>Wassim SAMAD</dc:creator>
      <pubDate>Fri, 25 Apr 2025 12:07:04 +0000</pubDate>
      <link>https://forem.com/wawasensei/how-to-create-a-vtuber-studio-with-threejs-react-vrm-2f6f</link>
      <guid>https://forem.com/wawasensei/how-to-create-a-vtuber-studio-with-threejs-react-vrm-2f6f</guid>
      <description>&lt;p&gt;Want to build your own VTuber studio on the web? Thanks to modern tools like Three.js, VRM models, and Mediapipe, creating an interactive 3D avatar that mimics your facial expressions is totally possible—even in your browser.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll go over how to bring a VTuber avatar to life using Three.js with React. For detailed code and implementation, check out the full walkthrough video below.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/6MP48RFhe2Y"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch the video version on YouTube&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a VRM?
&lt;/h2&gt;

&lt;p&gt;Let’s have a look at what VRM are.&lt;/p&gt;

&lt;p&gt;VRM stands for &lt;strong&gt;Virtual Reality Model&lt;/strong&gt;. It’s a 3D avatar format widely used by &lt;strong&gt;VTubers&lt;/strong&gt; and in &lt;strong&gt;games&lt;/strong&gt;, thanks to its standardized structure that makes it easy to reuse the same character across different platforms and experiences.&lt;/p&gt;

&lt;p&gt;You can &lt;strong&gt;find free avatars&lt;/strong&gt; or &lt;strong&gt;create your own&lt;/strong&gt; using the free tool &lt;a href="https://vroid.com/en/studio" rel="noopener noreferrer"&gt;VRoid Studio&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are also utility libraries available to load VRM models into engines like &lt;strong&gt;Unity&lt;/strong&gt; and &lt;strong&gt;Three.js&lt;/strong&gt;, making integration super accessible for developers.&lt;/p&gt;

&lt;p&gt;👉 For more info, visit the official &lt;a href="https://vrm.dev/en/" rel="noopener noreferrer"&gt;VRM documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Find VRM Models
&lt;/h2&gt;

&lt;p&gt;If you’re just getting started, the fastest way is to download pre-made avatars from the community.&lt;/p&gt;

&lt;p&gt;🎨 Explore a huge library of user-created characters here:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://hub.vroid.com/en" rel="noopener noreferrer"&gt;hub.vroid.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once downloaded, you’ll get a &lt;code&gt;.vrm&lt;/code&gt; file, which you can load directly into your Three.js scene using the right loader.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Your VRM
&lt;/h2&gt;

&lt;p&gt;Want to create a custom avatar?&lt;br&gt;&lt;br&gt;
&lt;a href="https://vroid.com/en/studio" rel="noopener noreferrer"&gt;VRoid Studio&lt;/a&gt; is your go-to tool. It’s a free app for Windows and macOS that lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Design anime-style avatars with a visual editor
&lt;/li&gt;
&lt;li&gt;Customize facial features, hair, clothing, and more
&lt;/li&gt;
&lt;li&gt;Export the finished avatar in the &lt;code&gt;.vrm&lt;/code&gt; format. &lt;a href="https://vroid.pixiv.help/hc/en-us/articles/38726063278233-How-do-I-export-a-model-as-VRM" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is a link to the documentation on how to export the VRM model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exported file will be ready to use in any VTuber application—including your own studio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load and Control VRM with Three.js
&lt;/h2&gt;

&lt;p&gt;To bring your avatar into a web environment, you’ll need Three.js and the &lt;strong&gt;VRM Loader&lt;/strong&gt; with &lt;a href="https://github.com/pixiv/three-vrm" rel="noopener noreferrer"&gt;@pixiv/three-vrm&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;The basic steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up your Three.js scene
&lt;/li&gt;
&lt;li&gt;Load the &lt;code&gt;.vrm&lt;/code&gt; file with the VRMLoader
&lt;/li&gt;
&lt;li&gt;Add it to your scene and animate it with custom logic
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The VRM file includes bones and expressions that you can control the usual way. Either manually, through FBX animations, or with camera tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Mediapipe with Kalidokit to Control the Avatar
&lt;/h2&gt;

&lt;p&gt;For real-time face and body tracking, Google’s &lt;a href="https://ai.google.dev/edge/mediapipe/solutions/vision/holistic_landmarker" rel="noopener noreferrer"&gt;Mediapipe&lt;/a&gt; is incredible. It runs in the browser using WebAssembly and gives you landmark data for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eyes
&lt;/li&gt;
&lt;li&gt;Mouth
&lt;/li&gt;
&lt;li&gt;Face mesh
&lt;/li&gt;
&lt;li&gt;Hands
&lt;/li&gt;
&lt;li&gt;Pose
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But Mediapipe only gives you raw data. This is where &lt;a href="https://github.com/yeemachine/kalidokit" rel="noopener noreferrer"&gt;Kalidokit&lt;/a&gt; comes in. It maps Mediapipe’s landmarks to a VRM model's facial expressions and bones.&lt;/p&gt;

&lt;p&gt;Here’s how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mediapipe tracks your webcam feed and extracts landmark data.
&lt;/li&gt;
&lt;li&gt;Kalidokit interprets that data and calculates bone rotations.
&lt;/li&gt;
&lt;li&gt;You apply these values to your VRM model in Three.js.
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s like motion capture, but in real time and in your browser.&lt;/p&gt;

&lt;p&gt;Checkout &lt;a href="https://3d.kalidoface.com/" rel="noopener noreferrer"&gt;Kalidoface&lt;/a&gt; made by the creators of Kalidokit. It’s a very well polished app that uses the same technology to track your face and apply it to a VRM model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining VRM avatars, Three.js rendering, Mediapipe tracking, and Kalidokit’s mapping magic, you can build your very own VTuber Studio—all with web technologies.&lt;/p&gt;

&lt;p&gt;Whether you’re planning to stream, build a game, or just experiment with 3D avatars, this setup gives you a powerful and accessible starting point.&lt;/p&gt;

&lt;p&gt;🧠 Want to see it all in action? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch the full video tutorial above for a step-by-step guide.&lt;/li&gt;
&lt;li&gt;Check out the code on &lt;a href="https://github.com/wass08/r3f-vrm-final" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Check the final project at &lt;a href="https://vrm.wawasensei.dev/" rel="noopener noreferrer"&gt;https://vrm.wawasensei.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>threejs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>wawa-vfx: Open-Source VFX + Particle System for React Three Fiber Projects</title>
      <dc:creator>Wassim SAMAD</dc:creator>
      <pubDate>Tue, 15 Apr 2025 05:57:02 +0000</pubDate>
      <link>https://forem.com/wawasensei/wawa-vfx-open-source-vfx-particle-system-for-react-three-fiber-projects-151l</link>
      <guid>https://forem.com/wawasensei/wawa-vfx-open-source-vfx-particle-system-for-react-three-fiber-projects-151l</guid>
      <description>&lt;p&gt;&lt;strong&gt;Add visual effects, particles, and trails to your 3D web apps with ease with &lt;code&gt;wawa-vfx&lt;/code&gt; engine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wass08/wawa-vfx" rel="noopener noreferrer"&gt;wawa-vfx&lt;/a&gt; is a lightweight, composable, and performance-focused &lt;strong&gt;visual effects engine&lt;/strong&gt; tailored for &lt;strong&gt;React Three Fiber&lt;/strong&gt;. Ideal if you're building a game, a product configurator, or an interactive 3D website, this library helps you adding &lt;strong&gt;GPU-accelerated particles&lt;/strong&gt;, &lt;strong&gt;bursts&lt;/strong&gt;, and &lt;strong&gt;trails&lt;/strong&gt; with minimal effort.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Preview of the wawa-vfx engine in action!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  💡 Why &lt;code&gt;wawa-vfx&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;Through multiple projects, I found myself needing a &lt;strong&gt;simple yet powerful VFX engine&lt;/strong&gt; that could easily integrate with &lt;strong&gt;Three.js&lt;/strong&gt; and &lt;strong&gt;React Three Fiber&lt;/strong&gt;. I wanted to create stunning visual effects without the complexity of traditional game engines or the steep learning curve of shader programming.&lt;/p&gt;

&lt;p&gt;I often found myself &lt;strong&gt;re-inventing the wheel&lt;/strong&gt;, creating &lt;strong&gt;custom shaders&lt;/strong&gt; and &lt;strong&gt;effects from scratch&lt;/strong&gt;. This was time-consuming and inefficient, while the code was often very similar across projects. I wanted to focus on &lt;strong&gt;building experiences&lt;/strong&gt; rather than getting bogged down in the technical details of VFX.&lt;/p&gt;

&lt;p&gt;During my course development, I realized there was a real need for a &lt;strong&gt;modular, React-friendly VFX system&lt;/strong&gt; that doesn't rely on bulky engines or require advanced shader knowledge. So I built &lt;code&gt;wawa-vfx&lt;/code&gt; to teach core VFX concepts &lt;strong&gt;while keeping it practical, performant, and production-ready&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;VFX engine&lt;/strong&gt; we build together in &lt;a href="https://wawasensei.dev/courses/react-three-fiber/lessons/vfx-engine" rel="noopener noreferrer"&gt;this lesson&lt;/a&gt; from my course: &lt;a href="https://wawasensei.dev/courses/react-three-fiber" rel="noopener noreferrer"&gt;React Three Fiber: The Ultimate Guide to 3D Web Development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then, I dedicated two lessons to learn how to leverage the engine to create lovely &lt;strong&gt;vfx&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first one is the &lt;a href="https://wawasensei.dev/courses/react-three-fiber/lessons/fireworks" rel="noopener noreferrer"&gt;Fireworks lesson&lt;/a&gt; 🎇 where we create a &lt;strong&gt;landing page&lt;/strong&gt; allowing us to trigger different types of fireworks.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Preview of the Fireworks lesson or &lt;a href="https://fireworks.wawasensei.dev/" rel="noopener noreferrer"&gt;follow this link&lt;/a&gt; to try it out yourself!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second one is the &lt;a href="https://wawasensei.dev/courses/react-three-fiber/lessons/wizard" rel="noopener noreferrer"&gt;Wizard Game&lt;/a&gt; in which we create a &lt;strong&gt;game&lt;/strong&gt; where you can shoot different spells (Fire, Ice, Void) to fight orcs. 🧙&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Preview of the Wizard Game lesson or &lt;a href="https://wizard.wawasensei.dev/" rel="noopener noreferrer"&gt;follow this link&lt;/a&gt; to try it out yourself!&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Features
&lt;/h2&gt;

&lt;p&gt;Here is what the &lt;a href="https://github.com/wass08/wawa-vfx" rel="noopener noreferrer"&gt;wawa-vfx&lt;/a&gt; engine offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💻 &lt;strong&gt;Built for React Three Fiber&lt;/strong&gt; (R3F-first design)&lt;/li&gt;
&lt;li&gt;✨ Declarative effects like particles, bursts, and trails&lt;/li&gt;
&lt;li&gt;⚙️ GPU-powered and optimized for real-time rendering&lt;/li&gt;
&lt;li&gt;🧱 Custom geometries&lt;/li&gt;
&lt;li&gt;🎓 Fully explained in my course for complete understanding&lt;/li&gt;
&lt;li&gt;🤝 Open to contributions and community-driven improvements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📦 Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;wawa-vfx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn add wawa-vfx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;To use the &lt;code&gt;wawa-vfx&lt;/code&gt; engine, you need to use the two main components: &lt;code&gt;VFXParticles&lt;/code&gt; and &lt;code&gt;VFXEmitter&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;VFXParticles&lt;/code&gt;: Defines the particle system and its rendering properties&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VFXEmitter&lt;/code&gt;: Controls where, when and how particles are emitted into the scene&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Basic Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;VFXEmitter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VFXParticles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wawa-vfx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MyEffect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Step 1: Define your particle system */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;VFXParticles&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"particles"&lt;/span&gt; &lt;span class="c1"&gt;// A unique identifier for this particle system&lt;/span&gt;
        &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;nbParticles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Maximum number of particles to allocate&lt;/span&gt;
          &lt;span class="na"&gt;gravity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;9.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Apply gravity (x, y, z)&lt;/span&gt;
          &lt;span class="na"&gt;fadeSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Size fade in/out settings&lt;/span&gt;
          &lt;span class="na"&gt;fadeOpacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Opacity fade in/out settings&lt;/span&gt;
          &lt;span class="na"&gt;renderMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "billboard" or "mesh"&lt;/span&gt;
          &lt;span class="na"&gt;intensity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Brightness multiplier&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;VFXParticles&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Step 2: Define your emitter */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;VFXEmitter&lt;/span&gt;
        &lt;span class="na"&gt;debug&lt;/span&gt; &lt;span class="c1"&gt;// Show debug visualization&lt;/span&gt;
        &lt;span class="na"&gt;emitter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"particles"&lt;/span&gt; &lt;span class="c1"&gt;// Target the particle system by name&lt;/span&gt;
        &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Continuously emit particles (only if `spawnMode` is 'time')&lt;/span&gt;
          &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Emission cycle duration in seconds&lt;/span&gt;
          &lt;span class="na"&gt;nbParticles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Number of particles to emit per cycle&lt;/span&gt;
          &lt;span class="na"&gt;spawnMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;time&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Emission mode: 'time' or 'burst'&lt;/span&gt;
          &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Time delay before starting emission&lt;/span&gt;

          &lt;span class="c1"&gt;// Particle lifetime range [min, max]&lt;/span&gt;
          &lt;span class="na"&gt;particlesLifetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Position range (min/max)&lt;/span&gt;
          &lt;span class="na"&gt;startPositionMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;startPositionMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Rotation range (min/max)&lt;/span&gt;
          &lt;span class="na"&gt;startRotationMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;startRotationMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="c1"&gt;// Rotation speed range (min/max)&lt;/span&gt;
          &lt;span class="na"&gt;rotationSpeedMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;rotationSpeedMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Direction range (min/max)&lt;/span&gt;
          &lt;span class="na"&gt;directionMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;directionMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Particle size range [min, max]&lt;/span&gt;
          &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Particle speed range [min, max]&lt;/span&gt;
          &lt;span class="na"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Color at start - an array of strings for random selection&lt;/span&gt;
          &lt;span class="na"&gt;colorStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skyblue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

          &lt;span class="c1"&gt;// Color at end - an array of strings for random selection&lt;/span&gt;
          &lt;span class="na"&gt;colorEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pink&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;VFXEmitter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explore the &lt;a href="https://github.com/wass08/wawa-vfx/tree/main/examples" rel="noopener noreferrer"&gt;source code of the examples&lt;/a&gt; you can find on the &lt;a href="https://wawa-vfx.wawasensei.dev/" rel="noopener noreferrer"&gt;demo page&lt;/a&gt; of the &lt;strong&gt;VFXEngine&lt;/strong&gt; to see how to use the engine in different scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Open for Contributions
&lt;/h2&gt;

&lt;p&gt;The project is open-source and community-driven!&lt;br&gt;
Feel free to check out the repo, submit issues, contribute ideas, or open pull requests.&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/wass08/wawa-vfx" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>threejs</category>
      <category>react</category>
    </item>
    <item>
      <title>What is a 3D website? Explanation for beginners</title>
      <dc:creator>Wassim SAMAD</dc:creator>
      <pubDate>Tue, 15 Apr 2025 05:51:30 +0000</pubDate>
      <link>https://forem.com/wawasensei/what-is-a-3d-website-explanation-for-beginners-3m3h</link>
      <guid>https://forem.com/wawasensei/what-is-a-3d-website-explanation-for-beginners-3m3h</guid>
      <description>&lt;p&gt;&lt;strong&gt;From flat pages to immersive digital worlds, here’s what makes a 3D website — and why they’re shaping the future of the web.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🆚 2D vs. 3D Websites
&lt;/h2&gt;

&lt;p&gt;Most websites you use every day — blogs, shops, social platforms — are 2D. They present content like text, images, and videos in a scrollable, flat layout. You view information, click links, and move from one page to another.&lt;/p&gt;

&lt;p&gt;But with &lt;strong&gt;3D websites&lt;/strong&gt;, you don’t just scroll — &lt;strong&gt;you explore&lt;/strong&gt;. These experiences add &lt;strong&gt;depth, interaction, and immersion&lt;/strong&gt;, creating a digital space you can move through, like a virtual environment or game.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 What Makes a Website 3D?
&lt;/h2&gt;

&lt;p&gt;Despite the name, 3D websites don’t need to be fully three-dimensional. They can be a mix of 2D and 3D elements, but they all share a few key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;3D Models&lt;/strong&gt;: Objects created in 3D software (like Blender or Maya) that can be manipulated and viewed from different angles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive Elements&lt;/strong&gt;: Users can click, drag, or hover over objects to trigger animations or changes in the scene.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Lighting and Shadows&lt;/strong&gt;: Real-time lighting effects that change based on the scene and user interactions, adding depth and realism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animations&lt;/strong&gt;: Objects can move, rotate, or change shape in response to user actions or events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camera Controls&lt;/strong&gt;: Users can navigate the scene using mouse or touch controls, allowing them to explore the environment from different perspectives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Rendering Techniques&lt;/strong&gt;: 3D can be used to create advanced sliders, carousels, and other UI elements that feel more alive and engaging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🎨 Interactive Visual Experience
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9irfrde7z3ug5bbvq7g7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9irfrde7z3ug5bbvq7g7.webp" alt="Wawa Sensei Virtual Teacher" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learning with a virtual teacher&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Realistic lighting, textures, and materials&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of static images, 3D websites use real-time lighting and rich textures to create scenes that feel tangible — like walking into a showroom or gallery.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Explore virtual objects and environments&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine rotating a 3D product, zooming into a design, or walking through a digital building — all in your browser.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Immersion that feels like a digital world&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users don’t just observe — they experience. This kind of depth and interaction makes content far more engaging.&lt;/p&gt;




&lt;h3&gt;
  
  
  🕹️ Enhanced User Engagement
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kycqkn2rmk2hh1vtu4c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kycqkn2rmk2hh1vtu4c.webp" alt="3D Car Slider" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;3D car slider&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Real-time interactions make content feel alive&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3D websites respond dynamically to user input, allowing people to &lt;strong&gt;move around, manipulate objects, or follow interactive stories&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control the experience&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From custom camera views to clickable 3D elements, users feel more in control of their journey through the site.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Increased engagement and retention&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more engaging your website is, the longer people stay — and the more likely they are to remember your brand or product.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔧 Powered by Modern Web Technologies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Built using WebGL/WebGPU&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technologies like &lt;a href="https://get.webgl.org/" rel="noopener noreferrer"&gt;WebGL&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API" rel="noopener noreferrer"&gt;WebGPU&lt;/a&gt; allows our browsers to render 3D graphics directly, without needing plugins or special software.&lt;/p&gt;

&lt;p&gt;Frameworks like &lt;a href="https://threejs.org/" rel="noopener noreferrer"&gt;Three.js&lt;/a&gt;, &lt;a href="https://r3f.docs.pmnd.rs/" rel="noopener noreferrer"&gt;React Three Fiber&lt;/a&gt; and &lt;a href="https://www.babylonjs.com/" rel="noopener noreferrer"&gt;Babylon.js&lt;/a&gt; make it easy to create complex 3D scenes that run smoothly in the browser.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No plugins or downloads needed&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs natively in the browser — no special software or extensions required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Responsive across devices&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether on desktop or mobile, 3D websites can be optimized for performance and accessibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎓 Want to Build One Yourself?
&lt;/h2&gt;

&lt;p&gt;If you’re excited by the idea of creating your own interactive 3D website — &lt;strong&gt;you can absolutely learn to do it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Check out my course:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://wawasensei.dev/courses/react-three-fiber" rel="noopener noreferrer"&gt;React Three Fiber: The Ultimate Guide to 3D Web Development&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In it, I walk you through the process of building fully interactive 3D experiences using &lt;strong&gt;React Three Fiber&lt;/strong&gt;, even if you’re new to 3D or React.&lt;/p&gt;

&lt;p&gt;Through project-based learning, you’ll gain hands-on experience with the tools and techniques used to create stunning 3D websites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3D fundamentals&lt;/li&gt;
&lt;li&gt;Cameras, lights, shadows, and materials&lt;/li&gt;
&lt;li&gt;How to find, prepare, and import 3D models&lt;/li&gt;
&lt;li&gt;Interactivity &amp;amp; animations&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;Shaders, VFX &amp;amp; post-processing&lt;/li&gt;
&lt;li&gt;New technologies WebGPU and Three.js Shading Language (TSL)&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;em&gt;Preview of the camera controls lesson&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;Preview of the image slider lesson&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To finish by building a &lt;a href="https://r3f-ultimate-portfolio-u4.vm.elestio.app/" rel="noopener noreferrer"&gt;professional 3D website portfolio&lt;/a&gt; that showcases your work and skills.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;The portfolio we build together in the course!&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Conclusion
&lt;/h2&gt;

&lt;p&gt;3D websites are opening the door to a new kind of digital experience — one that’s immersive, interactive, and deeply engaging. As more businesses and creators seek to stand out online, the demand for 3D web experiences will only grow.&lt;/p&gt;

&lt;p&gt;With the right tools and a bit of curiosity, anyone can start building their own.&lt;br&gt;&lt;br&gt;
Start exploring, start building — and maybe, your next website won’t just be seen — &lt;strong&gt;it’ll be experienced&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;🔗 &lt;em&gt;Want to get started?&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Join hundreds of developers learning React Three Fiber today:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://wawasensei.dev/courses/react-three-fiber" rel="noopener noreferrer"&gt;https://wawasensei.dev/courses/react-three-fiber&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>threejs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating a 3D Table Configurator with React Three Fiber</title>
      <dc:creator>Wassim SAMAD</dc:creator>
      <pubDate>Tue, 21 Feb 2023 01:00:25 +0000</pubDate>
      <link>https://forem.com/wawasensei/creating-a-3d-table-configurator-with-react-three-fiber-1dl2</link>
      <guid>https://forem.com/wawasensei/creating-a-3d-table-configurator-with-react-three-fiber-1dl2</guid>
      <description>&lt;p&gt;Let's create a 3D Table Configurator using the following libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Tailwind&lt;/li&gt;
&lt;li&gt;Three.js&lt;/li&gt;
&lt;li&gt;React Three Fiber&lt;/li&gt;
&lt;li&gt;Material UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔥 This tutorial is a good starting point to create a product configurator for an exciting shopping experience. &lt;/p&gt;

&lt;p&gt;The main topics covered are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to load a 3D model&lt;/li&gt;
&lt;li&gt;how to modify it using a user interface&lt;/li&gt;
&lt;li&gt;how to scale and move items smoothly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A video version is also available where you can watch the final render:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/wW0XwNhrDFQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;I prepared you a &lt;a href="https://github.com/wass08/table-configurator-three-js-r3F-tutorial-starter" rel="noopener noreferrer"&gt;starter pack&lt;/a&gt; including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a React app created with Vite&lt;/li&gt;
&lt;li&gt;a MUI User Interface&lt;/li&gt;
&lt;li&gt;three.js/React Three Fiber installed including a Canvas displaying a cube to get started&lt;/li&gt;
&lt;li&gt;the 3D model of the table in &lt;code&gt;public/models&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuubbyeodpwwxaiiglppa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuubbyeodpwwxaiiglppa.png" alt="3D Table model" width="800" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The table model contains all the different legs layout&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2dp6mgczg2kxbui70gt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe2dp6mgczg2kxbui70gt.png" alt="Table model hierarchy" width="694" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I separated in different meshes the left and right legs to make it simple when we will expand the table width.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Clone the repo and run *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5b92z5muklvlhtk66z0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5b92z5muklvlhtk66z0.png" alt="A simple 3D cube" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see a cube and be able to rotate around it thanks to &lt;code&gt;&amp;lt;OrbitControls /&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Staging
&lt;/h2&gt;

&lt;p&gt;Now let’s create a better staging environment. I rarely start from scratch, I recommend you to go through &lt;a href="https://docs.pmnd.rs/react-three-fiber/getting-started/examples" rel="noopener noreferrer"&gt;React Three Fiber examples&lt;/a&gt; to find a good starting point.&lt;/p&gt;

&lt;p&gt;I chose one named &lt;a href="https://codesandbox.io/s/if9crg" rel="noopener noreferrer"&gt;stage presets, gltfjsx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eyo25ss8ce30wx69zjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eyo25ss8ce30wx69zjj.png" alt="3D car" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It contains nice lighting and shadows settings.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We start by copying the canvas with the camera settings and enabling shadows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Canvas shadows camera={{ position: [4, 4, -12], fov: 35 }}&amp;gt;
...
&amp;lt;/Canvas&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I set &lt;code&gt;y position&lt;/code&gt; of the camera to &lt;code&gt;4&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then we copy the &lt;code&gt;stage&lt;/code&gt; component and &lt;code&gt;orbitcontrols&lt;/code&gt; and instead of using their model, we use the cube for now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Stage intensity={1.5} environment="city" shadows={{
  type: "accumulative",
  color: "#85ffbd",
  colorBlend: 2,
  opacity: 2,
}}
  adjustCamera={2}
&amp;gt;
...
&amp;lt;OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I assigned the color to the one of the gradient in the CSS index file. &lt;br&gt;
It helps to build good looking shadows on the floor. I also changed adjustCamera to &lt;code&gt;2&lt;/code&gt; to zoom out a little bit.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minPolarAngle&lt;/code&gt; and &lt;code&gt;maxPolarAngle&lt;/code&gt; are limits you can define on &lt;code&gt;OrbitControls&lt;/code&gt; to avoid going below or over the model.&lt;/p&gt;

&lt;p&gt;Adjust those parameters to what you prefer.&lt;/p&gt;

&lt;p&gt;We can’t see shadows yet even if the canvas and stage have shadows enabled. &lt;/p&gt;

&lt;p&gt;We need to tell our mesh to castShadows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4vwxdchf127350mw8dj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4vwxdchf127350mw8dj.png" alt="Adding cast shadow to our mesh" width="494" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can see the very smooth shadow generated by the stage component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf07vqmd79bfv57ddzdn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf07vqmd79bfv57ddzdn.png" alt="Cube with shadows" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Load the table
&lt;/h2&gt;

&lt;p&gt;Let’s render our table instead of the default cube.&lt;/p&gt;

&lt;p&gt;To do so we use the &lt;code&gt;gltfjsx&lt;/code&gt; client with&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx gltjfs public/models/Table.gltf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;It generates a &lt;code&gt;Table.js&lt;/code&gt; file containing a React component with the extracted meshes from the table model file.&lt;/p&gt;

&lt;p&gt;Let’s copy everything, delete the file, and create a new one in the &lt;code&gt;components&lt;/code&gt; folder named &lt;code&gt;Table.jsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By default it’s named &lt;code&gt;Model&lt;/code&gt;, rename it to &lt;code&gt;Table&lt;/code&gt;. We need to fix the path adding &lt;code&gt;./models&lt;/code&gt; for both &lt;code&gt;useGltf&lt;/code&gt; and the &lt;code&gt;preload&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklqkn4y1owgfjguum87r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklqkn4y1owgfjguum87r.png" alt="Table component code" width="800" height="295"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The final component code should looks like this&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let’s replace the cube with the table in &lt;code&gt;Experience.jsx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9ehfeieajsn69vjrwgw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx9ehfeieajsn69vjrwgw.png" alt="Experience code" width="720" height="908"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s no shadows yet. So let’s add the &lt;code&gt;castShadow&lt;/code&gt; prop on every meshes on the &lt;code&gt;Table&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;mesh casthShadows /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bxsqrqr9dfgtrm65bpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bxsqrqr9dfgtrm65bpn.png" alt="Table model with shadows enabled" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our table renders shadows correctly, but all the legs layouts are displayed at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Legs layout
&lt;/h2&gt;

&lt;p&gt;To be able to display only one layout at a time let’s create a folder named &lt;code&gt;contexts&lt;/code&gt; and a file named &lt;code&gt;Configurator.jsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's create a context Boilerplate with &lt;code&gt;createContext&lt;/code&gt; &lt;code&gt;ConfiguratorProvider&lt;/code&gt; and &lt;code&gt;useConfigurator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmalr6y6l28k6ayr4u0zc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmalr6y6l28k6ayr4u0zc.png" alt="Context boilerplate" width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, now we want to have the choice between our legs layout, so we define &lt;code&gt;legs&lt;/code&gt; and &lt;code&gt;setLegs&lt;/code&gt; with useState. &lt;br&gt;
Our layouts will be 0, 1 and 2. so let’s default to 0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [legs, setLegs] = useState(0);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go back to our table and get the legs from useConfigurator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { legs } = useConfigurator();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we will simply do conditional rendering for our legs.&lt;/p&gt;

&lt;p&gt;If it’s 0, we render the first one, if it’s 1 we render the second layout, and if it’s 2 we render the last one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{legs === 0 &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;mesh
            castShadow
            geometry={nodes.Legs01Left.geometry}
            material={materials.Metal}
            position={[-1.5, 0, 0]}
          /&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs01Right.geometry}
            material={materials.Metal}
            position={[1.5, 0, 0]}
            castShadow
          /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      {legs === 1 &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs02Left.geometry}
            material={materials.Metal}
            position={[-1.5, 0, 0]}
            castShadow
          /&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs02Right.geometry}
            material={materials.Metal}
            position={[1.5, 0, 0]}
            castShadow
          /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      {legs === 2 &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs03Left.geometry}
            material={materials.Metal}
            position={[-1.5, 0, 0]}
            castShadow
          /&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs03Right.geometry}
            material={materials.Metal}
            position={[1.5, 0, 0]}
            castShadow
          /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;In a real project you could refactor it more nicely.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now jump into &lt;code&gt;main.jsx&lt;/code&gt; and wrap the app in our &lt;code&gt;ConfiguratorProvider&lt;/code&gt; to make our context available everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnvjwsr256xmqf5eujp0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsnvjwsr256xmqf5eujp0.png" alt="main source code" width="800" height="186"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Wrapping the App in the Configurator provider&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae9xds3yxbap6xkvu637.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fae9xds3yxbap6xkvu637.png" alt="3D Table with first layout" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It works, now we only see the first legs layout!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let’s add the &lt;code&gt;Interface&lt;/code&gt; component I prepared for you next to the canvas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Canvas shadows camera={{ position: [4, 4, -12], fov: 35 }}&amp;gt;
  &amp;lt;Experience /&amp;gt;
&amp;lt;/Canvas&amp;gt;
&amp;lt;Interface /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fruv8sgh5djzqldo9ey2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fruv8sgh5djzqldo9ey2m.png" alt="Interface enabled" width="304" height="758"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;You now can see the different options available.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let's go to our &lt;code&gt;Interface&lt;/code&gt; code.&lt;/p&gt;

&lt;p&gt;I commented the full settings so we don’t mess up with the naming.&lt;/p&gt;

&lt;p&gt;Let’s grab legs and setLegs from our configurator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [legs, setLegs] = useConfigurator();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and let’s uncomment the value and onChange on our layout radio buttons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq8wofojpdgdcw5up867.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnq8wofojpdgdcw5up867.png" alt="Code uncomment on the interface" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now when we switch, the legs change correctly...&lt;/p&gt;

&lt;p&gt;...but the shadows are not re-rendered because the scene doesn’t know something changes.&lt;/p&gt;

&lt;p&gt;A simple way to tell it is to get the legs value in the experience component to force the re-rendering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { legs } = useConfigurator();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Add this line to the &lt;code&gt;Experience&lt;/code&gt; component.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now the shadows are re-generated when we switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Legs color
&lt;/h2&gt;

&lt;p&gt;Let’s change the legs color. Go to the &lt;code&gt;Configurator&lt;/code&gt; and create &lt;code&gt;legsColor&lt;/code&gt; and &lt;code&gt;setLegsColor&lt;/code&gt; with the same default value we have in the interface. Feel free to change it.&lt;br&gt;
Don't forget to add it to the exposed values from the context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4y81syr8ypjqxwn0chxi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4y81syr8ypjqxwn0chxi.png" alt="Context code with legs colors" width="800" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's apply the color to the &lt;code&gt;Table&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { legs, legsColor, setLegsColor } = useConfigurator();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add a useEffect with legsColor so every time it changes, this function will get called.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  materials.Metal.color = new Three.Color(legsColor);
}, [legsColor]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to add this import line manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as Three from "three";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the interface let’s add the &lt;code&gt;legsColor&lt;/code&gt; and &lt;code&gt;setLegsColor&lt;/code&gt; and uncomment the value and onChange.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;FormControl&amp;gt;
  &amp;lt;FormLabel&amp;gt;Legs Color&amp;lt;/FormLabel&amp;gt;
  &amp;lt;RadioGroup
    value={legsColor}
    onChange={(e) =&amp;gt; setLegsColor(e.target.value)}
&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v4jjxpiy6hg4xl43xx2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v4jjxpiy6hg4xl43xx2.png" alt="Table with gold legs" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the color of our legs changes every time we apply it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table width
&lt;/h2&gt;

&lt;p&gt;Last step, let’s change the width from our table!&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;Configurator&lt;/code&gt; context create a &lt;code&gt;tableWidth&lt;/code&gt; state with a default value of &lt;code&gt;100&lt;/code&gt; (centimeters)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7sgzlcx99x91cy8her16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7sgzlcx99x91cy8her16.png" alt="Context code with table width" width="800" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Our final context should looks like this&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Get it into the &lt;code&gt;Table&lt;/code&gt; component, and let’s calculate a &lt;code&gt;scalingPercentage&lt;/code&gt; by diving the tableWidth per one hundred.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const tableWidthScale = tableWidth / 100;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the plate, change the &lt;code&gt;scale&lt;/code&gt; with the tableWidthScale on the &lt;code&gt;x&lt;/code&gt; axis, and keep the &lt;code&gt;y&lt;/code&gt; and &lt;code&gt;z&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmc0ofkmmmzjodlezm0jg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmc0ofkmmmzjodlezm0jg.png" alt="Apply table width scale" width="796" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;Interface&lt;/code&gt; let’s uncomment the slider &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;onChange&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { tableWidth, setTableWidth, legs, setLegs, legsColor, setLegsColor } =
    useConfigurator();
...
&amp;lt;FormControl&amp;gt;
  &amp;lt;FormLabel&amp;gt;Table width&amp;lt;/FormLabel&amp;gt;
  &amp;lt;Slider
  sx={{
  width: "200px",
  }}
  min={50}
  max={200}
  value={tableWidth}
  onChange={(e) =&amp;gt; setTableWidth(e.target.value)}
  valueLabelDisplay="auto"
  /&amp;gt;
&amp;lt;/FormControl&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our table width change but we need to move the legs accordingly, we can do it simply by multiplying the &lt;code&gt;x&lt;/code&gt; position by the tableWidthScale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvu686pvesjo1m7073sz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frvu686pvesjo1m7073sz.png" alt="Code apply x position of the table" width="800" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adjusting the &lt;code&gt;x&lt;/code&gt; position of the table legs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzd97vcecuza3gqt9i8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzd97vcecuza3gqt9i8z.png" alt="3D table with a longer width" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now it works correctly, but still the movement is not very smooth&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Smooth animation
&lt;/h2&gt;

&lt;p&gt;To make a smooth animation when we changes the table width, let’s store references of our plate, left legs and right legs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const plate = useRef();
const leftLegs = useRef();
const rightLegs = useRef();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because our &lt;code&gt;leftLegs&lt;/code&gt; and &lt;code&gt;rightlegs&lt;/code&gt; are never rendered at the same time, we can save the 3 legs layouts in the same references.&lt;/p&gt;

&lt;p&gt;Let’s remove the tableWidthScale multiplier as we will do it another way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{legs === 0 &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;mesh
            castShadow
            geometry={nodes.Legs01Left.geometry}
            material={materials.Metal}
            position={[-1.5, 0, 0]}
            ref={leftLegs}
          /&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs01Right.geometry}
            material={materials.Metal}
            position={[1.5, 0, 0]}
            castShadow
            ref={rightLegs}
          /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      {legs === 1 &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs02Left.geometry}
            material={materials.Metal}
            position={[-1.5, 0, 0]}
            castShadow
            ref={leftLegs}
          /&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs02Right.geometry}
            material={materials.Metal}
            position={[1.5, 0, 0]}
            castShadow
            ref={rightLegs}
          /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      {legs === 2 &amp;amp;&amp;amp; (
        &amp;lt;&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs03Left.geometry}
            material={materials.Metal}
            position={[-1.5, 0, 0]}
            castShadow
            ref={leftLegs}
          /&amp;gt;
          &amp;lt;mesh
            geometry={nodes.Legs03Right.geometry}
            material={materials.Metal}
            position={[1.5, 0, 0]}
            castShadow
            ref={rightLegs}
          /&amp;gt;
        &amp;lt;/&amp;gt;
      )}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;useFrame&lt;/code&gt; hook which will be called at each frame.&lt;/p&gt;

&lt;p&gt;It provides the &lt;code&gt;state&lt;/code&gt;, and the &lt;code&gt;delta&lt;/code&gt; time. which is the time between elapsed from the last frame.&lt;/p&gt;

&lt;p&gt;We declare a targetScale Vector3 with tableWidthScale on the &lt;code&gt;x&lt;/code&gt; axis. And on the plate scale we use the lerp function to transition smoothly from our currentScale into our targetScale.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useFrame } from "@react-three/fiber"; 
...
useFrame((_state, delta) =&amp;gt; {
  const tableWidthScale = tableWidth / 100;
  const targetScale = new Vector3(tableWidthScale, 1, 1);

  plate.current.scale.lerp(targetScale, delta);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it animates smoothly, but it’s too slow, let’s define an &lt;code&gt;ANIM_SPEED&lt;/code&gt; constant of &lt;code&gt;12&lt;/code&gt; and multiply the delta by it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ANIM_SPEED = 12;
...
plate.current.scale.lerp(targetScale, delta * ANIM_SPEED);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s do the same process for both legs impacting the &lt;code&gt;position&lt;/code&gt; instead of the &lt;code&gt;scale&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
const ANIM_SPEED = 12;

export function Table(props) {
  const { nodes, materials } = useGLTF("./models/Table.gltf");

  const { legs, legsColor, tableWidth } = useConfigurator();

  const plate = useRef();
  const leftLegs = useRef();
  const rightLegs = useRef();

  useEffect(() =&amp;gt; {
    materials.Metal.color = new Three.Color(legsColor);
  }, [legsColor]);

  useFrame((_state, delta) =&amp;gt; {
    const tableWidthScale = tableWidth / 100;
    const targetScale = new Vector3(tableWidthScale, 1, 1);

    plate.current.scale.lerp(targetScale, delta * ANIM_SPEED);

    const targetLeftPosition = new Vector3(-1.5 * tableWidthScale, 0, 0);
    leftLegs.current.position.lerp(targetLeftPosition, delta * ANIM_SPEED);

    const targetRightPosition = new Vector3(1.5 * tableWidthScale, 0, 0);
    rightLegs.current.position.lerp(targetRightPosition, delta * ANIM_SPEED);
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrwurc0e887cy8hecgyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrwurc0e887cy8hecgyi.png" alt="Final result of the 3D table configurator" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yes! Our table configurator now works perfectly!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations you now have a great starting point to build a 3D product configurator using React Three Fiber and Material UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/p/github/wass08/table-configurator-three-js-r3F-tutorial-final/main?workspaceId=5cd54208-3423-4f91-a7e8-14ca07bdc9af&amp;amp;file=%2Fsrc%2FApp.jsx" rel="noopener noreferrer"&gt;Live preview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code is available here:&lt;br&gt;
&lt;a href="https://github.com/wass08/table-configurator-three-js-r3F-tutorial-final" rel="noopener noreferrer"&gt;https://github.com/wass08/table-configurator-three-js-r3F-tutorial-final&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I highly recommend you to read &lt;a href="https://docs.pmnd.rs/react-three-fiber/getting-started/introduction" rel="noopener noreferrer"&gt;React Three Fiber documentation&lt;/a&gt; and check their &lt;a href="https://docs.pmnd.rs/react-three-fiber/getting-started/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt; to discover what you can achieve and how to do it.&lt;/p&gt;

&lt;p&gt;For more React Three Fiber tutorials you can check my &lt;a href="https://www.youtube.com/watch?v=LNvn66zJyKs&amp;amp;list=PLpepLKamtPjiUF6PvVUbIFhx9HaS0qJs_" rel="noopener noreferrer"&gt;Three.js/React Three Fiber&lt;/a&gt; playlist on YouTube.&lt;/p&gt;

&lt;p&gt;Thank you, don't hesitate to ask your questions in the comments section 🙏&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>threejs</category>
    </item>
    <item>
      <title>Scroll animations with React Three Fiber and GSAP</title>
      <dc:creator>Wassim SAMAD</dc:creator>
      <pubDate>Wed, 15 Feb 2023 12:40:05 +0000</pubDate>
      <link>https://forem.com/wawasensei/scroll-animations-with-react-three-fiber-and-gsap-273j</link>
      <guid>https://forem.com/wawasensei/scroll-animations-with-react-three-fiber-and-gsap-273j</guid>
      <description>&lt;p&gt;Let's animate our 3D model and our user interface to follow the page scroll with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vite&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Tailwind&lt;/li&gt;
&lt;li&gt;Three.js&lt;/li&gt;
&lt;li&gt;React Three Fiber&lt;/li&gt;
&lt;li&gt;GSAP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔥 This tutorial is a good starting point to prepare a good looking portfolio. &lt;/p&gt;

&lt;p&gt;A video version is also available where you can watch the final render:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/pXpckHDDNYo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;Let's start by creating a React app with Vite&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn create vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the react/javascript template&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F075pp5ywf7kbkyuqxemi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F075pp5ywf7kbkyuqxemi.png" alt="Terminal screenshot using vite create app" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now add the dependencies for React Three Fiber&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add three @react-three/drei @react-three/fiber
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;code&gt;index.css&lt;/code&gt; and remove everything inside (keep the file we will use it later)&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;App.css&lt;/code&gt; replace everything with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#root {
  width: 100vw;
  height: 100vh;
  background-color: #d9afd9;
  background-image: linear-gradient(0deg, #d9afd9 0%, #97d9e1 100%);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a folder named &lt;code&gt;components&lt;/code&gt; and inside create an &lt;code&gt;Experience.jsx&lt;/code&gt; file. It's where we'll build our 3D experience.&lt;/p&gt;

&lt;p&gt;Inside let's create a cube and add &lt;code&gt;OrbitControls&lt;/code&gt; from React Three Drei:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { OrbitControls } from "@react-three/drei";

export const Experience = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;OrbitControls /&amp;gt;
      &amp;lt;mesh&amp;gt;
        &amp;lt;boxBufferGeometry /&amp;gt;
        &amp;lt;meshNormalMaterial /&amp;gt;
      &amp;lt;/mesh&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's open &lt;code&gt;App.jsx&lt;/code&gt; and replace the content with a &lt;code&gt;Canvas&lt;/code&gt; that will hold our Three.js components and the &lt;code&gt;Experience&lt;/code&gt; component we just built&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Canvas } from "@react-three/fiber";
import "./App.css";
import { Experience } from "./components/Experience";

function App() {
  return (
    &amp;lt;Canvas&amp;gt;
      &amp;lt;Experience /&amp;gt;
    &amp;lt;/Canvas&amp;gt;
  );
}

export default App;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and run the project with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a cube and be able to rotate around it with your mouse (thanks to &lt;code&gt;OrbitControls&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkezgnqe1ik58ughnxaci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkezgnqe1ik58ughnxaci.png" alt="The first render of a 3D cube" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading the 3D Model
&lt;/h2&gt;

&lt;p&gt;You can get the model from &lt;a href="https://github.com/wass08/r3f-scrolling-animation-tutorial/tree/main/public/models" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't forget to say thanks to &lt;a href="https://twitter.com/thayumiko" rel="noopener noreferrer"&gt;Thaís&lt;/a&gt; for building this beautiful model for us 🙏&lt;/p&gt;

&lt;p&gt;Now in your terminal run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx gltfjsx publics/models/WawaOffice.glb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/pmndrs/gltfjsx" rel="noopener noreferrer"&gt;gtlfjsx&lt;/a&gt; is a client to automatically create a react component from your 3D model. It even supports TypeScript.&lt;/p&gt;

&lt;p&gt;You should have this WawaOffice.js generated&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkc6zexe5dz8qcsr8ab3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkc6zexe5dz8qcsr8ab3s.png" alt="Source code of the generated react component" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy everything and create &lt;code&gt;Office.jsx&lt;/code&gt; in the &lt;code&gt;components&lt;/code&gt; folder and paste the component.&lt;/p&gt;

&lt;p&gt;Rename the component from &lt;code&gt;Model&lt;/code&gt; to &lt;code&gt;Òffice&lt;/code&gt; and fix the path to &lt;code&gt;./models/WawaOffice.glb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now your office should be like that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/

import React, { useRef } from 'react'
import { useGLTF } from '@react-three/drei'

export function Office(props) {
  const { nodes, materials } = useGLTF('./models/WawaOffice.glb')
  return (
    &amp;lt;group {...props} dispose={null}&amp;gt;
      &amp;lt;mesh geometry={nodes['01_office'].geometry} material={materials['01']} /&amp;gt;
      &amp;lt;mesh geometry={nodes['02_library'].geometry} material={materials['02']} position={[0, 2.11, -2.23]} /&amp;gt;
      &amp;lt;mesh geometry={nodes['03_attic'].geometry} material={materials['03']} position={[-1.97, 4.23, -2.2]} /&amp;gt;
    &amp;lt;/group&amp;gt;
  )
}

useGLTF.preload('./models/WawaOffice.glb')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in &lt;code&gt;Experience.jsx&lt;/code&gt; replace the &lt;code&gt;mesh&lt;/code&gt; with the &lt;code&gt;&amp;lt;Office /&amp;gt;&lt;/code&gt; component and add an &lt;code&gt;&amp;lt;ambientLight intensity={1}/&amp;gt;&lt;/code&gt; to avoid seeing the model in black.&lt;/p&gt;

&lt;p&gt;By the ways, this model contains baked textures (this is why it is quite big). What it means is that all lighting and shadows were made in Blender and baked using raytracing into a texture file to have this good looking result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animate the model on scroll
&lt;/h2&gt;

&lt;p&gt;Let's wrap our &lt;code&gt;Office&lt;/code&gt; component into &lt;code&gt;ScrollControls&lt;/code&gt; from React Three Drei&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ScrollControls pages={3} damping={0.25}&amp;gt;
    &amp;lt;Office /&amp;gt;
&amp;lt;/ScrollControls&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pages&lt;/code&gt; is the number of pages you want. Consider a page equals the height of the viewport. &lt;br&gt;
&lt;code&gt;damping&lt;/code&gt; is the smoothing factor. I had good results with &lt;code&gt;0.25&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Additional info in the &lt;a href="https://github.com/pmndrs/drei#scrollcontrols" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You should see a scrollbar appearing but you can't scroll because the &lt;code&gt;OrbitControls&lt;/code&gt; are catching the scroll event.&lt;/p&gt;

&lt;p&gt;Simply disable it as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;OrbitControls enableZoom={false} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To have control over our office animation we need to install &lt;a href="https://greensock.com/docs/v3/GSAP/gsap.to()" rel="noopener noreferrer"&gt;gsap library&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add gsap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the &lt;code&gt;Office.jsx&lt;/code&gt; and store a &lt;code&gt;ref&lt;/code&gt;to the main group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const ref = useRef();

return (
    &amp;lt;group
      {...props}
      dispose={null}
      ref={ref}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create ou &lt;code&gt;gsap&lt;/code&gt; timeline inside a &lt;code&gt;useLayoutEffect&lt;/code&gt; and we will update the group y position from it's current position to &lt;code&gt;-FLOOR_HEIGHT * (NB_FLOORS - 1)&lt;/code&gt; for a duration of 2 seconds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const FLOOR_HEIGHT = 2.3;
export const NB_FLOORS = 3;

export function Office(props) {
...

useLayoutEffect(() =&amp;gt; {
    tl.current = gsap.timeline();

    // VERTICAL ANIMATION
    tl.current.to(
      ref.current.position,
      {
        duration: 2,
        y: -FLOOR_HEIGHT * (NB_FLOORS - 1),
      },
      0
    );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use a duration of 2 seconds because we have 3 pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first page and initial position is 0 second&lt;/li&gt;
&lt;li&gt;The second is 1 second&lt;/li&gt;
&lt;li&gt;The third page is the end of the animation (2 seconds)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We scroll in reverse order the office based on the Y axis because we scroll the office and not the camera. As we go from bottom to top we need to decrease the vertical position of the office.&lt;/p&gt;

&lt;p&gt;Now let's play our animation. We have access to the scroll with &lt;code&gt;useScroll&lt;/code&gt; hook it contains an offset property with a value between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt; to represent the current scroll percentage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const scroll = useScroll();

useFrame(() =&amp;gt; {
    tl.current.seek(scroll.offset * tl.current.duration());
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our &lt;code&gt;Office&lt;/code&gt; scroll vertically following our page scroll.&lt;/p&gt;

&lt;p&gt;Let's use the same principles to animate the floors positions and rotation.&lt;/p&gt;

&lt;p&gt;Here is what I ended with, but feel free to adjust it to what you prefer!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
*/

import { useGLTF, useScroll } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import gsap from "gsap";
import React, { useLayoutEffect, useRef } from "react";

export const FLOOR_HEIGHT = 2.3;
export const NB_FLOORS = 3;

export function Office(props) {
  const { nodes, materials } = useGLTF("./models/WawaOffice.glb");
  const ref = useRef();
  const tl = useRef();
  const libraryRef = useRef();
  const atticRef = useRef();

  const scroll = useScroll();

  useFrame(() =&amp;gt; {
    tl.current.seek(scroll.offset * tl.current.duration());
  });

  useLayoutEffect(() =&amp;gt; {
    tl.current = gsap.timeline();

    // VERTICAL ANIMATION
    tl.current.to(
      ref.current.position,
      {
        duration: 2,
        y: -FLOOR_HEIGHT * (NB_FLOORS - 1),
      },
      0
    );

    // Office Rotation
    tl.current.to(
      ref.current.rotation,
      { duration: 1, x: 0, y: Math.PI / 6, z: 0 },
      0
    );
    tl.current.to(
      ref.current.rotation,
      { duration: 1, x: 0, y: -Math.PI / 6, z: 0 },
      1
    );

    // Office movement
    tl.current.to(
      ref.current.position,
      {
        duration: 1,
        x: -1,
        z: 2,
      },
      0
    );
    tl.current.to(
      ref.current.position,
      {
        duration: 1,
        x: 1,
        z: 2,
      },
      1
    );

    // LIBRARY FLOOR
    tl.current.from(
      libraryRef.current.position,
      {
        duration: 0.5,
        x: -2,
      },
      0.5
    );
    tl.current.from(
      libraryRef.current.rotation,
      {
        duration: 0.5,
        y: -Math.PI / 2,
      },
      0
    );

    // ATTIC
    tl.current.from(
      atticRef.current.position,
      {
        duration: 1.5,
        y: 2,
      },
      0
    );

    tl.current.from(
      atticRef.current.rotation,
      {
        duration: 0.5,
        y: Math.PI / 2,
      },
      1
    );

    tl.current.from(
      atticRef.current.position,
      {
        duration: 0.5,

        z: -2,
      },
      1.5
    );
  }, []);

  return (
    &amp;lt;group
      {...props}
      dispose={null}
      ref={ref}
      position={[0.5, -1, -1]}
      rotation={[0, -Math.PI / 3, 0]}
    &amp;gt;
      &amp;lt;mesh geometry={nodes["01_office"].geometry} material={materials["01"]} /&amp;gt;
      &amp;lt;group position={[0, 2.11, -2.23]}&amp;gt;
        &amp;lt;group ref={libraryRef}&amp;gt;
          &amp;lt;mesh
            geometry={nodes["02_library"].geometry}
            material={materials["02"]}
          /&amp;gt;
        &amp;lt;/group&amp;gt;
      &amp;lt;/group&amp;gt;
      &amp;lt;group position={[-1.97, 4.23, -2.2]}&amp;gt;
        &amp;lt;group ref={atticRef}&amp;gt;
          &amp;lt;mesh
            geometry={nodes["03_attic"].geometry}
            material={materials["03"]}
          /&amp;gt;
        &amp;lt;/group&amp;gt;
      &amp;lt;/group&amp;gt;
    &amp;lt;/group&amp;gt;
  );
}

useGLTF.preload("./models/WawaOffice.glb");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have nice animations based on your page scroll.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing the UI with Tailwind
&lt;/h2&gt;

&lt;p&gt;Let's create a UI. You can use whatever you want to style it but I chose my lover &lt;a href="https://tailwindcss.com/docs/guides/vite" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate a &lt;code&gt;tailwind.config.cjs&lt;/code&gt; replace the content with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/** @type {import('tailwindcss').Config} */

const defaultTheme = require("tailwindcss/defaultTheme");

module.exports = {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      serif: ["Playfair Display", ...defaultTheme.fontFamily.sans],
      sans: ["Poppins", ...defaultTheme.fontFamily.sans],
    },
  },
  plugins: [],
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells tailwind to watch into the &lt;code&gt;.html&lt;/code&gt; and &lt;code&gt;.jsx&lt;/code&gt; files and it changed the default fonts to one I chose from &lt;a href="https://fonts.google.com/" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now in &lt;code&gt;index.css&lt;/code&gt; add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@import url("https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600&amp;amp;family=Poppins&amp;amp;display=swap");

@tailwind base;
@tailwind components;
@tailwind utilities;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The first line is the Google font import&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ok now we have Tailwind installed let's create our UI.&lt;/p&gt;

&lt;p&gt;Create a component named &lt;code&gt;Overlay&lt;/code&gt; with the following content&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Scroll } from "@react-three/drei";

const Section = (props) =&amp;gt; {
  return (
    &amp;lt;section className={`h-screen flex flex-col justify-center p-10 ${
        props.right ? "items-end" : "items-start"
      }`}
      &amp;lt;div className="w-1/2 flex items-center justify-center"&amp;gt;
        &amp;lt;div className="max-w-sm w-full"&amp;gt;
          &amp;lt;div className="bg-white  rounded-lg px-8 py-12"&amp;gt;
            {props.children}
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/section&amp;gt;
  );
};

export const Overlay = () =&amp;gt; {
  return (
    &amp;lt;Scroll html&amp;gt;
      &amp;lt;div class="w-screen"&amp;gt;
        &amp;lt;Section&amp;gt;
          &amp;lt;h1 className="font-semibold font-serif text-2xl"&amp;gt;
            Hello, I'm Wawa Sensei
          &amp;lt;/h1&amp;gt;
          &amp;lt;p className="text-gray-500"&amp;gt;Welcome to my beautiful portfolio&amp;lt;/p&amp;gt;
          &amp;lt;p className="mt-3"&amp;gt;I know:&amp;lt;/p&amp;gt;
          &amp;lt;ul className="leading-9"&amp;gt;
            &amp;lt;li&amp;gt;🧑‍💻 How to code&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;🧑‍🏫 How to learn&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;📦 How to deliver&amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
          &amp;lt;p className="animate-bounce  mt-6"&amp;gt;↓&amp;lt;/p&amp;gt;
        &amp;lt;/Section&amp;gt;
        &amp;lt;Section right&amp;gt;
          &amp;lt;h1 className="font-semibold font-serif text-2xl"&amp;gt;
            Here are my skillsets 🔥
          &amp;lt;/h1&amp;gt;
          &amp;lt;p className="text-gray-500"&amp;gt;PS: I never test&amp;lt;/p&amp;gt;
          &amp;lt;p className="mt-3"&amp;gt;
            &amp;lt;b&amp;gt;Frontend 🚀&amp;lt;/b&amp;gt;
          &amp;lt;/p&amp;gt;
          &amp;lt;ul className="leading-9"&amp;gt;
            &amp;lt;li&amp;gt;ReactJS&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;React Native&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;VueJS&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;Tailwind&amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
          &amp;lt;p className="mt-3"&amp;gt;
            &amp;lt;b&amp;gt;Backend 🔬&amp;lt;/b&amp;gt;
          &amp;lt;/p&amp;gt;
          &amp;lt;ul className="leading-9"&amp;gt;
            &amp;lt;li&amp;gt;NodeJS&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;tRPC&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;NestJS&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;PostgreSQL&amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
          &amp;lt;p className="animate-bounce  mt-6"&amp;gt;↓&amp;lt;/p&amp;gt;
        &amp;lt;/Section&amp;gt;
        &amp;lt;Section&amp;gt;
          &amp;lt;h1 className="font-semibold font-serif text-2xl"&amp;gt;
            🤙 Call me maybe?
          &amp;lt;/h1&amp;gt;
          &amp;lt;p className="text-gray-500"&amp;gt;
            I'm very expensive but you won't regret it
          &amp;lt;/p&amp;gt;
          &amp;lt;p className="mt-6 p-3 bg-slate-200 rounded-lg"&amp;gt;
            📞 &amp;lt;a href="tel:(+42) 4242-4242-424242"&amp;gt;(+42) 4242-4242-424242&amp;lt;/a&amp;gt;
          &amp;lt;/p&amp;gt;
        &amp;lt;/Section&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/Scroll&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that our main div is wrapped inside a &lt;code&gt;Scroll&lt;/code&gt; component with the &lt;code&gt;html&lt;/code&gt; prop to be able to add &lt;code&gt;html&lt;/code&gt; inside our Canvas and have access to the scroll later.&lt;/p&gt;

&lt;p&gt;Now add the &lt;code&gt;Overlay&lt;/code&gt; component next to the &lt;code&gt;Office&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ScrollControls pages={3} damping={0.25}&amp;gt;       
    &amp;lt;Overlay /&amp;gt;
    &amp;lt;Office /&amp;gt;
&amp;lt;/ScrollControls&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface is ready and as each &lt;code&gt;Section&lt;/code&gt; height is &lt;code&gt;100vh&lt;/code&gt; the scroll is already good. But let's add some opacity animation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animating the UI on scroll
&lt;/h2&gt;

&lt;p&gt;We will change the opacity of our sections based on the scroll.&lt;/p&gt;

&lt;p&gt;To do so we store their opacity in a state&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const [opacityFirstSection, setOpacityFirstSection] = useState(1);
  const [opacitySecondSection, setOpacitySecondSection] = useState(1);
  const [opacityLastSection, setOpacityLastSection] = useState(1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;useFrame&lt;/code&gt; we animate them using the scroll hook methods available (more info &lt;a href="https://github.com/pmndrs/drei#scrollcontrols" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useFrame(() =&amp;gt; {
    setOpacityFirstSection(1 - scroll.range(0, 1 / 3));
    setOpacitySecondSection(scroll.curve(1 / 3, 1 / 3));
    setOpacityLastSection(scroll.range(2 / 3, 1 / 3));
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add the opacity as a prop to our sections&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Section opacity={opacityFirstSection}&amp;gt;
...
&amp;lt;Section right opacity={opacitySecondSection}&amp;gt;
...
&amp;lt;Section opacity={opacityLastSection}&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in our &lt;code&gt;Section&lt;/code&gt; component we adjust the opacity using this prop&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;section
      className={`h-screen flex flex-col justify-center p-10 ${
        props.right ? "items-end" : "items-start"
      }`}
      style={{
        opacity: props.opacity,
      }}
    &amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvisagr8smzvndss4b9bt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvisagr8smzvndss4b9bt.png" alt="Final render of the tutorial with the 3D office" width="800" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations you now have a great starting point to build your own portfolio with React Three Fiber and Tailwind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/p/github/wass08/r3f-scrolling-animation-tutorial/draft/icy-platform?file=%2Fsrc%2FApp.jsx&amp;amp;workspaceId=5cd54208-3423-4f91-a7e8-14ca07bdc9af" rel="noopener noreferrer"&gt;Live preview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code is available here:&lt;br&gt;
&lt;a href="https://github.com/wass08/r3f-scrolling-animation-tutorial" rel="noopener noreferrer"&gt;https://github.com/wass08/r3f-scrolling-animation-tutorial&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I highly recommend you to read &lt;a href="https://docs.pmnd.rs/react-three-fiber/getting-started/introduction" rel="noopener noreferrer"&gt;React Three Fiber documentation&lt;/a&gt; and check their &lt;a href="https://docs.pmnd.rs/react-three-fiber/getting-started/examples" rel="noopener noreferrer"&gt;examples&lt;/a&gt; to discover what you can achieve and how to do it.&lt;/p&gt;

&lt;p&gt;For more React Three Fiber tutorial you can check my &lt;a href="https://www.youtube.com/watch?v=LNvn66zJyKs&amp;amp;list=PLpepLKamtPjiUF6PvVUbIFhx9HaS0qJs_" rel="noopener noreferrer"&gt;Three.js/React Three Fiber&lt;/a&gt; playlist on YouTube.&lt;/p&gt;

&lt;p&gt;Thank you, don't hesitate to ask your questions in the comments section 🙏&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Full height sections perfectly centered with flex</title>
      <dc:creator>Wassim SAMAD</dc:creator>
      <pubDate>Sat, 11 Jan 2020 10:13:01 +0000</pubDate>
      <link>https://forem.com/wawasensei/full-height-sections-perfectly-centered-with-flex-3p4c</link>
      <guid>https://forem.com/wawasensei/full-height-sections-perfectly-centered-with-flex-3p4c</guid>
      <description>&lt;p&gt;Hello Dev community! This is my first post here and as this place is a great source of inspiration for me I wanted to take part of it. Let's go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Have you ever needed to display perfectly centered (vertically and horizontally) content on your sections whatever the content height is? There are multiple options to achieve this but my favorite one is to use &lt;strong&gt;flexbox&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A long time ago I would have done display table-cell and vertical-align middle, but come on... Web evolves (and so do I)!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Flex
&lt;/h2&gt;

&lt;p&gt;Display flex provides you easy ways to center the content both horizontally and vertically of an HTML element:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;align-items: center; // for vertical alignment&lt;br&gt;
justify-content: center; // for horizontal alignment&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To go deeper into flexbox go to &lt;a href="https://www.w3schools.com/css/css3_flexbox.asp" rel="noopener noreferrer"&gt;https://www.w3schools.com/css/css3_flexbox.asp&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Flex direction
&lt;/h2&gt;

&lt;p&gt;By default, the flex-direction of an element is set to &lt;strong&gt;row&lt;/strong&gt; which means its direct children will act as columns in a row displayed horizontally. You can decide to do the opposite by setting flex-direction to &lt;strong&gt;column&lt;/strong&gt; so its direct children will be displayed as the content of a column: one above the other.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that using flex-direction column align-items and justify-content will be inverted.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Full height sections
&lt;/h2&gt;

&lt;p&gt;To have full height sections I simply used height to &lt;strong&gt;100vh&lt;/strong&gt;. It means that the section will take 100% of the Viewport Height whatever the size of the screen is.&lt;/p&gt;
&lt;h2&gt;
  
  
  🔥 Bonus
&lt;/h2&gt;

&lt;p&gt;To give a better user experience when navigating to your full height sections website you can add this snapping scroll effect on the container of the sections:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;scroll-snap-type: y mandatory;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And add this property to tell your section to snap to the scroll of its container:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;scroll-snap-align: start;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Everything you need to know about scroll-snap &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  ✨Final result
&lt;/h2&gt;

&lt;p&gt;Adding a nice font and some color adjustments and we're ready to go!&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/wass08/embed/qBEYqOE?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Wish you liked it! Don't hesitate if you have any questions. Do you have any suggestions about a topic you would like me to cover, it would be a pleasure!&lt;/p&gt;

</description>
      <category>css</category>
      <category>design</category>
      <category>html</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
