Skip to content

Commit 9cf30b6

Browse files
zeyapmeta-codesync[bot]
authored andcommitted
(Redo D108193641) remove useNativeDriver under featureflag animatedForceNativeDriver" (#57250)
Summary: Pull Request resolved: #57250 ## 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 fbshipit-source-id: 2a061ddef3aa49893b88d81caa7f1fdbf28f7842
1 parent fe53279 commit 9cf30b6

10 files changed

Lines changed: 155 additions & 7 deletions

File tree

packages/react-native/Libraries/Animated/AnimatedImplementation.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {DecayAnimationConfig} from './animations/DecayAnimation';
2020
import type {SpringAnimationConfig} from './animations/SpringAnimation';
2121
import type {TimingAnimationConfig} from './animations/TimingAnimation';
2222

23+
import NativeAnimatedHelper from '../../src/private/animated/NativeAnimatedHelper';
2324
import {AnimatedEvent, attachNativeEventImpl} from './AnimatedEvent';
2425
import DecayAnimation from './animations/DecayAnimation';
2526
import SpringAnimation from './animations/SpringAnimation';
@@ -200,7 +201,11 @@ const springImpl = function (
200201
},
201202

202203
_isUsingNativeDriver: function (): boolean {
203-
return config.useNativeDriver || false;
204+
return (
205+
NativeAnimatedHelper.isNativeDriverForced?.() ||
206+
config.useNativeDriver ||
207+
false
208+
);
204209
},
205210
}
206211
);
@@ -254,7 +259,11 @@ const timingImpl = function (
254259
},
255260

256261
_isUsingNativeDriver: function (): boolean {
257-
return config.useNativeDriver || false;
262+
return (
263+
NativeAnimatedHelper.isNativeDriverForced?.() ||
264+
config.useNativeDriver ||
265+
false
266+
);
258267
},
259268
}
260269
);
@@ -296,7 +305,11 @@ const decayImpl = function (
296305
},
297306

298307
_isUsingNativeDriver: function (): boolean {
299-
return config.useNativeDriver || false;
308+
return (
309+
NativeAnimatedHelper.isNativeDriverForced?.() ||
310+
config.useNativeDriver ||
311+
false
312+
);
300313
},
301314
}
302315
);

packages/react-native/Libraries/Animated/NativeAnimatedAllowlist.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,42 @@ const SUPPORTED_STYLES: {[string]: true} = {
7878
top: true,
7979
/* flex */
8080
flex: true,
81+
flexGrow: true,
82+
flexShrink: true,
83+
flexBasis: true,
84+
aspectRatio: true,
85+
/* margin */
86+
margin: true,
87+
marginLeft: true,
88+
marginRight: true,
89+
marginTop: true,
90+
marginBottom: true,
91+
marginStart: true,
92+
marginEnd: true,
93+
marginHorizontal: true,
94+
marginVertical: true,
95+
/* padding */
96+
padding: true,
97+
paddingLeft: true,
98+
paddingRight: true,
99+
paddingTop: true,
100+
paddingBottom: true,
101+
paddingStart: true,
102+
paddingEnd: true,
103+
paddingHorizontal: true,
104+
paddingVertical: true,
105+
/* border width */
106+
borderWidth: true,
107+
borderLeftWidth: true,
108+
borderRightWidth: true,
109+
borderTopWidth: true,
110+
borderBottomWidth: true,
111+
borderStartWidth: true,
112+
borderEndWidth: true,
113+
/* gap */
114+
gap: true,
115+
rowGap: true,
116+
columnGap: true,
81117
}
82118
: {}),
83119
};

packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,65 @@ import {Animated, View, useAnimatedValue} from 'react-native';
2121
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
2222
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2323

