I’ve had an idea for my site for months, and last week I finally got round to making it! On my homepage, you can see a grid of smiley faces tracking the mouse cursor (or touch event) after the copy about my work creating a connection between you and your audience, to really hammer the message home 🙂
I’m a doodler, and have decided to use my sharpie sketches throughout my branding, so I knew the style I wanted. But to get the smiley faces to really look at the cursor, I knew I also needed them to be 3d, so I used Three.js. In case anyone wants to know how it’s done, I thought I’d detail some of the process here.
From sketch to Three.js
In the beginning I used a basic sphere shape, but once that was working, I did a smiley doodle, photographed it and put it into Illustrator where I traced it and made it vector. I then exported as an .svg and imported into Blender, where I gave it some weight and arranged it around a 3d sphere
I then exported it as a .glt file and imported it into my three.js scene
Getting Started with Three.js: The Basic Setup
First things first – setting up Three.js. If you’ve never used it before (like I hadn’t), here’s the basic scene setup I used:
const scene = new THREE.Scene();
scene.background = null; // I wanted a transparent background
const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 2000);
camera.position.z = 1000;
If you’re familiar with three.js this is pretty basic stuff, we’re just getting setup.
Making It Work Responsively
I decided that the grid needed to be different for each screen size, so I ended up creating this breakpoint system:
const CONFIG = {
breakpoints: {
mobile: {
maxWidth: 760,
rows: 5,
columns: 4,
spacing: 150,
modelScale: 15
},
tablet: {
maxWidth: 1230,
rows: 4,
columns: 6,
spacing: 200,
modelScale: 19
},
desktop: {
rows: 4,
columns: 8,
spacing: 200,
modelScale: 19
}
}
};
The variables here – rows, spacing, columns, I tied to a lil-gui, so I could adjust them to find the right values for each screen size
Making Things Interactive
I wanted the 3D models to react differently to mouse and touch:
function animate() {
if (pointer.hasTouched) {
// On touch, all models look at where you touched
// It's like they're all paying attention to your finger!
} else if (!pointer.isTouch && distance < horizontalThreshold) {
// With a mouse, only nearby models react
// Creates this cool effect where they follow your cursor, and look away when it's not close enough
}
}
Performance Optimisation (Because Nobody Likes a Laggy Website)
Model Caching – only load the model once and clone it:
let cachedModel = null;
async function loadGLTFModel() {
if (cachedModel) {
return cachedModel.clone();
}
// First time? Load it up!
}
Only render when visible – we don’t need the faces to follow you when you’ve scrolled past them:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
isVisible = entry.isIntersecting;
if (isVisible) {
animate();
} else {
cancelAnimationFrame(animationFrameId);
}
});
});
Some other key points
For the 2D/3D effect to really work, I decided to make the outline of the circle stay static and just have the eyes and mouth track the cursor.
I also employed GSAP, which I was using on the rest of the page anyway, to animate the faces in using a stagger effect. This I attached to a scrollTrigger, so they animate in when the user scrolls to that point.
What I Learned
Three.js is incredibly powerful, there’s so much you can do with it, and personally I love the 2D/3D effect. It also requires thinking about things differently than traditional web development. Some key takeaways:
- Responsive design in 3D is important to consider in the design and development phases
- Performance optimisation is crucial – 3D can get heavy fast
- User interaction needs to work well for both mouse and touch, and is usually worth considering separately as the results are very different
The end result was exactly what I wanted – an interactive 3D grid that’s both fun to play with and performs well. If you’re thinking about diving into Three.js, I’d say go for it – there are a tonne of resources available – but be prepared for some trial and error.
If you’ve got any questions or any suggestions on improving the block, I’d love to hear them!