Positive Vibes to your Inbox

An occasional newsletter to brighten your inbox, bringing you the best thoughts and links from the studio


Creating an Interactive 3D Grid of Smileys with Three.js (And Why It Was Actually Fun)

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!