24+
// marginLeft (and the other margin props) are only on the native animated
25+
// allowlist when the shared backend is enabled. This test deliberately does NOT
26+
// call allowStyleProp('marginLeft') — it verifies the prop is supported natively
27+
// out of the box under useSharedAnimatedBackend.
28+
test('animate marginLeft layout prop', () => {
29+
const viewRef = createRef<HostInstance>();
30+
31+
let _animatedMarginLeft;
32+
let _marginLeftAnimation;
33+
34+
function MyApp() {
35+
const animatedMarginLeft = useAnimatedValue(0);
36+
_animatedMarginLeft = animatedMarginLeft;
37+
return (
38+
<Animated.View
39+
ref={viewRef}
40+
style={[
41+
{
42+
width: 100,
43+
height: 100,
44+
marginLeft: animatedMarginLeft,
45+
},
46+
]}
47+
/>
48+
);
49+
}
50+
51+
const root = Fantom.createRoot();
52+
53+
Fantom.runTask(() => {
54+
root.render(<MyApp />);
55+
});
56+
57+
Fantom.runTask(() => {
58+
_marginLeftAnimation = Animated.timing(_animatedMarginLeft, {
59+
toValue: 100,
60+
duration: 200,
61+
useNativeDriver: true,
62+
}).start();
63+
});
64+
65+
Fantom.unstable_produceFramesForDuration(100);
66+
67+
expect(root.getRenderedOutput({props: ['marginLeft']}).toJSX()).toEqual(
68+
<rn-view marginLeft="50" />,
69+
);
70+
71+
Fantom.unstable_produceFramesForDuration(100);
72+
73+
// TODO: this shouldn't be necessary since animation should be stopped after duration
74+
Fantom.runTask(() => {
75+
_marginLeftAnimation?.stop();
76+
});
77+
78+
expect(root.getRenderedOutput({props: ['marginLeft']}).toJSX()).toEqual(
79+
<rn-view marginLeft="100" />,
80+
);
81+
});
82+
2483
test('animated opacity', () => {
2584
let _opacity;
2685
let _opacityAnimation;

packages/react-native/Libraries/Animated/animations/Animation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export default class Animation {
7070
previousAnimation: ?Animation,
7171
animatedValue: AnimatedValue,
7272
): void {
73+
// TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out
7374
if (!this._useNativeDriver && animatedValue.__isNative === true) {
7475
throw new Error(
7576
'Attempting to run JS driven animation on animated node ' +

packages/react-native/Libraries/Animated/animations/DecayAnimation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export default class DecayAnimation extends Animation {
8585
this._startTime = Date.now();
8686

8787
const useNativeDriver = this.__startAnimationIfNative(animatedValue);
88+
// TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out
8889
if (!useNativeDriver) {
8990
this._animationFrame = requestAnimationFrame(() => this.onUpdate());
9091
}

packages/react-native/Libraries/Animated/animations/SpringAnimation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ export default class SpringAnimation extends Animation {
225225

226226
const start = () => {
227227
const useNativeDriver = this.__startAnimationIfNative(animatedValue);
228+
// TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out
228229
if (!useNativeDriver) {
229230
this.onUpdate();
230231
}

packages/react-native/Libraries/Animated/animations/TimingAnimation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export default class TimingAnimation extends Animation {
129129
this._startTime = Date.now();
130130

131131
const useNativeDriver = this.__startAnimationIfNative(animatedValue);
132+
// TODO: T274006331 - Remove js-only animation once shared backend is fully rolled out
132133
if (!useNativeDriver) {
133134
// Animations that sometimes have 0 duration and sometimes do not
134135
// still need to use the native driver when duration is 0 so as to

packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,17 @@ const definitions: FeatureFlagDefinitions = {
959959
},
960960
ossReleaseStage: 'none',
961961
},
962+
animatedForceNativeDriver: {
963+
defaultValue: false,
964+
metadata: {
965+
dateAdded: '2026-06-10',
966+
description:
967+
'When enabled, forces `useNativeDriver` to `true` for all Animated animations and events, overriding the config (including an explicit `false`). Has no effect unless the shared animated backend is enabled, which is required to support native driver for all props.',
968+
expectedReleaseValue: true,
969+
purpose: 'experimentation',
970+
},
971+
ossReleaseStage: 'none',
972+
},
962973
animatedShouldDebounceQueueFlush: {
963974
defaultValue: false,
964975
metadata: {

packages/react-native/src/private/animated/NativeAnimatedHelper.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,17 +417,35 @@ function assertNativeAnimatedModule(): void {
417417

418418
let _warnedMissingNativeAnimated = false;
419419

420+
// Whether the native driver should be forced on for every animation, overriding
421+
// the config (including an explicit `useNativeDriver: false`). This is only safe
422+
// when the shared animated backend is enabled — that backend is what makes every
423+
// prop drivable natively. Forcing native without it would break animations of
424+
// props the legacy native driver doesn't support.
425+
function isNativeDriverForced(): boolean {
426+
return (
427+
ReactNativeFeatureFlags.animatedForceNativeDriver() &&
428+
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() &&
429+
// eslint-disable-next-line
430+
ReactNativeFeatureFlags.useSharedAnimatedBackend()
431+
);
432+
}
433+
420434
function shouldUseNativeDriver(
421435
config: Readonly<{...AnimationConfig, ...}> | EventConfig<unknown>,
422436
): boolean {
423-
if (config.useNativeDriver == null) {
437+
const forceNativeDriver = isNativeDriverForced();
438+
439+
if (config.useNativeDriver == null && !forceNativeDriver) {
424440
console.warn(
425441
'Animated: `useNativeDriver` was not specified. This is a required ' +
426442
'option and must be explicitly set to `true` or `false`',
427443
);
428444
}
429445

430-
if (config.useNativeDriver === true && !NativeAnimatedModule) {
446+
const useNativeDriver = forceNativeDriver || config.useNativeDriver === true;
447+
448+
if (useNativeDriver === true && !NativeAnimatedModule) {
431449
if (process.env.NODE_ENV !== 'test') {
432450
if (!_warnedMissingNativeAnimated) {
433451
console.warn(
@@ -443,7 +461,7 @@ function shouldUseNativeDriver(
443461
return false;
444462
}
445463

446-
return config.useNativeDriver || false;
464+
return useNativeDriver;
447465
}
448466

449467
function transformDataType(value: number | string): number | string {
@@ -469,6 +487,7 @@ export default {
469487
assertNativeAnimatedModule,
470488
generateNewAnimationId,
471489
generateNewNodeTag,
490+
isNativeDriverForced,
472491
// $FlowExpectedError[unsafe-getters-setters] - unsafe getter lint suppression
473492
// $FlowExpectedError[missing-type-arg] - unsafe getter lint suppression
474493
get nativeEventEmitter(): NativeEventEmitter {

packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<9665e3af57529f1b50d02c79e7a869eb>>
7+
* @generated SignedSource<<68e9dbd18bfcb5e7d5cad27d8663ce66>>
88
* @flow strict
99
* @noformat
1010
*/
@@ -30,6 +30,7 @@ import {
3030
export type ReactNativeFeatureFlagsJsOnly = Readonly<{
3131
jsOnlyTestFlag: Getter<boolean>,
3232
animatedDeferStartOfTimingAnimations: Getter<boolean>,
33+
animatedForceNativeDriver: Getter<boolean>,
3334
animatedShouldDebounceQueueFlush: Getter<boolean>,
3435
animatedShouldSyncValueBeforeStartCallback: Getter<boolean>,
3536
animatedShouldUseSingleOp: Getter<boolean>,
@@ -145,6 +146,11 @@ export const jsOnlyTestFlag: Getter<boolean> = createJavaScriptFlagGetter('jsOnl
145146
*/
146147
export const animatedDeferStartOfTimingAnimations: Getter<boolean> = createJavaScriptFlagGetter('animatedDeferStartOfTimingAnimations', false);
147148

149+
/**
150+
* When enabled, forces `useNativeDriver` to `true` for all Animated animations and events, overriding the config (including an explicit `false`). Has no effect unless the shared animated backend is enabled, which is required to support native driver for all props.
151+
*/
152+
export const animatedForceNativeDriver: Getter<boolean> = createJavaScriptFlagGetter('animatedForceNativeDriver', false);
153+
148154
/**
149155
* Enables an experimental flush-queue debouncing in Animated.js.
150156
*/

0 commit comments

Comments
 (0)