About this animation
A sale banner with jumping balloon letters and a printing receipt, with configurable copy, character artwork, fonts, and colors.
Why this one
A sale illustration usually locks its headline into an image. This poster keeps the headline editable, draws each character with rounded ink and cream outlines, and lets a touch send a jump through the lettering.
How it works
- The container measures its available space and fits a 360 × 640 poster inside it, including landscape and embedded layouts.
- The poster canvas draws the window, grain, seventeen fine bristle passes per percentage, six textured flower petals, and balloon letters. A sixteen-point mustard badge carries a fitted heavy sale label, a slowly rotating ring of editable offer copy, thick ink outlines, and a hard offset shadow. It stamps in at 430 ms with scale 1.6 → 1 and a short window shake.
- Each letter derives its pose from an entrance spring, an interruptible jump, and one shared ambient clock. Entrance springs use explicit mass 1 and stagger by 45 ms.
- Touching the headline locates the closest letter and emits three ink sparks. The accessible headline button provides the same interaction; the offer button sends a wave through every letter.
- The character jumps from below after 680 ms. One shared progress drives a mass-1 entrance spring, foot-pivoted squash and stretch (0.9 × 1.1 → 1.08 × 0.92 → 1), and the resting effects. A 600 ms laugh moves it up to 2.7 points with ±2° rotation every 3 seconds; the remaining time is still. Every 4 seconds, a 700 ms shine crosses the artwork-specific card clip with four yellow sparks. Skia dilates the alpha silhouette for a thick white sticker border and a hard black offset shadow. The optional hand clip retains a subtle synchronized card tap.
- Reduced motion removes movement and particles, and fades in the poster artwork.
Install
npx expo install @shopify/react-native-skia expo-image react-native-reanimated react-native-workletsConfigure Reanimated for your Expo app. On web, configure Skia's CanvasKit asset using the Skia web installation instructions. No fonts or character images are bundled into the reusable component: supply your own font asset and transparent cutout.
Usage
import { useRef } from 'react';
import { BubbleSaleBanner, type BubbleSaleBannerRef } from './bubble-sale-banner';
const poster = useRef<BubbleSaleBannerRef>(null);
<BubbleSaleBanner
ref={poster}
titleFont={require('./assets/Display-ExtraBold.ttf')}
fontFamily="DisplayExtraBold"
headline={['BLACK', 'FRIDAY']}
date="11.27.26"
character={require('./assets/shopper.webp')}
onPress={() => openOffers()}
/>;
// Personality knobs and external control:
<BubbleSaleBanner
titleFont={require('./assets/Display-ExtraBold.ttf')}
headline={['BIG', 'WEEKEND']}
offerLines={['SHOP', 'THE SALE']}
stagger={55}
sway={1.4}
colors={{ background: '#FF9AD9', flower: '#74DD8A' }}
/>;
poster.current?.wave();
poster.current?.replay();Props
| Prop | Type | Default | Description |
|---|---|---|---|
headline | readonly string[] | ['BLACK', 'FRIDAY'] | Up to three fitted headline lines; use short words for generous letter touch spacing. |
date | string | '11.27.26' | Date label. |
ribbon | string | 'SALE' | Fitted center label on the sale badge. |
saleRingText | string | 'UP TO 70% OFF • UP TO 70% OFF •' | Rotating badge ring copy. |
offerLines | readonly string[] | ['SPECIAL', 'OFFER'] | Short offer button lines. |
character | ImageSource | — | Transparent character cutout; use a source at least 640 px wide. |
characterFlipHorizontal | boolean | false | Mirrors the illustration toward the offer. |
characterHandPath | string | — | SVG path around the hand and card, in original image pixels; enables the separate tap. Supply an artwork-specific silhouette and a URI or bundled image source. |
characterCardPath | string | — | SVG clip around the card in original artwork pixels; enables the synchronized shine and anchors four sparkles. Works without a hand clip. |
titleFont | DataSourceParam | Required | Font asset for Skia lettering. |
fontFamily | string | System | Loaded supporting text font. |
colors | Partial<BubbleSaleColors> | Poster palette | Background, frame, block, accent, flower, percentA, percentB, letterFill, letterStroke, ribbon, pattern, and paper. |
onPress | () => void | — | Offer action callback. |
onInteraction | () => void | — | Stops host autoplay on direct interaction. |
headlineAccessibilityLabel | string | 'Make the headline jump' | Headline button instruction. |
offerAccessibilityLabel | string | Offer lines | Accessible offer label. |
stagger | number | 45 | Entrance delay per letter in milliseconds. |
sway | number | 2 | Ambient rotation amplitude in degrees. |
style | StyleProp<ViewStyle> | — | Root container style. |
testID | string | 'bubble-sale-banner' | Prefix for root, headline, and offer identifiers. |
The ref exposes replay() and wave(). Autoplay belongs to the demo and stops on interaction.
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Skia 2.14.0, and expo-image 58.0.17. Uses bundled Expo Go modules. Supports iOS, Android, and web.
The host owns safe-area padding. The poster fits its parent instead of scrolling and centers in wide containers. Very long copy should be shortened for this display composition. Supporting text respects system font scaling; the headline is deliberately drawn as poster artwork.
Accessibility
The headline is one accessible button announcing the complete headline. Touch position selects the nearest letter, and the offer button provides an accessible whole-headline wave. The offer has a stable test identifier and an effective target larger than 48 points in the required layouts. Reduced motion disables flights, sway, spinning, scrolling, sparks, and spring jumps. Decorative artwork is excluded from interaction.
The demo uses a supplied 1024 × 1024 transparent comic sticker, shown at 212 × 212 poster points at the lower right and extending below the browser frame. The default poster retains its vertical mobile composition; it adapts the horizontal reference rather than reproducing its landscape aspect ratio.




