Free course recommendation: Master JavaScript animation with GSAP through 34 free video lessons, step-by-step projects, and hands-on demos. Enroll now →
I’ve always been interested in finding simple ways to bring more depth into web interfaces, not just through visuals, but through interaction and space.
In this demo, I explored how flat UI cards can become interactive 3D scenes using GLB models, Three.js, and Webflow. Each card starts as a basic layout but reveals a small, self-contained environment built with real-time rendering and subtle motion.
It’s a lightweight approach to adding spatial storytelling to familiar components, using tools many designers already work with.
Welcome to My Creative World
I’m always drawn to visuals that mix the futuristic with the familiar — space-inspired forms, minimal layouts, and everyday elements seen from a different angle.
Most of my projects start this way: by reimagining ordinary ideas through a more immersive or atmospheric lens.
It All Started with a Moodboard
This one began with a simple inspiration board:

From that board, I picked a few of my favorite visuals and ran them through an AI tool that converts images into GLB 3D models.
The results were surprisingly good! Abstract, textured, and full of character.

The Concept: Flat to Deep
When I saw the output from the AI-generated GLB models, I started thinking about how we perceive depth in UI design, not just visually, but interactively.
That led to a simple idea: what if flat cards could reveal a hidden spatial layer? Not through animation alone, but through actual 3D geometry, lighting, and camera movement.
I designed three UI cards, each styled with minimal HTML and CSS in Webflow. On interaction, they load a unique GLB model into a Three.js scene directly within the card container. Each model is lit, framed, and animated to create the feeling of a self-contained 3D space.

Building the Web Experience
The layout was built in Webflow using a simple flexbox structure with three cards inside a wrapper. Each card contains a div
that serves as the mounting point for a 3D object.
The GLB models are rendered using Three.js, which is integrated into the project with custom JavaScript. Each scene is initialized and handled separately, giving each card its own interactive 3D space while keeping the layout lightweight and modular.

Scene Design with Blender
Each GLB model was prepared in Blender, where I added a surrounding sphere to create a sense of depth and atmosphere. This simple shape helps simulate background contrast and encloses the object in a self-contained space.
Lighting played an important role; especially with reflective materials like glass or metal. Highlights and soft shadows were used to create that subtle, futuristic glow.
The result is that each 3D model feels like it lives inside its own ambient environment, even when rendered in a small card.

Bringing It Together with Three.js
Once the models were exported from Blender as .glb
files, I used Three.js to render them inside each card. Each card container acts as its own 3D scene, initialized through a custom JavaScript function.
The setup involves creating a basic scene with a perspective camera, ambient and directional lighting, and a WebGL renderer. I used GLTFLoader
to load each .glb
file and OrbitControls
to enable subtle rotation. Zooming and panning are disabled to keep the interaction focused and controlled.
Each model is loaded into a separate container, making it modular and easy to manage. The camera is offset slightly for a more dynamic starting view, and the background is kept dark to help the lighting pop.
Here’s the full JavaScript used to load and render the models:
// Import required libraries
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import gsap from 'gsap';
/**
* This function initializes a Three.js scene inside a given container
* and loads a .glb model into it.
*/
function createScene(containerSelector, glbPath) {
const container = document.querySelector(containerSelector);
// 1. Create a scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020); // dark background
// 2. Set up the camera with perspective
const camera = new THREE.PerspectiveCamera(
45, // Field of view
container.clientWidth / container.clientHeight, // Aspect ratio
0.1, // Near clipping plane
100 // Far clipping plane
);
camera.position.set(2, 0, 0); // Offset to the side for better viewing
// 3. Create a renderer and append it to the container
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);
// 4. Add lighting
const light = new THREE.DirectionalLight(0xffffff, 4);
light.position.set(30, -10, 20);
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x404040); // soft light
scene.add(ambientLight);
// 5. Set up OrbitControls to allow rotation
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableZoom = false; // no zooming
controls.enablePan = false; // no dragging
controls.minPolarAngle = Math.PI / 2; // lock vertical angle
controls.maxPolarAngle = Math.PI / 2;
controls.enableDamping = true; // smooth movement
// 6. Load the GLB model
const loader = new GLTFLoader();
loader.load(
glbPath,
(gltf) => {
scene.add(gltf.scene); // Add model to the scene
},
(xhr) => {
console.log(`${containerSelector}: ${(xhr.loaded / xhr.total) * 100}% loaded`);
},
(error) => {
console.error(`Error loading ${glbPath}`, error);
}
);
// 7. Make it responsive
window.addEventListener("resize", () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
// 8. Animate the scene
function animate() {
requestAnimationFrame(animate);
controls.update(); // updates rotation smoothly
renderer.render(scene, camera);
}
animate(); // start the animation loop
}
// 9. Initialize scenes for each card (replace with your URLs)
createScene(".div", "https://yourdomain.com/models/yourmodel.glb");
createScene(".div2", "https://yourdomain.com/models/yourmodel2.glb");
createScene(".div3", "https://yourdomain.com/models/yourmodel3.glb");
This script is added via a <script type="module">
tag, either in the Webflow page settings or as an embedded code block. Each call to createScene()
initializes a new card, linking it to its corresponding .glb
file.
How This Works in Practice
In Webflow, create three containers with the classes .div
, .div2
, and .div3
. Each one will act as a canvas for a different 3D scene.
Embed the JavaScript module shown above by placing it just before the closing </body>
tag in your Webflow project, or by using an Embed block with <script type="module">
.
Once the page loads, each container initializes its own Three.js scene and loads the corresponding GLB model. The result: flat UI cards become interactive, scrollable 3D objects — all directly inside Webflow.
This approach is lightweight, clean, and performance-conscious, while still giving you the flexibility to work with real 3D content.
Important Note for Webflow Users
This setup works in Webflow, but only if you structure it correctly.
To make it work, you’ll need to:
- Host your Three.js code externally using a bundler like Vite, Parcel, or Webpack
- Or bundle the JavaScript manually and embed it as a
<script type="module">
in your exported site
Keep in mind: Webflow’s Designer does not support ES module imports (import
) directly. Pasting the code into an Embed block won’t work unless it’s already built and hosted elsewhere.
You’ll need to export your Webflow project or host the script externally, then link it via your project settings.
Final Thoughts
Thanks for following along with this project. What started as a simple moodboard turned into a small experiment in mixing UI design with real-time 3D.
Taking flat cards and turning them into interactive scenes was a fun way to explore how much depth you can add with just a few tools: Webflow, Three.js, and GLB models.
If this gave you an idea or made you want to try something similar, that’s what matters most.
Keep experimenting, keep learning, and keep building.