Fast at the start, gentle at the end, never past the target.
This page compares how windows move in Hyprland, in a typical host app today, and under the proposed rules. The curves come from their real parameters: Hyprland's source, the host app's springs and tokens, and the proposal's launched, critically damped settle, stepped by this library. The dots are measured from a screen recording. Time runs left to right in milliseconds.
Built with @n-uf/motion (opens in a new tab) createSettle, SETTLE_TEMPO, reveal, createFrameLoop
Settle: one move from A to B
Hyprland front-loads the move and never bounces. A host app's re-layout spring starts slowly, and its maximize/restore curve overshoots. The proposed settle starts fast like Hyprland, cannot overshoot, and has one tempo knob ω.
Up is progress from A to B; above the dashed target line means overshoot.
- Measured: a real Hyprland window (dots)
- Hyprland spring 'easy'
- Hyprland older ease-out
- Typical app: re-layout spring from rest
- Typical app: maximize/restore (overshoots)
- n-uf motion settle, quick ω 37
- n-uf motion settle, default ω 25 library
- n-uf motion settle, calm ω 20
Each row beside the chart is the same curve as a moving block; dashed ticks mark the target. Speed slows the playback; Tempo picks the proposal's settle (calm 20, default 25, quick 37).
Try the settle at each tempo in the lab, with every move checked frame by frame.
Four moves, frame by frame
Settle: one move from A to B
Hyprland front-loads the move and never bounces. A host app's re-layout spring starts slowly, and its maximize/restore curve overshoots. The proposed settle starts fast like Hyprland, cannot overshoot, and has one tempo knob ω.
Drag: pointer vs window, then drop
Held means held: the window is the pointer minus the grab offset in the same frame. The host app's own-drag spring (stiffness 1400) trails by about 50 ms. On release the window settles from where it was let go into its slot (drop at 260 ms).
Retarget mid-flight
A nearer target at 40 ms. The proposed settle relaunches from the drawn rect toward the nearer target, at its tempo times the distance left, so it can't overshoot it. A tween restart kinks the speed, and the host app's docked branch (and a space change) stops and restarts from rest.
A window shrinking 220 → 140 px wide
Frames at 0, 30, 60 and 120 ms after the change. Stretch (today, and Hyprland for ~40 ms): content is painted at the final size and scaled to the drawn box. Reveal (proposed): content is laid out at the final size and never scaled, and the frame's background band closes in.
Eight rules, four engines
What Hyprland does (from its source and a recording), what a tiling library and a host app do today, and what the proposal does: one settle for every move, held drags, reveal instead of stretch.
Held drag
- Hyprland
- warped 1:1; only 1 frame of capture latency
- hypr-tiling today
- separate ghost at 90%, hops to the seat
- Host app today
- localDrag spring ≈ 50 ms lag (live move holds docked panels only)
- Proposed
- hold: pointer − grab, same frame
Interactive resize
- Hyprland
- all tiles warped, content unscaled
- hypr-tiling today
- divider 1:1
- Host app today
- sprung and scaled
- Proposed
- hold + reveal
Reflow curve
- Hyprland
- 90% ≈ 60 ms (recording) / 175–190 ms (shipped)
- hypr-tiling today
- 170 ms CSS, only during drags; others jump
- Host app today
- stiffness 320 from rest: 90% at 218 ms, slow start
- Proposed
- settle ω=25: 90% at 92 ms, done at 184 ms
Overshoot on size
- Hyprland
- none seen (shipped spring allows 1.8%)
- hypr-tiling today
- none by default; swap dip to 0.7
- Host app today
- maximize/restore "snappy" y2 = 1.05
- Proposed
- impossible (launch + clamp)
Retarget
- Hyprland
- from drawn; spring keeps velocity
- hypr-tiling today
- restarts CSS transition; settle strip snaps
- Host app today
- spring keeps velocity; tweens, space changes and docked restart
- Proposed
- always from drawn; a new target relaunches toward it, so it never passes it
Content
- Hyprland
- stretched ~40–50 ms on reflow
- hypr-tiling today
- stretched the whole 170 ms
- Host app today
- stretched (non-docked), docked size jumps
- Proposed
- never stretched; uniform popin only for open/close
Open / close
- Hyprland
- popin 87% + fade, ~100 ms
- hypr-tiling today
- appear/vanish instantly
- Host app today
- fade + 8 px rise 180 ms / 140 ms
- Proposed
- popin 0.87 → 1 (uniform) + fade on settle
Neighbours during drag
- Hyprland
- reflow at once when the window lifts
- hypr-tiling today
- reservation slot keeps the gap
- Host app today
- old slot kept (live move)
- Proposed
- left to your layout; motion glides whatever rects it is given
The same settle moves docks and palettes on a desk and lands a panel after a drag.
Questions
Why does the settle never overshoot its target?
Because createSettle follows distance · e^(−omega·t), a curve that only approaches its target. On every new target each component gets the velocity omega · distance toward it from wherever it is drawn, so there is no overshoot on position and no wobble on size, however often it is retargeted.
What are the settle's tempo values?
SETTLE_TEMPO has three: calm 20, default 25 and quick 37, as the omega in 1/s. They cover 90 % of the way in 115, 92 and 62 ms, then ease in to the target. Pass one as the omega option of createSettle or settleValue.
How do I respect prefers-reduced-motion?
Pass the reducedMotion option, and targets are reached at once, with no glide and no fade. prefersReducedMotion() from @n-uf/motion/browser returns a function that reads the (prefers-reduced-motion: reduce) media query, which is what the option takes. settleValue, springValue and createLayoutMotion all accept it.
Does resizing a window stretch its content?
Not when you use reveal. reveal(drawn, target) says how to draw a window gliding at drawn whose content is laid out at target: move the element, draw the frame at its animated size and clip the content. There is no scale and no layout per frame, so content is never stretched.