Hover✨ Premium

Spring Physics

Each box stores its own spring physics: a click imparts an initial force, the loop calculates acceleration and damping frame by frame until the box returns to rest. Several boxes bounce independently on the same page, each with its own cycle. No CSS animation, no library.

JSSpringBounce

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →

3 usage examples

Add to cart button on a product page — visual confirmation of the primary action — Spring Physics example 1

① Add to cart button on a product page — visual confirmation of the primary action

WhenThe user clicks the primary CTA; the button bounces slightly upward then returns, signalling the action was registered.
WhyA spring on a single button focuses feedback without animating the whole page; it replaces a simple colour change and reinforces trust at the key conversion moment.
SettingsOne full-width [data-spring] element (height: 44px); violet brand gradient background; impulse at −15 (default, visible bounce without excess).
Tab bar icons in a navigation bar — micro visual feedback on section change — Spring Physics example 2

② Tab bar icons in a navigation bar — micro visual feedback on section change

WhenThe user taps a bottom-tab icon; the icon and its label spring slightly upward then return, reproducing the haptic feedback of a native interface.
WhyA micro-spring on a tab icon makes navigation feel more tactile on mobile without weighing down the UI; it complements the active colour and removes the flat-interface impression.
SettingsFour [data-spring] elements in flex (flex:1) inside the tab bar (height: 64px); SVG icons + label (10 px); min-height:0 on the root, physics-container at 64 px.
Vote ▲ buttons in a community board — gamified click feedback — Spring Physics example 3

③ Vote ▲ buttons in a community board — gamified click feedback

WhenThe user clicks ▲ to vote for a project; the button springs upward and returns, reinforcing the sense of participation and encouraging voting on other projects.
WhyA spring on a micro-component (36×38 px vote button) isolates the feedback without distracting from the content; repeating the effect on each list row creates interaction consistency.
SettingsOne [data-spring] per list row, in a 48×48 px physics-container; impulse −15 default, damping 0.7 — the bounce is quick and short to avoid blocking repeated votes.

How it works

The .fx-0386-root wrapper centres the three .spring-box[data-spring] boxes horizontally in a 300 px tall container. The script finds every root on the page — multiple instances coexist — and for each data-spring box creates an isolated physics context: a displacement t, an acceleration a, and a running flag.

On click (or touch), a receives an impulse of −15. Each frame the loop applies a += 0.3 × −t (restoring force proportional to displacement, Hooke's law), then a *= 0.7 (30 % damping) and t += a. The box is repositioned via translateY(t) scale(1 + 0.01 × |t|): it rises, overshoots, and returns with decreasing oscillations. When |t| < 0.05 and |a| < 0.05, the loop stops and the box snaps back to zero.

Each box has its own requestAnimationFrame, independent of the others: clicking « BOUNCE » while « SPRING » is still bouncing does not interfere with either cycle. Two copies of the effect on the same page work identically, each found by its own .fx-0386-root.

Accessibility

  • Reduced motion: under prefers-reduced-motion: reduce, the loop does not start and CSS forces transform: none on the boxes. The boxes remain visible, clickable, and readable — the visual bounce disappears, not the content.
  • Keyboard: .spring-box elements are clickable <div> elements but not focusable by default — add tabindex="0" and a keydown listener for Enter/Space to make them keyboard-accessible when used as functional buttons.
  • Touch: a touchstart event triggers the spring, with preventDefault to avoid double-firing mouse/touch. The effect works on mobile touchscreens.
  • Decorative or functional role: if the boxes carry an action (vote, download), replace the <div> elements with <button> elements and remove the redundant touchstart listener — a <button> responds natively to keyboard and touch events.

Browser compatibility

ES5 JavaScript (closures, requestAnimationFrame, matchMedia), no dependency. Not measured in Firefox and WebKit: the expected behaviour is identical since the effect uses only universal APIs.

Chrome 24+✓ Full (measured)
Firefox 23+✓ Full (not measured)
Safari 7+✓ Full (not measured)
Edge 79+✓ Full (Chromium engine)
Mobile iOS 7+✓ Full (not measured)
Android Chrome 24+✓ Full (measured on Chromium)

Without JavaScript, the boxes display at their rest position with no animation. Without matchMedia (very old browsers), the spring animates even under reduced motion.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0386-root"><div class="physics-container"><div class="spring-demo"><div class="spring-box" data-spring="">LABEL</div></div><span class="info-label">Help text</span></div></div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Initial impulse (JS — a = -15) −15 −8 gives a softer spring, −25 a stronger bounce. Negative goes down first, positive goes up first.
Restoring force (JS — a += 0.3 × −t) 0.3 0.1 slows the spring (long oscillations), 0.5 speeds it up (abrupt return).
Damping (JS — a *= 0.7) 0.7 0.9 gives an almost frictionless spring (bounces for a long time), 0.5 stops it quickly.
Box colour (CSS — background of .spring-box) linear-gradient(135deg, #6366f1, #8b5cf6) Replace with your brand's primary colour or a solid fill. The box labels can be changed directly in the HTML.
Number of boxes (HTML) 3 boxes Add or remove .spring-box[data-spring] elements; each new box automatically receives its own physics.

FAQ

Yes. Each box has its own independent requestAnimationFrame loop. Clicking all three quickly launches three springs in parallel without interference.
The loop stops automatically when position and acceleration are both near zero (|t| < 0.05 and |a| < 0.05). To speed up the stop, lower the damping value (for example a *= 0.5).
Yes: replace the click listener with mouseenter, and add a = 0; t = 0; on mouseleave if you want to reset the state on exit.