Spring Animations
Spring animations provide a natural, physics-based way to animate values. They’re perfect for creating smooth, organic motion that responds to changing targets.
- 🐦️ Physics-based spring motion
 - 🐦️ Configurable stiffness and damping
 - 🐦️ Automatic settling detection
 - 🐦️ Real-time target updates
 
Basic Usage Section titled Basic Usage
The simplest way to create a spring animation is using createSpring:
import { createSpring } from 'ararajs'
function SpringExample() {
const [spring, controller] = createSpring({
  target: 100,    // The value we're animating towards
  stiffness: 0.5, // How "rigid" the spring is
  damping: 0.5    // How quickly oscillations settle
})
return (
  <div style={{
    transform: `translateX(${spring.position}px)`
  }}>
    🔵
  </div>
)
}import { createElementSize } from '@solid-primitives/resize-observer'
import { createSignal } from 'solid-js'
import { createSpring } from 'ararajs'
export function SpringDemo() {
  const [container, setContainer] = createSignal<HTMLDivElement>()
  const containerSize = createElementSize(container)
  const [ball, setBall] = createSignal<HTMLDivElement>()
  const ballSize = createElementSize(ball)
  const [targetState, setTargetState] = createSignal(false)
  const target = () =>
    targetState() ? (ballSize.width ?? 0) : (containerSize.width ?? 0) - 120
  const [body] = createSpring(() => ({
    target: target(),
    stiffness: 66,
    damping: 16,
  }))
  return (
    <div class="flex w-full flex-col items-center">
      <div
        class="relative flex h-24 w-full justify-center overflow-hidden p-8"
        ref={setContainer}
      >
        <div
          class="absolute left-8 top-8 z-10 size-8 rounded-full bg-emerald-500 shadow-xl"
          style={{
            transform: `translate(${body.position}px`,
          }}
          ref={setBall}
        />
        <div
          class="absolute left-8 top-8 size-8 rounded-full bg-rose-400 shadow-xl"
          style={{
            transform: `translate(${target()}px`,
          }}
        />
      </div>
      <button
        onClick={() => setTargetState(!targetState())}
        class="flex w-fit flex-row items-center gap-4 rounded-lg bg-arara-300 px-4 py-3 text-lg font-medium transition-all duration-100 active:translate-y-0.5"
      >
        <div class="size-4 rounded-full bg-rose-400" /> Change Target
      </button>
    </div>
  )
}Spring Configuration Section titled Spring Configuration
Springs can be customized through several parameters:
type SpringOptions = {
  mass: number         // Mass of the object (default: 1)
  target: number       // Target value to animate to
  damping: number      // Damping force (default: 0.5)
  stiffness: number   // Spring stiffness (default: 0.5)
  targetThreshold?: number  // Distance to target to consider "settled" (default: 0.001)
}Understanding Spring Parameters Section titled Understanding Spring Parameters
- Mass - Higher mass means more inertia and slower response to forces
 - Damping - Higher damping means less oscillation but slower settling
 - Stiffness - Higher stiffness means faster motion but more oscillation
 - Target Threshold - How close to the target we need to be to stop the animation
 
Example: Spring 2D with Mouse Section titled Example: Spring 2D with Mouse
Move Mouse
import { createElementSize } from '@solid-primitives/resize-observer'
import { createSignal } from 'solid-js'
import { createSpring2D, vec2 } from 'ararajs'
import { cn } from '@lib/cn'
import { createMousePosition } from '@solid-primitives/mouse'
import { getDOMRootPosition } from './getDOMRootPosition'
export function Spring2DDemo() {
  const [container, setContainer] = createSignal<HTMLDivElement>()
  const containerSize = createElementSize(container)
  const [ball, setBall] = createSignal<HTMLDivElement>()
  const ballSize = createElementSize(ball)
  const padding = 32 // px
  const mouse = createMousePosition()
  const target = (): vec2 => {
    const origin = getDOMRootPosition(container())
    const rawTargetX =
      mouse.x - origin.left - padding - (ballSize.width ?? 0) / 2
    const minX = 0
    const maxX =
      (containerSize.width ?? 0) - (ballSize.width ?? 0) - 2 * padding
    const targetX = Math.max(minX, Math.min(rawTargetX, maxX))
    const rawTargetY =
      mouse.y - origin.top - padding - (ballSize.height ?? 0) / 2
    const minY = 0
    const maxY =
      (containerSize.height ?? 0) - (ballSize.height ?? 0) - 2 * padding
    const targetY = Math.max(minY, Math.min(rawTargetY, maxY))
    return [targetX, targetY]
  }
  const [body] = createSpring2D(() => ({
    target: target(),
    stiffness: 32,
    damping: 16,
  }))
  return (
    <div
      class="group relative min-h-64 w-full overflow-hidden rounded-lg bg-arara-text/90 p-8 shadow-inner"
      ref={setContainer}
    >
      <MoveMouseOverlay />
      <div
        class="z-10 size-8 rounded-full bg-emerald-500 shadow-xl"
        style={{
          transform: `translate(${body.position[0]}px, ${body.position[1]}px)`,
        }}
        ref={setBall}
      />
      <div
        class="absolute left-8 top-8 size-8 rounded-full bg-rose-400 shadow-xl transition-opacity"
        style={{
          left: `${target()[0] + padding}px`,
          top: `${target()[1] + padding}px`,
        }}
      />
    </div>
  )
}
function MoveMouseOverlay() {
  return (
    <div class="pointer-events-none absolute left-1/2 top-1/2 z-0 -translate-x-1/2 -translate-y-1/2 text-arara-article-bg transition-opacity duration-500 group-hover:opacity-0">
      <PointerSVG class="ml-auto size-12 animate-bounce pb-0 text-arara-article-bg" />
      <div class="text-center text-xl transition-opacity">
        Move <span class="bg-arara-bg p-1 text-arara-text">Mouse</span>
      </div>
    </div>
  )
}
function PointerSVG(props: { class?: string }) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      class={cn('size-32', props.class)}
      viewBox="0 0 36 36"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
    >
      <path d="M12.586 12.586 19 19" />
      <path d="M3.688 3.037a.497.497 0 0 0-.651.651l6.5 15.999a.501.501 0 0 0 .947-.062l1.569-6.083a2 2 0 0 1 1.448-1.479l6.124-1.579a.5.5 0 0 0 .063-.947z" />
    </svg>
  )
}Advanced: Using Spring Pass Section titled Advanced: Using Spring Pass
For more complex animations, you can use the spring pass directly with createBodyAnimation:
import { springPass, type SpringOptions } from 'ararajs'
function ComplexSpringAnimation() {
// Create a spring with multiple passes
const [spring] = createBodyAnimation(
  () => [
    // Spring physics
    springPass({ 
      target: 100,
      stiffness: 0.5 
    }),
    // Additional passes (e.g., constraints)
    ({ body }) => ({
      ...body,
      position: Math.max(0, Math.min(100, body.position)) // Clamp position
    })
  ]
)
return (
  <div style={{
    transform: `translateX(${spring.position}px)`
  }}>
    🔵
  </div>
)
}