Skip to content

feat(rnre): background-image for css animations - #10664

Closed
tshmieldev wants to merge 9 commits into
mainfrom
@tshmieldev/bgimg-css
Closed

tshmieldev wants to merge 9 commits into
mainfrom
@tshmieldev/bgimg-css

Conversation

@tshmieldev

@tshmieldev tshmieldev commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Note

This pull request was authored by AI on behalf of @tshmieldev.

Summary

Adds backgroundImage (linear and radial gradients) to CSS animations and CSS transitions. #10486 added the JS processor and made gradients work in useAnimatedStyle; the CSS engine had no interpolator for the property, so animationName / transitionProperty with backgroundImage threw No interpolator factory found for property: backgroundImage.

Native

  • New value type CSSGradient (Common/cpp/reanimated/CSS/common/values/complex/CSSGradient.{h,cpp}) registered as array({value<CSSGradient>(CSSGradient())}), one interpolator per layer, like boxShadow. It reads and emits the exact shape produced by processBackgroundImage, so React Native parses the animated value with its existing backgroundImage parser on both platforms.
  • 'backgroundImage' is added to DEFAULT_SEPARATELY_INTERPOLATED_NESTED_PROPERTIES, so keyframes are split per layer index.
  • Interpolation rules (from CSS Images 4 §7.4 and §3.5.3 where the spec has an answer):
    • same-type gradients interpolate colors, color stop positions, the angle direction, and the radial size and position per component;
    • a shorter color stop list is padded by repeating its last stop, so red, blue → red, blue, lime animates instead of snapping;
    • missing stop positions are resolved with the CSS color stop fixup (first 0%, last 100%, runs spaced evenly) before interpolation, only when the surrounding stops use percentages; a stop that is null in both keyframes stays null in the output so React Native resolves it;
    • a layer present in only one keyframe interpolates against an empty "none" gradient, i.e. it fades in from or out to transparent;
    • pairs that cannot be interpolated switch at the midpoint of the animation (FALLBACK_INTERPOLATION_THRESHOLD): linear ↔ radial, keyword ↔ angle direction, different keywords, shape mismatch, size keyword ↔ explicit size, px ↔ %, transition hint ↔ color stop at the same index.
  • Layers pair by array index, so a layer that exists in only one keyframe has to be last. Documented in the "Animating background image" section of supported-properties.mdx.
  • The JS processor now rejects non-finite numbers and negative radial sizes in the object form, matching what the CSS string parser already did.

Web

  • processBackgroundImageWeb turns the object form into a CSS linear-gradient() / radial-gradient() string (strings pass through), with the same defaults as the native processor. Browsers treat background-image as a discrete animation type, so on the Web the gradient switches at the midpoint between keyframes; the docs say so.

Related

Test plan

  • Example screen: apps/common-app → CSS → Animations → Animated properties → Others → Background Image (Linear / Radial / Layers tabs). Checked on the Android emulator (Pixel 9 Pro, API 37), the iPhone 17 simulator, and Expo web.

  • Runtime tests (new suite css/backgroundImage.test.tsx, 8 cases; it pins a paused animation at a known progress with a negative animationDelay and reads the parsed native prop back with getViewProp):

    yarn workspace fabric-example runtime-tests --library reanimated --platform android --avd <avd> --only "css animations"

    10/10 on Android (incl. the existing cancellation cases).

  • Jest: yarn workspace react-native-reanimated jest backgroundImage registry (native processor incl. new validation cases, web processor, nested properties registry).

  • yarn workspace react-native-reanimated type:check, type:check:legacy-rn-types, lint:js; clang-format v19 and scripts/validate-common.sh on the C++.

Minimal snippet:

<Animated.View
  style={{
    width: 120,
    height: 120,
    animationName: {
      from: { backgroundImage: 'linear-gradient(0deg, cyan, blue)' },
      to: { backgroundImage: 'linear-gradient(360deg, red, yellow)' },
    },
    animationDuration: '2s',
    animationIterationCount: 'infinite',
    animationTimingFunction: 'linear',
  }}
/>

Expected: the gradient rotates a full turn while its colors blend, on both platforms.

Changelog

  • I added an entry to the Unpublished section of each changed package's CHANGELOG.md, or this PR does not change react-native-reanimated or react-native-worklets.

Copilot AI lite review requested due to automatic review settings September 22, 2026 13:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds native interpolation support for linear and radial backgroundImage gradients and registers a web processor that serializes background images as CSS gradient strings. It also tightens gradient input validation, adds runtime tests and app examples, and updates the supported-properties documentation and changelog.

