GitHub repository

Hover Card Example

A fancy hover card showcasing what can be done by combining sine wave and spring primitives.

Hover Me

Features Section titled Features

  • 🐦️ 3D rotation based on mouse position
  • 🐦️ Scale animation on hover
  • 🐦️ Shining effect animation
  • 🐦️ Image scaling and rotation
  • 🐦️ Physics-based spring animations

Example Breakdown Section titled Example Breakdown

Hover State Management Section titled Hover State Management

const [isHovering, setIsHovering] = createSignal(false)
const [effectPercentage] = createSpring(() => ({
target: isHovering() ? 1 : 0,
stiffness: 150,
damping: 16,
}))

The component uses a spring-based animation to smoothly transition between hover states. The spring configuration uses:

  • stiffness: 150 - Controls how “rigid” the spring is
  • damping: 16 - Controls how quickly the spring settles

Mouse Position Tracking Section titled Mouse Position Tracking

const mouseDistanceFromCenter = createMouseDistanceFromCenter(cardContainer)
const [rotationPoint] = createSpring2D(() => ({
target: mouseDistanceFromCenter(),
stiffness: 150,
damping: 16,
}))

The rotation effect tracks the mouse position relative to the card’s center, creating a natural-feeling 3D effect. The createSpring2D adds smooth interpolation to the mouse movement.

Scale Animations Section titled Scale Animations

const raiseAmount = () => 1 + 0.1 * effectPercentage.position
const heroRaiseAmount = () => 1.2 - 0.1 * effectPercentage.position

Two scaling effects occur simultaneously:

  • The container scales up slightly on hover (raiseAmount)
  • The image scales down slightly (heroRaiseAmount)

Rotation Effects Section titled Rotation Effects

const CARD_ROTATION = 0.05
const roll = () => CARD_ROTATION * effectPercentage.position * rotationPoint.position[1]
const tilt = () => CARD_ROTATION * effectPercentage.position * rotationPoint.position[0]

The card rotates based on:

  • Mouse position (through rotationPoint)
  • Hover state (through effectPercentage)
  • A constant factor (CARD_ROTATION) to control the maximum rotation

Shining Effect Section titled Shining Effect

const [shiningEffectAnimation] = createBodyAnimation(() => [
sineWavePass({
  amplitude: 0.5,
  frequency: 0.2,
  phase: Math.PI / 2,
}),
/*
CUSTOM PASS
Manually filter only the upper movement of the wave
*/
({ body }) => {
  if (body.velocity < 0) {
    return {
      position: 0,
      velocity: 0,
      acceleration: 0,
    }
  }
  return {
    ...body,
    position: body.position + 0.5,
  }
},
])

The shining effect combines:

  1. A sine wave for periodic movement
  2. A custom filter that only allows upward movement
  3. Linear interpolation for positioning

The interpolation needs to take into account the size of the card and the size of the effect. The bottom of the effect should start right above the card and end when it’s top passed the whole card’s height.

const [shiningEffectContainer, setShineEffectContainer] = createSignal<HTMLDivElement>()
const shiningEffectContainerSize = createElementSize(shiningEffectContainer)

const cardContainerSize = createElementSize(cardContainer)
const shiningEffectTranslation = () => {
const min = -(shiningEffectContainerSize.height ?? 0) - 30
const max =
  (cardContainerSize.height ?? 0) + (shiningEffectContainerSize.height ?? 0)
return lerp(shiningEffectAnimation.position, min, max)
}

Styling Section titled Styling

The Image rotation and scaling is slightly different than the card’s. That effect is intentional and is called parallax.

Here are some of the TailwindCSS techniques used for styling:

  • Rounded corners (rounded-lg)
  • Dark background (bg-gray-800)
  • Shadow effects (shadow-lg)
  • Inset shadows for image depth
  • Ring effects for border highlighting