ENGINEERING NOTE
Controlling scroll-driven 3D motion
A scroll animation has two clocks: the distance the page has moved and the time available to show that movement. Connecting them directly makes a fast swipe produce a fast rotation. This portfolio’s hero separates those clocks so the phone can follow scrolling with a calmer, bounded response.
Scroll sets the destination
Scroll position selects a target pose: rotation, depth, scale and opacity. The mobile phone starts turning gently, then gains response as its exit approaches. Desktop uses an enlarging fade instead. Native scrolling remains untouched; the animation follows the page rather than controlling it. A full turn is available during gradual scrolling, but forcing every swipe to display that entire turn would leave the animation chasing a visitor who has already moved on.
Time limits the rotation
Each animation frame moves the current angle toward its target. An exponential damper softens the approach, while an angular speed cap bounds the step using elapsed time. The mobile hero uses a 200° per second yaw limit, with 60° per second limits for pitch and roll, and a 130 ms damping duration. These are tuning choices for this composition, not universal values or a frame-rate guarantee.
step = clamp(
(target - current) * (1 - exp(-dt / tau)),
-rate * dt,
rate * dt
)Fade without waiting for a full turn
Opacity follows its own 110 ms damper. A large scroll jump can therefore fade the phone before its capped rotation catches up. Once fully invisible, the remaining angle can be reconciled without replaying a hidden spin. Reverse scrolling needs similar care: equivalent whole turns can be discarded during reappearance. Meanwhile, the incoming device group’s counter-scroll stays responsive. Applying the same slow rotation limit to its position would make its brief sticky interval drift away from the viewport.
Stop work when nothing changes
A passive scroll listener schedules work through requestAnimationFrame. Only one frame is pending at a time, and the loop ends when the pose settles. Cached layout geometry is refreshed after resize or other relevant changes, rather than measured for every style update. Visibility checks suspend work outside the hero corridor and in hidden tabs. An invisible phone also pauses its decorative float and surface shine.
Keep motion within a budget
The devices use CSS transforms and opacity with simple geometry, rather than a WebGL runtime. Updates go directly to the elements that own those transforms, avoiding inherited animation variables across decorative SVG trees. This reduces avoidable style work; compositing still depends on the browser and device. Reduced-motion preferences restore a static pose. Visual smoothness should be checked on real hardware with fast swipes, slow scrolling and reversals. A navigation performance score cannot establish animation FPS.