Skip to content

support native interpolation easing (#57237)#57237

Open
zeyap wants to merge 3 commits into
react:mainfrom
zeyap:export-D108760799
Open

support native interpolation easing (#57237)#57237
zeyap wants to merge 3 commits into
react:mainfrom
zeyap:export-D108760799

Conversation

@zeyap

@zeyap zeyap commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary:

Changelog:

[General] [Added] - support native driven AnimatedValue interpolation easing

Interpolation with easing on AnimatedValue was not supported with native driver, example like below simply doesn't work, because easing function is called on JS thread. This is not solved with shared backend.

const progress = useAnimatedValue(0);

const easedX = progress.interpolate({
    inputRange: [0, 1],
    outputRange: [0, DISTANCE],
    easing: Easing.inOut(Easing.cubic),
  });

// case 1: driven by Animated.timing
Animated.timing(progress, {
      toValue: 1,
      duration: 1500,
      useNativeDriver: true,
    }).start();

// case 2: driven by native event
<Animated.ScrollView
    onScroll={Animated.event(
      [{nativeEvent: {contentOffset: {y: scrollY}}}],
      {useNativeDriver: true},
    )}>
</Animated.ScrollView>

// JS error: Interpolation property 'easing' is not supported by native animated module


How it works

The JS easing function is sampled and baked into the native interpolation config as a compact set of non-uniform [position, value] stops — the same representation as CSS linear():

  • JS (AnimatedInterpolation): densely samples the easing curve, then simplifies it with Ramer–Douglas–Peucker into non-uniform stops. The simplification tolerance is derived from the interpolation's output span so the on-screen error stays ~sub-pixel — flat curves collapse to a few stops, curvy ones keep more (bounded by the dense-sample budget). easingStops is emitted only when an easing is set (and is not the linear identity).
  • Native (InterpolationAnimatedNode): applies the stops to each segment's normalized ratio via binary search + linear interpolation. easing is now an accepted interpolation param, so the "not supported" error is gone.

Overshoot is preserved: easings that leave [0, 1] (e.g. Easing.back, Easing.elastic) keep their excursion even under extrapolate: 'clamp', matching the JS driver — clamp/identity only apply to out-of-range input, not to the easing's own excursion. Works for all output types since easing acts on the normalized ratio, not the output values.

Known limitation

Color/platform_color interpolation under an overshoot easing can push channel values outside [0, 255], which currently wrap on the native uint8_t cast (JS instead emits out-of-gamut). Not addressed here.

This is sometimes exchangeable with the native driven easing on Animated.timing. But one unique use case is when you need to interpolate native event driven animated value (e.g. scroll offset from scroll event) differently to derive multiple animation values. I find it impossible to replace with another existing Animated API for this use case.

Differential Revision: D108760799

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 16, 2026
@meta-codesync

meta-codesync Bot commented Jun 16, 2026

Copy link
Copy Markdown

@zeyap has exported this pull request. If you are a Meta employee, you can view the originating Diff in D108760799.

@meta-codesync meta-codesync Bot changed the title support native interpolation easing support native interpolation easing (#57237) Jun 17, 2026
zeyap added a commit to zeyap/react-native that referenced this pull request Jun 17, 2026
Summary:

## Changelog:

[General] [Added] - support native driven AnimatedValue interpolation easing

Interpolation with easing on AnimatedValue was not supported with native driver, example like below simply doesn't work, because easing function is called on JS thread. This is not solved with shared backend.

```
const progress = useAnimatedValue(0);

const easedX = progress.interpolate({
    inputRange: [0, 1],
    outputRange: [0, DISTANCE],
    easing: Easing.inOut(Easing.cubic),
  });

Animated.timing(progress, {
      toValue: 1,
      duration: 1500,
      useNativeDriver: true,
    }).start();

// JS error: Interpolation property 'easing' is not supported by native animated module
```

## How it works

The JS `easing` function is sampled and baked into the native interpolation config as a compact set of non-uniform `[position, value]` stops — the same representation as CSS `linear()`:

- JS (`AnimatedInterpolation`): densely samples the easing curve, then simplifies it with Ramer–Douglas–Peucker into non-uniform stops. The simplification tolerance is derived from the interpolation's output span so the on-screen error stays ~sub-pixel — flat curves collapse to a few stops, curvy ones keep more (bounded by the dense-sample budget). `easingStops` is emitted only when an `easing` is set (and is not the linear identity).
- Native (`InterpolationAnimatedNode`): applies the stops to each segment's normalized ratio via binary search + linear interpolation. `easing` is now an accepted interpolation param, so the "not supported" error is gone.

Overshoot is preserved: easings that leave `[0, 1]` (e.g. `Easing.back`, `Easing.elastic`) keep their excursion even under `extrapolate: 'clamp'`, matching the JS driver — `clamp`/`identity` only apply to out-of-range input, not to the easing's own excursion. Works for all output types since easing acts on the normalized ratio, not the output values.

## Known limitation

Color/platform_color interpolation under an overshoot easing can push channel values outside `[0, 255]`, which currently wrap on the native `uint8_t` cast (JS instead emits out-of-gamut). Not addressed here.

Differential Revision: D108760799
@zeyap zeyap force-pushed the export-D108760799 branch from 1a0f22b to 859b32d Compare June 17, 2026 15:44
zeyap added 3 commits June 17, 2026 08:52
…ForceNativeDriver" (react#57250)

Summary:

## Changelog:

[General] [Added] - remove `useNativeDriver` under featureflag animatedForceNativeDriver

When `animatedForceNativeDriver` is enabled, it forces `useNativeDriver` to `true` for all Animated animations and events, overriding the config (explicit `false` set by user will be no-op). Has no effect unless the shared animated backend is enabled, which is required to support native driver for all props.

When calling `NativeAnimatedHelper.isNativeDriverForced`, do null check first for backward compatibility in rn-macos

Also using this flag to gate the js animation logic that could be cleaned up when this path is fully working.

Reviewed By: javache, bmsdave

Differential Revision: D108880837
Summary:
The RNTester integration build only proves that pods work *together* inside one workspace — it does not build every pod from source, and pods outside RNTester's dependency closure are never validated on their own. As a result, a podspec can ship with a defect that only shows up when the pod is built in isolation: a malformed compiler flag, or a header/dependency that the pod uses but never declares (which surfaces as a `'yoga/Yoga.h' file not found` style build error for open-source consumers).

This adds a pull-request job that runs `pod lib lint` on the `*.podspec` files a change actually touches. Each pod is linted in isolation, so a dependency that is used-but-not-declared fails to resolve and the job fails. Because React Native's pods are not published to a spec repo, the script exposes every local podspec via `--include-podspecs`, letting the linter resolve inter-pod dependencies from the checkout.

The job is scoped to changed podspecs via `dorny/paths-filter`, so PRs that touch no podspec do no extra work.

Changelog:
[Internal] - Add CI linting for changed iOS podspecs

Differential Revision: D108889958
Summary:

## Changelog:

[General] [Added] - support native driven AnimatedValue interpolation easing

Interpolation with easing on AnimatedValue was not supported with native driver, example like below simply doesn't work, because easing function is called on JS thread. This is not solved with shared backend.

```
const progress = useAnimatedValue(0);

const easedX = progress.interpolate({
    inputRange: [0, 1],
    outputRange: [0, DISTANCE],
    easing: Easing.inOut(Easing.cubic),
  });

// case 1: driven by Animated.timing
Animated.timing(progress, {
      toValue: 1,
      duration: 1500,
      useNativeDriver: true,
    }).start();

// case 2: driven by native event
<Animated.ScrollView
    onScroll={Animated.event(
      [{nativeEvent: {contentOffset: {y: scrollY}}}],
      {useNativeDriver: true},
    )}>
</Animated.ScrollView>

// JS error: Interpolation property 'easing' is not supported by native animated module


```

## How it works

The JS `easing` function is sampled and baked into the native interpolation config as a compact set of non-uniform `[position, value]` stops — the same representation as CSS `linear()`:

- JS (`AnimatedInterpolation`): densely samples the easing curve, then simplifies it with Ramer–Douglas–Peucker into non-uniform stops. The simplification tolerance is derived from the interpolation's output span so the on-screen error stays ~sub-pixel — flat curves collapse to a few stops, curvy ones keep more (bounded by the dense-sample budget). `easingStops` is emitted only when an `easing` is set (and is not the linear identity).
- Native (`InterpolationAnimatedNode`): applies the stops to each segment's normalized ratio via binary search + linear interpolation. `easing` is now an accepted interpolation param, so the "not supported" error is gone.

Overshoot is preserved: easings that leave `[0, 1]` (e.g. `Easing.back`, `Easing.elastic`) keep their excursion even under `extrapolate: 'clamp'`, matching the JS driver — `clamp`/`identity` only apply to out-of-range input, not to the easing's own excursion. Works for all output types since easing acts on the normalized ratio, not the output values.

## Known limitation

Color/platform_color interpolation under an overshoot easing can push channel values outside `[0, 255]`, which currently wrap on the native `uint8_t` cast (JS instead emits out-of-gamut). Not addressed here.

This is sometimes exchangeable with the native driven easing on Animated.timing. But one unique use case is when you need to interpolate native event driven animated value (e.g. scroll offset from scroll event) differently to derive multiple animation values. I find it impossible to replace with another existing Animated API for this use case.

Differential Revision: D108760799
@zeyap zeyap force-pushed the export-D108760799 branch from 859b32d to d8166de Compare June 17, 2026 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant