axi0motion

Card pile

A pile of cards you drag, flick or click through; a thrown card flies off and tucks back in at the bottom of the pile.

  • enhancer
  • cards
  • drag
  • swipe
  • gallery
  • keyboard
  • since 0.2

Background Studio: tune it below, then copy the code

Your system asks for reduced motion: the demo shows its final state and nothing moves.

Live demo

Card pile example

Dusk over the dunes
A calm sea
Morning peaks
Night watch

Controls

7 deg
5 px
90 px

Code

It follows the controls above: change an option and the code changes with it.

<script src="https://search.axi0.io/axi0/motion/motion.js" defer></script>

<div data-motion="card-pile" data-motion-label="Postcards"><figure class="m-pile-pc"><svg viewBox="0 0 190 220" width="190" height="220" aria-hidden="true"><defs><linearGradient id="m-pile-g1" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#2b1b4a"/><stop offset=".6" stop-color="#ff6b8b"/><stop offset="1" stop-color="#f6a04d"/></linearGradient></defs><rect width="190" height="220" fill="url(#m-pile-g1)"/><circle cx="120" cy="120" r="30" fill="#ffd9a8"/><path d="M0 150 Q50 120 100 150 T190 140 V220 H0z" fill="#b8541c"/><path d="M0 180 Q60 150 120 178 T190 172 V220 H0z" fill="#7a3210"/></svg><figcaption>Dusk over the dunes</figcaption></figure><figure class="m-pile-pc"><svg viewBox="0 0 190 220" width="190" height="220" aria-hidden="true"><defs><linearGradient id="m-pile-g2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#7fd3ff"/><stop offset="1" stop-color="#e8f7ff"/></linearGradient></defs><rect width="190" height="220" fill="url(#m-pile-g2)"/><circle cx="52" cy="70" r="20" fill="#fff4c2"/><rect y="128" width="190" height="92" fill="#1f6fb8"/><path d="M0 150 q12 -6 24 0 t24 0 t24 0 t24 0 t24 0 t24 0 t24 0 t24 0" fill="none" stroke="#bfe6ff" stroke-width="3"/><path d="M0 178 q12 -6 24 0 t24 0 t24 0 t24 0 t24 0 t24 0 t24 0 t24 0" fill="none" stroke="#8cc8f2" stroke-width="3"/></svg><figcaption>A calm sea</figcaption></figure><figure class="m-pile-pc"><svg viewBox="0 0 190 220" width="190" height="220" aria-hidden="true"><defs><linearGradient id="m-pile-g3" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#b9f0e1"/><stop offset="1" stop-color="#f4fbe9"/></linearGradient></defs><rect width="190" height="220" fill="url(#m-pile-g3)"/><path d="M-10 170 L60 70 L120 160 L160 100 L210 170 V220 H-10z" fill="#3f8f6b"/><path d="M60 70 L76 93 L44 93z" fill="#fff"/><path d="M-10 200 L40 150 L100 196 L150 160 L210 200 V220 H-10z" fill="#1f5c45"/></svg><figcaption>Morning peaks</figcaption></figure><figure class="m-pile-pc"><svg viewBox="0 0 190 220" width="190" height="220" aria-hidden="true"><defs><linearGradient id="m-pile-g4" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#0b1030"/><stop offset="1" stop-color="#3b3f8f"/></linearGradient></defs><rect width="190" height="220" fill="url(#m-pile-g4)"/><circle cx="136" cy="58" r="22" fill="#fff6d8"/><circle cx="146" cy="52" r="20" fill="#0f1538"/><circle cx="30" cy="40" r="1.6" fill="#fff"/><circle cx="70" cy="76" r="1.2" fill="#fff"/><circle cx="96" cy="30" r="1.6" fill="#fff"/><circle cx="52" cy="112" r="1.2" fill="#fff"/><circle cx="168" cy="120" r="1.4" fill="#fff"/><path d="M0 176 Q70 138 190 170 V220 H0z" fill="#090c22"/></svg><figcaption>Night watch</figcaption></figure></div>

The wrappers: CardPile.tsx (React, TypeScript) and CardPile.vue (Vue). Both render the same attributes as the HTML; the loader does the rest.

Options

AttributeDefaultWhat it does
data-motion-tilt7How far the cards under the top one turn, in degrees
data-motion-spread5How far each card below peeks out
data-motion-distance90How far a drag must go to throw the card
data-motion-labelCard pileThe pile's name for screen readers (when it has no aria-label)

Accessibility

The pile is one focusable group ("card pile") with a label. Enter, Space, Right or Down sends the top card to the bottom; Left or Up brings the last card back to the top. A polite live region names the new top card ("Card 2 of 4: A calm sea"). Only the top card is reachable; the ones under it are inert and hidden from screen readers. Links and buttons inside a card stay clickable. Touch: swipe sideways to throw; vertical swipes still scroll the page. Only transform, opacity and paint change, so nothing around it moves. Frames run through the shared loop, which stops off screen, in hidden tabs and under reduced motion; reduced motion shows the final state with no movement.

Size

card-pile.js is 2.4 KB gzip. A page loads the loader (2.9 KB) and core.js (1.4 KB) once, whatever it uses.