Priority: ➖ Normal

Change: Feature

Merge Risk: 🔵 Low · up to c495c

Malformed non-finite gradients can still reach native interpolation, and a regression in Web object-form keyframe processing could escape current tests. Both risks are bounded, but the native validation and Web integration coverage should be addressed or accepted before merge.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding background-image support for CSS animations.
Description check ✅ Passed The description directly explains the native and Web backgroundImage changes, interpolation behavior, tests, documentation, and changelog updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 24bc7660-3478-48be-8930-c14d0d61780e

📥 Commits

Reviewing files that changed from the base of the PR and between bd31388 and de23764.

📒 Files selected for processing (14)
  • apps/common-app/runtime-tests/reanimated/suites.ts
  • apps/common-app/runtime-tests/reanimated/tests/css/backgroundImage.test.tsx
  • apps/common-app/src/apps/css/examples/animations/routes/properties/base.ts
  • apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/base/appearance/others/BackgroundImage.tsx
  • apps/common-app/src/apps/css/examples/animations/screens/animatedProperties/base/appearance/others/index.ts
  • docs/docs-reanimated/docs/guides/supported-properties.mdx
  • packages/react-native-reanimated/CHANGELOG.md
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/InterpolatorRegistry.cpp
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/CSSValueVariant.cpp
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/complex/CSSGradient.cpp
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/common/values/complex/CSSGradient.h
  • packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/SimpleValueInterpolator.cpp
  • packages/react-native-reanimated/src/common/style/registry.ts
  • packages/react-native-reanimated/src/css/native/__tests__/registry.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread packages/react-native-reanimated/CHANGELOG.md Outdated
# Conflicts:
#	packages/react-native-reanimated/src/common/web/style/config.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 943dd468-f063-434a-b646-feb3811d5928

📥 Commits

Reviewing files that changed from the base of the PR and between de23764 and d054300.

📒 Files selected for processing (8)
  • docs/docs-reanimated/docs/guides/supported-properties.mdx
  • packages/react-native-reanimated/CHANGELOG.md
  • packages/react-native-reanimated/src/common/style/processors/__tests__/backgroundImage.test.ts
  • packages/react-native-reanimated/src/common/style/processors/backgroundImage.ts
  • packages/react-native-reanimated/src/common/web/style/config.ts
  • packages/react-native-reanimated/src/common/web/style/processors/__tests__/backgroundImage.test.ts
  • packages/react-native-reanimated/src/common/web/style/processors/backgroundImage.ts
  • packages/react-native-reanimated/src/common/web/style/processors/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-native-reanimated/CHANGELOG.md

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment thread packages/react-native-reanimated/src/common/style/processors/backgroundImage.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/react-native-reanimated/src/common/web/style/config.ts (1)

196-196: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add an object-form backgroundImage keyframe integration test.

CSSAnimationsManager sends object keyframes through processKeyframeDefinitions, which uses webPropsBuilder. The parser tests cover only opacity, and the manager tests cover opacity and SVG d. The backgroundImage tests call processBackgroundImageWeb directly.

A regression that removes backgroundImage from PROPERTIES_CONFIG can therefore pass all existing tests while generated @keyframes contain no serialized background-image declaration.

Suggested fix
+    test('builds backgroundImage keyframes with the Web props builder', () => {
+      const backgroundImageKeyframes = {
+        from: {
+          backgroundImage: [
+            {
+              type: 'linear-gradient',
+              direction: 'to right',
+              colorStops: [{ color: 'red' }, { color: 'blue' }],
+            },
+          ],
+        },
+        to: {
+          backgroundImage: [
+            {
+              type: 'linear-gradient',
+              direction: 'to left',
+              colorStops: [{ color: 'red' }, { color: 'blue' }],
+            },
+          ],
+        },
+      };
+
+      manager.update({
+        animationName: backgroundImageKeyframes,
+        animationDuration: 200,
+      } as unknown as ExistingCSSAnimationProperties);
+
+      expect(insertCSSAnimation).toHaveBeenCalledWith(
+        element.style.animationName,
+        'from { background-image: linear-gradient(to right, red, blue) } to { background-image: linear-gradient(to left, red, blue) }'
+      );
+    });
+

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 06ae4cef-b8a2-45c3-90bd-fa84c56f4e73

📥 Commits

Reviewing files that changed from the base of the PR and between c323bc7 and c495c18.

📒 Files selected for processing (1)
  • packages/react-native-reanimated/changelog/bgimg-css.feature.md

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants