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

PXL_20241030_150742708 Screenshot

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!