From 77f742a300e7b32305fa1f7481c9d56d065d7d1b Mon Sep 17 00:00:00 2001 From: ben cripps Date: Fri, 4 Sep 2026 15:04:11 -0500 Subject: [PATCH] Disable Android Image fadeDuration on native html.img RN fades images in over 300ms on Android by default, which is not a web behavior. Force fadeDuration to 0 so html.img matches web; CSS transitions can still animate opacity. --- .../src/native/modules/createStrictDOMImageComponent.js | 4 ++++ packages/react-strict-dom/src/types/renderer.native.js | 1 + .../tests/compat/__snapshots__/compat-test.native.js.snap | 1 + .../tests/html/__snapshots__/html-test.js.snap-native | 5 +++++ .../tests/html/__snapshots__/html-test.native.js.snap | 2 ++ packages/react-strict-dom/tests/html/html-test.native.js | 8 ++++++++ 6 files changed, 21 insertions(+) diff --git a/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js b/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js index fc0fbbbf..4e67672a 100644 --- a/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js +++ b/packages/react-strict-dom/src/native/modules/createStrictDOMImageComponent.js @@ -85,6 +85,10 @@ function applyImageProps( // Component-specific props + // RN Image fades in over 300ms on Android by default. That is not a web + // behavior, so disable it. CSS transitions can still animate opacity. + nativeProps.fadeDuration = 0; + nativeProps.ref = elementRef; } diff --git a/packages/react-strict-dom/src/types/renderer.native.js b/packages/react-strict-dom/src/types/renderer.native.js index 4f91a652..165996d9 100644 --- a/packages/react-strict-dom/src/types/renderer.native.js +++ b/packages/react-strict-dom/src/types/renderer.native.js @@ -70,6 +70,7 @@ type ReactNativeProps = { disabled?: ?boolean, editable?: TextInputProps['editable'], enterKeyHint?: TextInputProps['enterKeyHint'], + fadeDuration?: ImageProps['fadeDuration'], focusable?: ?boolean, height?: ImageProps['height'], importantForAccessibility?: 'no-hide-descendants', diff --git a/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap b/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap index 7e6b6b79..e188bc02 100644 --- a/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap +++ b/packages/react-strict-dom/tests/compat/__snapshots__/compat-test.native.js.snap @@ -15,6 +15,7 @@ exports[` "as" equals "div": as=div 1`] = ` exports[` "as" equals "img": as=img 1`] = ` "i" supports inline event handlers 1`] = ` exports[` "img" default rendering 1`] = ` "img" default rendering 1`] = ` exports[` "img" ignores and warns about unsupported attributes 1`] = ` "img" supports additional image attributes 1`] = ` Alt text "img" supports global attributes 1`] = ` } } accessibilityViewIsModal={true} + fadeDuration={0} focusable={true} importantForAccessibility="no-hide-descendants" nativeID="some-id" @@ -3159,6 +3163,7 @@ exports[` "img" supports global attributes 1`] = ` exports[` "img" supports inline event handlers 1`] = ` (native polyfills) polyfills: layout default flex layout: flex exports[` (native polyfills) polyfills: props "src" prop with loading props 1`] = ` (native polyfills) polyfills: props "src" prop with load exports[` (native polyfills) polyfills: props "srcSet" prop with loading props 1`] = ` (native polyfills)', () => { expect(root.toJSON()).toMatchSnapshot(); }); + test('disables Android fadeDuration default', () => { + let root; + act(() => { + root = create(); + }); + expect(root.toJSON().props.fadeDuration).toBe(0); + }); + test('"onError" and "onLoad" prop', () => { const onError = jest.fn(); const onLoad = jest.fn();