Skip to content
Native Animation
Meal Scan Nutrition animation preview

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

npx nativeanimation add meal-scan-nutrition
Glass

Meal Scan Nutrition

An animated nutrition scan with ingredient labels and a results panel, using your photo, labels, fonts, and theme.

$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

Related guides

An animated nutrition scan with ingredient labels and a results panel, using your photo, labels, fonts, and theme. Your app supplies ingredient and macro data; the component does not capture photos or infer nutrition.

Why this one

Instead of a spinner followed by a table, the scan shows its work: the frame settles around the plate, a luminous line passes down and back up, ingredient anchors release glass labels, and the photo makes room for the nutrition panel. Selecting an ingredient connects the visual detection to its calorie and macro breakdown.

This is a visualization of application-supplied nutrition data. It does not use the camera, identify food, or estimate nutrition from an image.

How it works

  1. onLayout measures the host. The photo spans the available width with a 440-point cap; small containers can scroll vertically.
  2. One continuous UI-thread progress value coordinates the 3-second scan: framing at 0–360 ms, down-and-up scan at 360–1380 ms, staggered detections at 1410–1935 ms, photo contraction at 2040–2520 ms, and nutrition at 2100–3000 ms.
  3. A clipped second rendering of the actual image uses a color matrix to brighten the scanned region. A translucent gradient trails the scan line inside the photo.
  4. Ingredient labels use rounded BlurView surfaces over a BlurTargetView. Gesture Handler taps have their own pressure springs. Their anchor coordinates track the image's centered cover crop.
  5. Calorie columns roll on the UI thread, each carrying into the next column and settling at the exact integer. The three macro bars grow in sequence; the balance ring uses a single continuous path.
  6. Tapping a label dims the other detections and updates the panel. Tap it again, or tap the panel's calorie heading, to restore the meal totals. Repeated scan requests resume an active scan; completed scans restart.

Install

npx expo install @shopify/react-native-skia expo-blur react-native-gesture-handler react-native-reanimated react-native-worklets

Use a GestureHandlerRootView at your app root and configure Reanimated/Worklets as required by your Expo SDK. For Skia on web, initialize CanvasKit through your host's Skia web setup. The demo additionally uses expo-linear-gradient for its kit brand tile and host-loaded fonts.

Usage

import MealScanNutrition from './meal-scan-nutrition';

<MealScanNutrition
  image={require('./assets/bowl.jpg')}
  ingredients={[
    { name: 'Tofu', kcal: 180, x: 0.4, y: 0.52,
      macros: { protein: 20, carbs: 4, fat: 10 } },
  ]}
  totals={{ kcal: 482, protein: 28, carbs: 46, fat: 19, balance: 8.4 }}
  onScan={() => requestNutrition()}
/>

Customize copy, typography, and the whole palette:

const scanner = useRef<MealScanNutritionRef>(null);

<MealScanNutrition
  ref={scanner}
  image={photo}
  ingredients={ingredients}
  totals={totals}
  duration={2800}
  fontFamily="YourSans"
  titleFontFamily="YourDisplay"
  monoFontFamily="YourMono"
  labels={{ title: 'Lunch, understood.', scan: 'Analyze lunch' }}
  theme={{ accent: '#C6F432', surface: '#171C12' }}
  testID="lunch-scan"
/>

// Event handlers or effects:
scanner.current?.scan();
scanner.current?.selectIngredient(0);
scanner.current?.reset();

Import MealScanNutritionRef from the same folder. The demo owns autoplay: 1.5-second idle, scan, labels, panel, Tofu selection at 5.1 seconds, total at 6.7 seconds, reset at 8.2 seconds. All timers are cleaned up.

Props

PropTypeDefaultDescription
imageSkia DataSourceParamRequiredLocal module or image URI; portrait 4:5 recommended.
ingredientsMealIngredient[]RequiredNames, kcal, normalized photo coordinates, and macros in grams. Four detections recommended.
totalsMealMacros & { kcal; balance }RequiredMeal nutrition and balance score from 0 to 10.
onScan() => void—Scan request callback.
labelsPartial<Labels>English copyAll visible copy, units, and announcements.
themePartial<MealScanTheme>Dark/limeBackground, surface, text, muted, border, accent, amber, coral, buttonText, glass. Supply all fields for a complete light theme.
fontFamilystringSystemInterface text.
titleFontFamilystringInterface fontDisplay heading.
monoFontFamilystringSystemCalorie columns and score.
durationnumber3000Timeline duration in ms, minimum 1000.
headerReactNode—Optional product identity.
styleStyleProp<ViewStyle>—Root container styling.
testIDstringmeal-scan-nutritionRoot ID; controls append -scan, -total, or -ingredient-N.

The forwarded ref exposes scan(), reset(), and selectIngredient(index | null). Values are supplied in kcal and grams; replace their visible unit labels through labels. Calories display nonnegative whole values up to 9999. Ingredient names should be short enough for two-line glass labels. More than four detections may need custom label placement to avoid collisions.

Compatibility

Expo SDK 58; React Native 0.88.0; Reanimated 4.7.1; Worklets 0.13.0; Skia 2.14.0. Dependencies are included in the project's Expo Go stack. Supports iOS, Android, and web. The host applies safe area insets; the component does not add device insets. Vertical scrolling preserves access in small containers, landscape, and larger text settings. Use a high-resolution local image to prevent empty remote-image scan entrances.

Accessibility

Scan and total controls expose button roles and labels. Ingredient labels support touch and accessibility activation. Scan start, nutrition readiness, reset, and ingredient selection are announced. Pressure feedback uses independent shared values. Reduced motion removes breathing, scan travel, panel translation, and digit travel, replacing changes with short fades. No per-frame React state is used.

Icons: Lucide (ISC), copied from the repository kit. Demo identity: Ember & Oat, from the repository brand kit. Photo: supplied local demo art, 1024×1280.