parallax how-to web-design
A 3D image effect on mouse movement, without writing the code
The image that tilts and shifts as the cursor moves: how the effect works, the code route, and the no-code route with one embed.
Aman Jha·September 25, 2026·5 min read
A 3D image effect on mouse movement makes a picture shift as the cursor moves over it. There are two versions. A tilt rotates the whole image like a card, with a few lines of CSS and JavaScript. Real depth parallax moves the subject more than the background, which needs a depth map and WebGL, or one iframe from a tool that builds it for you.
Two effects share this name
Search for this and you'll get a mix of both, usually without anyone saying which is which.
The tilt treats the image as a flat card. Move right, the card turns right. Every pixel moves together, so it looks like a photo on a stand, not a scene. It's great on product cards, buttons and pricing tiles.
Depth parallax treats the image as a surface. Move right and the nearest parts slide further than the far ones, the way a real window view shifts as you lean. That's the effect people usually mean when they say a photo “looks 3D”, and it's the one that's hard to build.
The CSS tilt, in about 20 lines
A container with perspective, and the image inside it:
<div class="tilt">
<img src="photo.jpg" alt="Describe the photo">
</div>.tilt { perspective: 800px; }
.tilt img {
display: block;
width: 100%;
transition: transform 0.15s ease-out;
}Then map the pointer to two rotations:
const box = document.querySelector(".tilt");
const img = box.querySelector("img");
box.addEventListener("pointermove", (e) => {
const r = box.getBoundingClientRect();
const x = (e.clientX - r.left) / r.width - 0.5; // -0.5 … 0.5
const y = (e.clientY - r.top) / r.height - 0.5;
img.style.transform =
`rotateY(${x * 12}deg) rotateX(${-y * 12}deg)`;
});
box.addEventListener("pointerleave", () => {
img.style.transform = "";
});Twelve degrees is plenty. Past about 15 the image starts to look like it's falling over. The short transition takes the jitter out of fast mouse movement, and resetting on pointerleave stops the card from staying stuck at an angle.
Real depth: move pixels by a depth map
For parallax inside one photo, every point needs to know how far away it is. That's a depth map: a grayscale image where bright is near and dark is far. You can make one from any photo with the free depth map generator.
Put the photo on a finely subdivided plane in Three.js, pass the depth map in as a texture, and move each vertex in the vertex shader. This is a trimmed copy of the shader Gifsy's viewer runs:
uniform sampler2D depthMap; // white = near, black = far
uniform vec2 uPointer; // eased cursor position, -1 … 1
uniform float uParallax; // how far this plane moves
uniform float uDepthBias; // the depth that stays put
varying vec2 vUv;
void main() {
vUv = uv;
float d = texture2D(depthMap, uv).r;
vec3 pos = position;
pos.xy += uPointer * uParallax * (d - uDepthBias);
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}The key line is the last pos.xy one. Points nearer than uDepthBias move with the cursor, points further away move against it, and the pivot depth stays still. Then smooth the cursor, or the scene twitches with every mouse jitter:
// every animation frame
cur.x += (target.x - cur.x) * 0.08;
cur.y += (target.y - cur.y) * 0.08;
material.uniforms.uPointer.value.set(cur.x, cur.y);That gets you a working effect in an afternoon. Making it look good takes longer, because of three problems:
- The gap behind the subject. One surface stretches at the subject's edge instead of revealing what's behind it. The fix is two planes: the subject cut out on its own, in front of a background with the subject painted out.
- Torn borders. Near the frame edge there's no image to move into, so the border curls. Pin the outer ring of vertices flat and crop in slightly.
- Soft depth edges. Depth models blur boundaries, so the background near the subject gets dragged along. Sharpening the map against the photo's own edges helps; the depth-in-the-browser write-up shows the guided filter we use.
The no-code route: one iframe
If you'd rather not maintain a WebGL scene, Gifsy builds all of the above from one photo: depth map, subject cut-out, filled background, two planes and the eased pointer. You upload the photo at the 3D parallax photo maker, pick a pointer-driven preset such as Mouse Tilt or Gentle 3D, publish, and paste the iframe it gives you. The scene follows the mouse on desktop and the finger on touch screens; the Orbit 3D preset spins on a drag instead.
3D needs a free account and is free and unlimited. The embed works in Webflow, Framer, Squarespace, WordPress or plain HTML; the menu path for each is in how to embed a 3D photo on any website.
Accessibility and performance
- Respect reduced motion. When someone has set
prefers-reduced-motion: reduce, Gifsy's viewer drops every movement it would start by itself (the idle drift, the slow zoom) and keeps only the motion the visitor causes by moving or dragging. Do the same in your own code. - Don't hide anything behind the effect. Text and buttons should work with the image perfectly still.
- Plan for touch. There's no hover on a phone. Either make it draggable or let it sit still.
- Lazy-load below the fold. A CSS tilt is almost free. A WebGL scene isn't, so give its iframe
loading="lazy"unless it's the hero.
Which one to use
Cards, buttons, anything that should feel clickable: the CSS tilt. A photo that should feel like a window into a place, especially in a hero: depth parallax. And if nobody on the team wants to own a shader, the iframe. For hero layouts that work, see the interactive 3D hero section guide.
FAQ
How do I make an image move with the mouse in 3D?
For a whole-image tilt, listen to pointermove and set rotateX and rotateY on the image inside a container with perspective. For real depth, where the subject moves more than the background, you need a depth map and a WebGL plane that shifts each point by its depth, or a tool like Gifsy that builds that for you from one photo.
What's the difference between a tilt effect and a parallax effect?
A tilt rotates the whole picture as one flat card. Parallax moves different parts of the picture by different amounts depending on how far away they are, which is what makes a photo look like it has depth.
Can I do it without code?
Yes. Upload a photo to Gifsy's 3D photo maker, publish it, and paste the iframe it gives you into your page. 3D needs a free account and is free and unlimited.
Does it work on phones, where there's no mouse?
Hover effects don't fire on touch screens. Gifsy's embed follows the finger instead, and in the Orbit preset a visitor drags to spin it; a CSS tilt needs its own touch handling or it simply stays still.
Is a mouse-move effect bad for accessibility?
Not if it respects prefers-reduced-motion and nothing important depends on it. Gifsy's viewer turns off all motion it starts by itself under that setting, and keeps only the movement the visitor causes.
Will it slow my page down?
A CSS tilt costs almost nothing. A WebGL scene costs a renderer and a few images; in an iframe it runs in its own document, so add loading="lazy" when it sits below the fold.
Make a 3D photo that follows the mouse. Related: the 2.5D parallax effect and how 3D photos work.