Skip to content
Native Animation
Caught Up Feed End animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add caught-up-feed-end
LoadingSkia

Caught Up Feed End

A feed ending with a scroll-drawn check and elastic load-more action, with configurable stories, copy, fonts, and accent color.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A feed ending with a scroll-drawn check and elastic load-more action, with configurable stories, copy, fonts, and accent color.

Why this one

An endless spinner gives readers no sense of arrival. This pattern makes completion a reward: the check draws with the final 180 points of scrolling, the circle fills, and ten fine particles accompany a small lift. A tensioned arc makes the next page feel attached to the finger. Three skeletons resolve individually, and the completion block moves to the new end.

How it works

  1. A native ScrollView measures its content and viewport. A UI-thread shared scroll offset derives the remaining distance and check stroke.
  2. A simultaneous pan tracks only upward travel after reaching the bottom. Progressive resistance caps the pull without a hard stop.
  3. Releasing beyond the threshold or tapping the button triggers the same guarded async request. Three compact placeholders are inserted above the completion block.
  4. After data arrives, one shared page progress crossfades the three stories sequentially. Shimmer stays clipped inside each skeleton shape.
  5. A successful page commits once and returns to the relocated end. Touching interrupts automatic scrolling; failed requests expose a retry button.

Install

npx expo install @shopify/react-native-skia expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-worklets

Wrap the app in GestureHandlerRootView. Load Skia/CanvasKit before rendering on web (for example, with Skia's WithSkiaWeb loading boundary). Keep demo/ out of the files copied into your product. No assets, fonts, or theme imports outside this folder are required by the component.

Usage

import { CaughtUpFeedEnd, type FeedStory } from './caught-up-feed-end';

const stories: FeedStory[] = [{
  id: 'dawn',
  title: 'A slower start to the day',
  category: 'Field notes',
  readingTime: '4 min read',
  image: require('./assets/dawn.jpg'),
  imageLabel: 'A landscape at dawn',
}];

<CaughtUpFeedEnd
  stories={stories}
  onLoadMore={async () => fetchOlderStories()}
/>

A customized feed:

<CaughtUpFeedEnd
  stories={stories}
  onLoadMore={fetchOlderStories}
  colorScheme="dark"
  accentColor="#38BDF8"
  fontFamily="YourLoadedSans"
  titleFontFamily="YourLoadedSerif"
  pullThreshold={64}
  revealDuration={420}
  title="A good place to pause"
  description="Everything from your favorite writers, read."
  onStateChange={state => updateReadingStatus(state)}
/>

Props

PropTypeDefaultDescription
storiesreadonly FeedStory[]RequiredInitial articles with unique IDs, headline, category, reading time, image, and image label.
onLoadMore() => Promise<readonly FeedStory[]>RequiredFetches up to three older articles. Reject or return an empty page to offer retry.
titlestringYou're all caught upCompletion headline.
descriptionstringYou've read everything from the last 3 daysCompletion description.
buttonLabelstringSee older storiesVisible and accessible load label.
accentColorstring#0EA5E9Check, arc, and button accent; use a six-digit hex color.
colorScheme'light' | 'dark' | 'auto'autoColor treatment.
fontFamilystringSystemBody font. Load it in the host app.
titleFontFamilystringBody fontEditorial font.
pullThresholdnumber58Pull distance after resistance, in points. Keep below the 170-point asymptote.
revealDurationnumber380Crossfade duration per article, in milliseconds.
onStateChange(state) => void—caught-up, loading, loaded, or error.
onInteraction() => void—Stops playback when the reader touches or manually scrolls.
styleStyleProp<ViewStyle>—Root container style; root defaults to flex: 1.
testIDstring—Root test identifier.

The forwarded CaughtUpFeedEndRef exposes scrollToEnd(), pull(), loadMore(), and reset(). The demo owns autoplay; the production component has no demo-specific props. Incoming articles should have IDs distinct from earlier pages. The initial list is read directly from props; appended pages remain until reset.

Timing and layout

The demo has a 0.35-second reading pause, a 1-second scroll to the reward (visible before 3 seconds), an elastic pull at 2 seconds, and loading at 2.5 seconds. The mock request resolves after 800 ms; stories crossfade at 380 ms each. The next loop starts at 7.4 seconds. Touching stops that loop and leaves real interaction available.

At 320×694 and 390×844, the header and bottom appearance control stay in flow. The feed fills the remaining height and scrolls; cards use available width with 20-point side padding. The upper-right 58 points of the header are reserved for the catalog close control. Cards use minimum heights and can grow with multiline editorial titles; completion copy stays centered in a 240-point measure.

Compatibility

Built for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Uses modules included in this project's Expo Go stack. Web requires CanvasKit initialization before mounting.

Accessibility

The button has a button role, explicit label, loading state, and native accessibility activation action. It is a 48-point touch target and provides the same action as the overscroll gesture. Important transitions announce completion, loading, page arrival, or failure; status copy uses a polite live region. Images have descriptions and decorative canvases/skeletons are hidden from assistive technology.

Reduced motion removes particles, the check lift, shimmer, press scaling, and article translation. Each incoming article uses a 200 ms crossfade; automatic scrolling jumps to its destination. The check uses a 200 ms opacity change instead of drawing its stroke. There are no blur or trail effects.

Icons: Lucide (ISC), copied from the project kit. Demo brand: Lumen, using the kit's amber tile and lamp glyph. Photos are bundled inside demo/assets/.

Implementation notes

Copy stays in normal flow; flexible card heights prevent headline clipping at 320×694 and 390×844. The scrolling feed absorbs the difference in screen height, and the completion copy has a 240-point measure. The header reserves the upper-right 58 points for the viewer close control. The completion burst is scheduled around 1.35 seconds into autoplay. Shimmer is clipped to empty skeleton shapes, never placed over rendered text. Reduced motion replaces the elastic arc with a 200 ms fade.