Simple Math for Better UI Motion
• UI/UX, Interaction Design
Why this post
This post shows how to use simple math to make everyday UI motion feel deliberate and steady instead of jittery or dizzying. We will lean on a few practical patterns: nonlinear intensity, smooth noise, and time scaled smoothing.
Rule of thumb: think of motion like seasoning. A pinch improves everything; a handful ruins the dish. Use it thoughtfully.
Nonlinear intensity and soft drop (Error dialog)
Goal: make the error modal feel physical. Baseline uses a small vertical jitter. Improved does a tiny diagonal “drop” from top left to bottom right, then settles back to center.
Open an error modal and compare the baseline (random jitter) to the improved version (smooth noise with nonlinear intensity and decay).
Code idea
// baseline: small vertical jitter (nonlinear intensity + decay)
intensity = Math.max(0, intensity - decay * dt)
amp = maxPx * (intensity * intensity)
ox = 0
oy = (Math.random()*2 - 1) * amp
// improved: short springy drop along a 45° diagonal, then settle
// s is position along the diagonal; v is velocity
u = [1/Math.SQRT2, 1/Math.SQRT2]
// start from top‑left (negative s), spring toward 0 with damping
acc = (-k * s) - (d * v)
v += acc * dt
s += v * dt
ox = u[0] * s; oy = u[1] * s
modal.style.transform = `translate(${ox}px, ${oy}px)`
Before: After:
Smooth follow (Drawer)
Goal: avoid a hard snap when the drawer opens and closes. Move a fraction of the remaining distance each frame, scaled by time.
Code idea
// pos 1 is offscreen, 0 is onscreen
alpha = 1 - Math.pow(1 - kPerSecond, dt * 60) // time scaled
pos += (target - pos) * alpha // smooth follow
drawer.style.transform = `translateX(${pos*100}%)`
Before: After:
Notifications
All caught up. Come back later for new updates.
Feathered bias (Inline error)
Goal: draw attention to the field with an error without a harsh jump. Compute a target scroll position with a small bias, then ease toward it.
Code idea
targetY = fieldTop - window.innerHeight * 0.35 // biased target
alpha = 1 - Math.pow(1 - 0.18, dt * 60) // gentle ease
y += (targetY - y) * alpha
window.scrollTo(0, y)
Before: After:
Sample Form
Enter a valid email.
Toast: snap vs smooth follow with gentle pulse
Goal: show feedback that feels calm and readable. Baseline snaps. Improved eases in and adds a tiny settling motion.
Code idea
// baseline
toast.hidden = false; toast.style.transform = 'translateY(0)';
// improved
pos = 1; target = 0; // 1 off, 0 on
alpha = 1 - Math.pow(1 - kPerSecond, dt * 60)
pos += (target - pos) * alpha
toast.style.transform = `translateY(${pos*20}px)` // 20px travel
// small noise pulse for a short time after showing
Before: After:
Saved successfully
Sticky header: jump vs smooth follow
Goal: avoid a hard jump when the header sticks. Baseline flips at a threshold. Improved eases into place.
Code idea
// baseline
if (scrollY >= threshold) header.style.transform = 'translateY(0)'; else header.style.transform = 'translateY(-60px)';
// improved
targetY = (scrollY >= threshold) ? 0 : -60
posY += (targetY - posY) * alpha
Before: After:
Section Header
Reorder: instant vs smooth settle
Goal: when items change order, let them glide into place. Baseline teleports. Improved eases between positions.
Code idea
// baseline: swap immediately
swap()
// improved: animate positions with smoothing
posA += (targetA - posA) * alpha
posB += (targetB - posB) * alpha
Before: After:
A
B
Summary
- Map event strength nonlinearly to response.
- Use coherent noise for natural, time-scaled micro‑motion.
- Apply asymptotic smoothing for calm, readable movement.
- Feather biases toward POIs; avoid snaps.
- Split attention when truly needed, and merge gracefully.