How to Put an Interactive 3D Logo on Your Website
By the Charmotion team · Updated

To put an interactive 3D logo on a website, export the logo as a compact glTF (GLB) model, load it with three.js into a transparent canvas, light it with an environment map and turn pointer position into a small rotation. Show a still poster image first, pause rendering off screen and keep the still for reduced motion.
Key takeaways
- glTF (a .glb file) is the format the three.js manual recommends; curves must become meshes before export, and Blender's area lights and World lighting are not exported.
- Light the logo in code with an environment map, or metal and glossy materials look flat and dark.
- Map the pointer to a few degrees of rotation and ease toward it, using the frame timestamp so the feel is the same at 60 Hz and 120 Hz.
- Render only while something moves, pause when the canvas is off screen, and limit the pixel ratio on high-density phones.
- Ship a poster image as the first paint and as the fallback for no WebGL and for reduced motion.
- Use a video when the logo only needs to play, and Spline when a designer should own the scene without code.
Already have a character or logo? See examples in real app screens: splash, onboarding, loading and empty-state screens, with the animation that plays in each.
What do you need to put an interactive 3D logo on a website?
Five pieces. Most broken 3D logos are missing the last two.
| Piece | What it is | Typical choice |
|---|---|---|
| Model | The logo as a 3D mesh with materials | A .glb file exported from Blender |
| Renderer | Code that draws the model into a canvas | three.js (WebGL) |
| Lighting | What the materials reflect | An environment map plus one or two lights |
| Interaction | How the logo answers the pointer or the scroll | A small eased rotation |
| Fallbacks | What people see before load, without WebGL or with reduced motion | A still poster image of the same logo |
The work splits cleanly: modeling happens once in a 3D tool, and the page only loads a finished file. If you have not built the model yet, turning a flat logo into 3D covers extruding and beveling in Blender.
How do you turn a logo into a GLB model for the web?
Start from the vector logo, give it depth in a 3D tool, then export glTF. The three.js manual says: "Where possible, we recommend using glTF (GL Transmission Format). Both .GLB and .GLTF versions of the format are well supported." Khronos, which maintains the format, notes that the JSON core "can be stored in a single binary glTF file (.glb)", which is the easiest form to host.
Blender's glTF exporter has a few rules that matter for logos:
- Convert curves to meshes. An SVG imported into Blender arrives as curves, and the exporter says "curves and other non-mesh data are not preserved, and must be converted to meshes prior to export."
- Use the Principled BSDF. The exporter reads base color, metallic and roughness from it. A nonzero Coat adds the
KHR_materials_clearcoatextension and Transmission addsKHR_materials_transmission. - Be careful with glass. Blender's docs warn that transmission "is complex for real-time rendering engines to implement" and that support for the extension "is not yet widespread." three.js's GLTFLoader does list it as supported, but test it on a phone.
- Lights do not carry over. "Blender's Area light type and World lighting are not supported by glTF, and will be ignored during export." Plan to light the logo in three.js.
To keep the file small, three.js supports Draco mesh compression and KTX2 textures through the loader. The DRACOLoader docs say compressed geometry "can be significantly smaller, at the cost of additional decoding time on the client device," so it pays off for detailed meshes more than for a simple extruded mark.
How do you load and light a 3D logo with three.js?
Create a renderer with a transparent background, load the GLB and give the scene an environment map. three.js's RoomEnvironment produces image-based lighting for PBR materials without shipping an HDR file.
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
const canvas = document.querySelector("#logo3d");
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(30, 1, 0.1, 100);
camera.position.set(0, 0, 6);
const pmrem = new THREE.PMREMGenerator(renderer);
scene.environment = pmrem.fromScene(new RoomEnvironment()).texture;
const gltf = await new GLTFLoader().loadAsync("/logo.glb");
const logo = gltf.scene;
scene.add(logo);
Keep alpha: true so the page background shows through. MDN's WebGL best practices explain that alpha: false makes the browser composite the canvas "as though it were opaque."
If nothing appears, the three.js manual's checklist applies: log errors from the loader, "try scaling the model up or down by a factor of 1000", and "try to add and position a light source. The model may be hidden in the dark."
How do you make a 3D logo react to the pointer and scroll?
Listen for pointermove, store a target angle, and move the logo part of the way toward it on every frame. Pointer events cover "a mouse, pen/stylus or touch" with one set of handlers, according to MDN.
let targetX = 0, targetY = 0, last = 0;
window.addEventListener("pointermove", (e) => {
targetY = ((e.clientX / innerWidth) * 2 - 1) * 0.35; // radians, left/right
targetX = ((e.clientY / innerHeight) * 2 - 1) * 0.2; // radians, up/down
});
function frame(t) {
const dt = last ? t - last : 16;
last = t;
const k = 1 - Math.exp(-dt / 120); // same easing at 60 Hz and 120 Hz
logo.rotation.y += (targetY - logo.rotation.y) * k;
logo.rotation.x += (targetX - logo.rotation.x) * k;
renderer.render(scene, camera);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
The timestamp matters. MDN warns that if you do not use it to measure progress, "the animation will run faster on high refresh-rate screens."
Scroll: read the scroll position inside the same frame loop and map it to a gentle turn or lift while the logo is in view. WCAG's Animation from Interactions criterion names parallax and motion triggered by scrolling as typical examples of motion that users must be able to turn off, so scroll effects belong behind the reduced-motion check below.
Touch: phones have no resting pointer. MDN notes that on devices without hover, enter and leave events arrive with the press and release, so the logo only reacts while a finger is down. Give touch users a slow idle motion or a reaction on tap instead, and do not set touch-action: none on a large canvas, or the page stops scrolling under it.
What performance budget should a 3D logo have?
There is no universal number. web.dev defines a performance budget as "a set of limits imposed on metrics that affect site performance": set one for the GLB size, the JavaScript added for three.js, and the time until your hero is visible, then measure against it. The levers:
| Lever | Why | Source |
|---|---|---|
| Render on demand | Rendering continuously when nothing changes "is a waste of the devices power" and of battery on portable devices | three.js manual |
| Pause off screen | The Intersection Observer API is meant for "deciding whether or not to perform tasks or animation processes" based on visibility | MDN |
| Limit pixel ratio | A 3x display means "9x the rendering"; the manual suggests capping the internal resolution | three.js manual |
| Compress the model | Draco for geometry, KTX2 for textures, both supported by GLTFLoader | three.js docs |
| Handle context loss | webglcontextlost "can be emitted when the GPU runs out of memory" | MDN |
For a logo, rendering on demand means stopping the frame loop once the logo has eased to rest and starting it again on the next pointer move. Browsers already pause requestAnimationFrame "in background tabs or hidden <iframe>s," so you only need to handle the scrolled-away case yourself.
Loading matters too. web.dev's list of elements that count for Largest Contentful Paint covers images, video posters, background images and text, and does not mention <canvas>. In practice your poster image or headline is what gets measured, so make the poster fast and load three.js after it.
What should visitors see without WebGL or with reduced motion?
The same logo, still. Render one frame of the model in its resting pose to a WebP or PNG, show it as a normal <img> with alt text, and swap in the canvas only after the model has loaded.
- No WebGL. The three.js manual shows
WebGL.isWebGL2Available()for checking support "before attempting to render anything." If it returns false, keep the poster. - Screen readers. MDN notes that canvas content "is not exposed to accessibility tools," so the brand name must live in the image's alt text or nearby text, not in the canvas.
- Reduced motion.
prefers-reduced-motiontells you the user "has enabled the setting on their device for reduced motion." W3C lists this media query as a sufficient technique (C39) for WCAG 2.3.3.
const still = matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!still && WebGL.isWebGL2Available()) {
startLogo(); // load three.js and the GLB, then hide the poster
}
More patterns for motion settings are in accessible animation and reduced motion.
When are Spline or a video a better choice than three.js?
| three.js | Spline | Video | |
|---|---|---|---|
| Who builds it | A developer | A designer in Spline's editor | Anyone with a 3D or video tool |
| Reacts to the pointer | Yes, any way you code it | Yes, through events set up in the editor | No |
| Embed | Your own code and model file | A <spline-viewer> web component, a public URL or code export | A <video> tag |
| Best for | Custom behavior, tight file budgets | Fast iteration without code | A logo that only plays, with complex light and glass |
Spline's docs (as of November 2026) say the Viewer lazy-loads by default, loading the canvas "only when the canvas is visible on the screen," can track mouse events globally or only inside its container, and can show a hint image when nobody interacts. Code export covers Vanilla JS, three.js, React, Next.js and react-three-fiber, but "animations and events are only enabled when exporting to Vanilla js and React." Spline also "draws on WebGPU by default and falls back to WebGL automatically." The trade-offs are compared in Rive vs Spline.
Choose video when nothing needs to react. A rendered video can show refraction and soft shadows that are expensive in real time. For a transparent background you need two files: VP9 WebM for Chrome and Firefox and HEVC in a MOV for Safari, with the HEVC source listed first so Safari picks it (Jake Archibald's write-up explains why). See transparent video on a website.
Can you get an interactive 3D logo without building it yourself?
Yes. Charmotion's Motion Studio builds a website hero from your own logo file: it finds the logo's parts and moves them, so nothing is redrawn. The hero follows the pointer, reacts on hover and loops while idle. You get a hosted embed that pauses off screen and respects reduced motion, self-host files, a React component, and MP4 plus transparent WebM.
The three concepts are free on every plan. Building one starts at Starter ($9/month as of November 2026, 3 logo animations a month), which builds with Rive and interactive three.js. A rendered cinematic 3D video is on All in one ($100/month). If you want full control over the code, the steps above are all you need.
How to add an interactive 3D logo to a website
- Model the logo. Extrude and bevel the vector logo in a 3D tool, convert curves to meshes and use Principled BSDF materials.
- Export a GLB. Export glTF as a single .glb file, compressing geometry with Draco only if the mesh is detailed.
- Load and light it. Load the GLB with three.js GLTFLoader into a transparent canvas and set scene.environment from RoomEnvironment.
- Add pointer reaction. Map pointermove to a small target rotation and ease toward it each frame using the frame timestamp.
- Save power. Stop rendering when the logo is at rest or off screen, and limit the pixel ratio on high-density displays.
- Add fallbacks. Show a still poster image first, and keep it for browsers without WebGL and for prefers-reduced-motion.
Frequently asked questions
Should a 3D logo be a GLB or a glTF file?
Either works: the three.js manual says both are well supported. A .glb packs the JSON, geometry and textures into one binary file, so it is one request and harder to break when moving files around. A .gltf with separate files is easier to inspect by hand.
Does a 3D logo hurt Largest Contentful Paint?
Usually not directly. web.dev's list of LCP elements covers images, video posters, background images and text, and does not mention canvas. Your poster image or headline is what gets measured, so keep the poster small and load three.js and the model after it.
Will an interactive 3D logo work on phones?
Yes, with two changes. Touch screens have no hover, so the logo only gets pointer input while a finger is down; add an idle motion or a tap reaction. High-density screens multiply the pixels to render, so limit the canvas resolution to keep the frame rate up.
Sources
- Loading 3D models, three.js manual
- GLTFLoader, three.js docs
- DRACOLoader, three.js docs
- RoomEnvironment, three.js docs
- Rendering on demand, three.js manual
- Responsive design (HD-DPI displays), three.js manual
- WebGL compatibility check, three.js manual
- glTF overview, Khronos Group
- glTF 2.0 import and export, Blender Manual
- WebGL best practices, MDN Web Docs
- Pointer events, MDN Web Docs
- Window: requestAnimationFrame() method, MDN Web Docs
- Intersection Observer API, MDN Web Docs
- <canvas>: the Graphics Canvas element, MDN Web Docs
- Largest Contentful Paint (LCP), web.dev
- Performance budgets 101, web.dev
- prefers-reduced-motion, MDN Web Docs
- Understanding SC 2.3.3: Animation from Interactions, W3C WAI
- C39: Using the CSS prefers-reduced-motion query to prevent motion, W3C WAI
- Exporting as Spline Viewer, Spline docs
- Exporting as code, Spline docs
- Video with alpha transparency on the web, Jake Archibald