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:

Could not save changes

Please fix the highlighted fields and try again.

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:

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

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:

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.