← All articles
Mathijs Hanenkamp: Photography Portfolio Case Study
A photography portfolio website case study — how the Mathijs Hanenkamp site fused WordPress, GSAP & WebGL into an award-winning, image-led experience (Awwwards Honorable Mention).

When the entire product is a photographer's eye, the website has one job: get out of the way and make the work feel inevitable. This Mathijs Hanenkamp case study breaks down how I built a photography portfolio website that does exactly that — an image-led experience woven from WordPress, GSAP and WebGL that earned an Awwwards Honorable Mention. Mathijs is a portrait and documentary photographer based in Nijmegen, the Netherlands, and the brief was deceptively simple: build something that elevates the photography without ever competing with it. Here's the craft behind it, written by the developer who shipped it.
Who built it — and why the developer credit matters
The Mathijs Hanenkamp site was a collaboration between designer Huy Phan — an Awwwards jury member — and Hon Tran as the creative developer. That's me.
I lead with that for a concrete reason. A photography portfolio is the hardest kind of site to add motion to, because every effect risks doing harm: a heavy transition that softens an image, a scroll animation that distracts from a composition, a clever hover that pulls the eye away from the face in frame. The win condition isn't "more animation" — it's motion that makes the photographs feel more present. I've spent 11+ years building award-winning web experiences for clients across Norway, Denmark, Sweden, Malta, Germany and Vietnam; I've been named Awwwards "Independent of the Year" twice and I sit on the Awwwards jury. On a portfolio like this, that experience is the line between motion that serves the work and motion that buries it.
The brief: the photography leads, the site elevates
Portrait and documentary work lives or dies on presence — the moment, the expression, the light.
The mandate from the design was a restrained, near-white canvas (the palette is essentially pure
#ffffff) where the images are the only color in the room. Three constraints shaped every
engineering decision:
- Never degrade the image. Crispness was non-negotiable. No effect could soften, compress, or crop a photograph in a way the photographer wouldn't sign off on.
- Motion as choreography, not decoration. Loading transitions, gallery reveals, and menu hover states all had to feel intentional — a single directed sequence, not a pile of one-off tricks.
- Fast under heavy imagery. A photography site is, by definition, a megabyte-heavy site. It still had to load quickly and scroll at 60fps on a mid-range laptop and a phone.
The craft: building award-level motion on a WordPress stack
Here's the part most people get wrong about this project when they see it: it runs on WordPress. There's a persistent myth that award-level motion requires a fully custom JavaScript framework and that a CMS like WordPress caps you at template-grade output. It doesn't. The trick is treating WordPress as a content layer and building the experience as a proper front-end motion system on top of it — GSAP for the timeline choreography and Three.js / WebGL for the image rendering. The result is a site the photographer can update himself that still competes on Awwwards.
Image-led page and loading transitions
The first impression is a loading-to-content transition, and on a portfolio it has to do double duty: cover the asset load and introduce the first image as a hero moment. The principle is to treat the whole sequence as one GSAP timeline — the loader, the cover wipe, and the first image reveal are choreographed together so the eye never hits a dead frame.
import gsap from 'gsap'
function introTimeline(onReady: () => void) {
const tl = gsap.timeline({ defaults: { ease: 'expo.inOut', duration: 1 } })
tl.to('[data-loader-bar]', { scaleX: 1, transformOrigin: 'left' })
.add(onReady) // content mounted behind the cover
.to('[data-loader]', { yPercent: -100 }, '+=0.05') // lift the cover
.from('[data-hero-image]', { scale: 1.08, autoAlpha: 0 }, '<0.15') // settle the first photo
.from('[data-hero] [data-stagger]', { yPercent: 110, stagger: 0.05 }, '<0.2')
return tl
}
The position parameters ('<0.15', '<0.2') overlap the hero image reveal with the cover lift,
so the first photograph is already settling into place before the loader clears. That overlap is the
whole difference between "a page loaded" and "an experience began."
WebGL that elevates the image instead of crushing it
Three.js handles the heavier image treatments — including the fluid, distortion-driven transitions between gallery shots. The hard rule from the brief (never degrade the image) dictates a specific approach: load textures at full resolution and do the shader work around the photo — a fluid displacement on transition, a subtle settle — rather than permanently altering the source pixels. On a WebGL plane that means respecting device pixel ratio, color space, and anisotropy so nothing softens at angles:
import * as THREE from 'three'
const loader = new THREE.TextureLoader()
loader.load('/uploads/portrait.jpg', (texture) => {
texture.colorSpace = THREE.SRGBColorSpace
texture.anisotropy = renderer.capabilities.getMaxAnisotropy() // crisp at any angle
texture.minFilter = THREE.LinearMipmapLinearFilter
texture.generateMipmaps = true
})
// Cap DPR: above 2 you pay a huge fill-rate cost for a difference almost nobody can see.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
The fluid transition itself is a fragment shader that mixes between the outgoing and incoming texture using a displacement value driven by progress — the image appears to flow into the next rather than hard-cut. If you want the mechanics behind this class of effect, I wrote a full guide on the WebGL image displacement hover effect that uses the same GLSL technique.
Immersive scroll and the menu microinteractions
Away from the WebGL planes, the rest of the site runs on GSAP and ScrollTrigger: gallery items rise
and settle as they enter the viewport, and the menu hover states — which the Awwwards community
called out specifically — animate on transform and opacity only so they stay buttery. The rule
that keeps a motion-dense, image-heavy page fast is boring but absolute:
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
gsap.utils.toArray<HTMLElement>('[data-photo]').forEach((el) => {
gsap.from(el, {
yPercent: 12,
autoAlpha: 0,
ease: 'expo.out',
duration: 1,
scrollTrigger: { trigger: el, start: 'top 88%', once: true },
})
})
Staying fast under a megabyte-heavy gallery
A photography site is the worst-case scenario for performance: large images everywhere. The discipline that kept it quick:
- Animate only
transformandopacity. They're GPU-composited and skip layout/paint, so the main thread stays free for scrolling through dozens of photos. - Lazy-init the WebGL. The Three.js scene spins up only when a gallery section nears the viewport, and pauses its render loop when scrolled off-screen — no GPU cycles burned on what you can't see.
- Responsive, mip-mapped textures. Serve appropriately sized images per breakpoint and let mipmaps handle the downscale, so a portrait never ships a 4000px file to a phone.
- Respect the visitor.
gsap.matchMedia()strips the heavy motion underprefers-reduced-motion, keeping the site accessible without forking the code.
The result: an Awwwards Honorable Mention
The site earned an Awwwards Honorable Mention, with a community rating averaging around 8.5, and was tagged for its interaction and microinteraction design. You can see it live at mathijshanenkamp.com and the award listing on Awwwards.
What that recognition signals for a buyer is the point of this whole case study: the jury rewarded a site where the photography is unmistakably the star, and the motion is what makes it feel cinematic. For a portrait and documentary photographer, that's the brief executed exactly — the work leads, the experience elevates.
What this case study is really about
The generalisable lesson: award-level motion is a front-end engineering decision, not a stack decision. WordPress didn't hold this site back — a disciplined GSAP + WebGL layer on top of it delivered an experience that competes with fully custom builds, while leaving the photographer able to manage his own galleries. The same playbook — content-managed CMS, a real motion system, and ruthless performance discipline — is what carries any image-heavy brand or portfolio site. You can see it applied differently in the Mat Voyce kinetic typography case study and the Iventions award-winning events site, and if you're building your own, the guide on how to build an award-winning portfolio site walks through the fundamentals.
FAQ
What is the Mathijs Hanenkamp website built with?
WordPress as the content layer, with GSAP driving the motion system and Three.js / WebGL handling the image transitions and fluid rendering — a stack chosen so the photographer can manage his own galleries while the front end still delivers award-level motion.
Can you build an Awwwards-level site on WordPress?
Yes. WordPress is a content layer; the experience is built as a custom front-end motion system (GSAP + WebGL) on top of it. This site is the proof — it earned an Awwwards Honorable Mention while running on WordPress.
Who designed and developed the Mathijs Hanenkamp site?
It was a collaboration between designer Huy Phan (an Awwwards jury member) and Hon Tran as the creative developer responsible for the front-end, GSAP motion and WebGL engineering.
How do you add motion to a photography site without hurting the images?
Load textures at full resolution and do shader work around the photo rather than altering the
source, cap device pixel ratio at 2, use anisotropy and mipmaps for crispness, animate only
transform/opacity, and lazy-init WebGL so heavy galleries stay fast.
Let's build something award-worthy
If you're a photographer, brand, or agency that needs an image-led portfolio or WebGL photography site that elevates the work instead of competing with it — that's exactly what I do as a creative developer.
- See how I work and start a project on the hire page.
- Browse more shipped, awarded work in the projects archive.
- Explore engagement tiers on the services page.
- Ready to talk? Let's talk →
Written by Hon Tran — creative developer, founder of hontran.dev, and Awwwards jury member. 11+ years building award-winning, performance-first web experiences (GSAP, Three.js / WebGL, Next.js, WordPress) for clients worldwide. The first Vietnamese developer to win an international web award. hontran.dev · Behance.