Spatial Photo

When I saw @cristicrtu's tweet about the visionOS blur effect, I knew I had to try it out. I decided to take the idea further by adding a 3D parallax effect to create a spatial photo.

Antelope Canyon

November 13, 2016

How it works

The scene consists of three layers: the environment, the window, and the image.

Scene layers: environment, window, and image, visualized in Spline.

The main idea is to create a parallax effect by moving the layers relative to the mouse movement. We can also rotate the layers on the Y-axis to create a 3D effect.

1const applyParallax = ( 2 ref: React.RefObject<HTMLElement>, 3 mouseX: number, 4 mouseY: number, 5 xPosScale: number, 6 yPosScale: number, 7 rotateYScale: number, 8 scale = 1, 9 clampPosX: [number, number] | undefined = undefined, 10 clampPosY: [number, number] | undefined = undefined, 11) => { 12 if (ref.current) { 13 const rect = ref.current.getBoundingClientRect(); 14 15 const xPos = (mouseX - rect.left) / rect.width - 0.5; 16 const yPos = (mouseY - rect.top) / rect.height - 0.5; 17 18 const positionX = clampPosX 19 ? clamp(clampPosX[0], clampPosX[1], xPos * xPosScale) 20 : xPos * xPosScale; 21 22 const positionY = clampPosY 23 ? clamp(clampPosY[0], clampPosY[1], yPos * yPosScale) 24 : yPos * yPosScale; 25 26 const rotateY = clamp(MIN_ROTATE_Y, MAX_ROTATE_Y, xPos * rotateYScale); 27 28 ref.current.style.transform = `perspective(1000px) translate3d(${positionX}%, ${positionY}%, 0) rotateY(${rotateY}deg) scale(${scale})`; 29 } 30};

Here the applyParallax function takes the ref of the element, the mouse position, and some input parameters to calculate the position and rotation of the element based on the mouse movement.

The important part is the transform property where we set the perspective, translate3d, rotateY, and scale values. All the position movements are calculated relative to the element's center to create a more natural effect.

1const parallax = (e: PointerEvent) => { 2 const { clientX, clientY } = e; 3 4 applyParallax(environmentRef, clientX, clientY, -5, -5, 5, 1.3); 5 applyParallax(windowRef, clientX, clientY, -5, -5, 5); 6 applyParallax(imageRef, clientX, clientY, -2, -2, -2.5, 1, [-3, 3], [-5, 5]); 7}; 8 9useEventListener("pointermove", parallax);

Using pointermove event listener allows the effect to work on touch devices as well. The applyParallax function is called for each layer with different parameters to create a unique effect for each element.