Skip to content

Commit 7de48e6

Browse files
zeyapmeta-codesync[bot]
authored andcommitted
remove useNativeDriver under featureflag animatedForceNativeDriver (#57211)
Summary: Pull Request resolved: #57211 ## 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. Also using this flag to gate the js animation logic that could be cleaned up when this path is fully working. Reviewed By: javache Differential Revision: D108193641
1 parent a160cac commit 7de48e6

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
@@ -981,6 +981,17 @@ const definitions: FeatureFlagDefinitions = {
981981
},
982982
ossReleaseStage: 'none',
983983
},
984+
animatedForceNativeDriver: {
985+
defaultValue: false,
986+
metadata: {
987+
dateAdded: '2026-06-10',
988+
description:
989+
'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.',
990+
expectedReleaseValue: true,
991+
purpose: 'experimentation',
992+
},
993+
ossReleaseStage: 'none',
994+
},
984995
animatedShouldDebounceQueueFlush: {
985996
defaultValue: false,
986997
metadata: {

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

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

407407
let _warnedMissingNativeAnimated = false;
408408

409+
// Whether the native driver should be forced on for every animation, overriding
410+
// the config (including an explicit `useNativeDriver: false`). This is only safe
411+
// when the shared animated backend is enabled — that backend is what makes every
412+
// prop drivable natively. Forcing native without it would break animations of
413+
// props the legacy native driver doesn't support.
414+
function isNativeDriverForced(): boolean {
415+
return (
416+
ReactNativeFeatureFlags.animatedForceNativeDriver() &&
417+
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() &&
418+
// eslint-disable-next-line
419+
ReactNativeFeatureFlags.useSharedAnimatedBackend()
420+
);
421+
}
422+
409423
function shouldUseNativeDriver(
410424
config: Readonly<{...AnimationConfig, ...}> | EventConfig<unknown>,
411425
): boolean {
412-
if (config.useNativeDriver == null) {
426+
const forceNativeDriver = isNativeDriverForced();
427+
428+
if (config.useNativeDriver == null && !forceNativeDriver) {
413429
console.warn(
414430
'Animated: `useNativeDriver` was not specified. This is a required ' +
415431
'option and must be explicitly set to `true` or `false`',
416432
);
417433
}
418434

419-
if (config.useNativeDriver === true && !NativeAnimatedModule) {
435+
const useNativeDriver = forceNativeDriver || config.useNativeDriver === true;
436+
437+
if (useNativeDriver === true && !NativeAnimatedModule) {
420438
if (process.env.NODE_ENV !== 'test') {
421439
if (!_warnedMissingNativeAnimated) {
422440
console.warn(
@@ -432,7 +450,7 @@ function shouldUseNativeDriver(
432450
return false;
433451
}
434452

435-
return config.useNativeDriver || false;
453+
return useNativeDriver;
436454
}
437455

438456
function transformDataType(value: number | string): number | string {
@@ -458,6 +476,7 @@ export default {
458476
assertNativeAnimatedModule,
459477
generateNewAnimationId,
460478
generateNewNodeTag,
479+
isNativeDriverForced,
461480
// $FlowExpectedError[unsafe-getters-setters] - unsafe getter lint suppression
462481
// $FlowExpectedError[missing-type-arg] - unsafe getter lint suppression
463482
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<<785d84f617e6b1870c3ff1eeed9f1c66>>
7+
* @generated SignedSource<<2aaa9f49f9d072aca935862bf5da1630>>
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>,
@@ -147,6 +148,11 @@ export const jsOnlyTestFlag: Getter<boolean> = createJavaScriptFlagGetter('jsOnl
147148
*/
148149
export const animatedDeferStartOfTimingAnimations: Getter<boolean> = createJavaScriptFlagGetter('animatedDeferStartOfTimingAnimations', false);
149150

151+
/**
152+
* 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.
153+
*/
154+
export const animatedForceNativeDriver: Getter<boolean> = createJavaScriptFlagGetter('animatedForceNativeDriver', false);
155+
150156
/**
151157
* Enables an experimental flush-queue debouncing in Animated.js.
152158
*/

0 commit comments

Comments
 (0)