Skip to content

[Bug]: Default spring keeps running ~200 ms after the sheet has settled on Reanimated 4 (restDisplacementThreshold / restSpeedThreshold are ignored) #2799

Open

Description

@slacki123

Version

v5

Reanimated Version

v3 (the form has no v4 option: seen on Reanimated 4.7.1, and on 4.1.1 in the snack)

Gesture Handler Version

v2 (the form has no v3 option: seen on Gesture Handler 3.3.0, and on 2.28 in the snack)

Platforms

iOS, Web

What happened?

On Reanimated 4 the library's default spring takes about 680 ms to finish, although the sheet is within a pixel of its target after about 430 to 500 ms. Until the spring finishes, onChange / onClose / onDismiss have not fired, a modal has not unmounted, and a BottomSheetBackdrop still takes touches (its touchability follows animatedIndex reaching disappearsOnIndex). So a tap on the screen right after a sheet has visibly closed is swallowed.

Cause. The default spring in src/constants.ts (the default on iOS, and on web since v5.2.0) is told when to stop with restDisplacementThreshold: 10 and restSpeedThreshold: 10. Reanimated 4 removed both options (migration guide): withSpring now stops by a relative energyThreshold (default 6e-9) and silently ignores the two old keys. The same two keys are in src/utilities/getKeyboardAnimationConfigs.ts (the keyboard easing). Both are unchanged in v5.2.14 and on master.

Measured in the snack below (web, Reanimated 4.1.1), time from snapToIndex(0) / close() to onChange:

animationConfigs onChange fires after
the library's spring, as in src/constants.ts 683 to 687 ms
the same spring with energyThreshold: 1e-5 459 to 465 ms

Calculated by stepping Reanimated's own spring functions with the library's spring at 60 fps, for a travel of 300 to 800 px:

within 1 px of the target animation ends
Reanimated 3.19.1 (ends below 10 px and 10 px/s) 433 to 500 ms 467 to 533 ms
Reanimated 4.7.1 (default energyThreshold) 433 to 500 ms 683 ms
Reanimated 4.7.1 with energyThreshold: 1e-5 433 to 467 ms 467 ms

Android is not affected: its default is the 250 ms timing.

Workaround. Pass the same spring with an explicit threshold:

<BottomSheet
  animationConfigs={{
    damping: 500,
    stiffness: 1000,
    mass: 3,
    overshootClamping: true,
    energyThreshold: 1e-5,
  }}
/>

This does not reach keyboard-driven moves, which take their config from getKeyboardAnimationConfigs.

Suggested fix. In both files, replace the two removed keys with energyThreshold when running on Reanimated 4.

Reproduction steps

  • Open the snack on web (or iOS).
  • Press "Next animationConfigs" once, so it reads "library spring". Press "Open", then "Close": onChange fires about 680 ms after each call.
  • Press "Next animationConfigs" again ("spring + energyThreshold") and repeat: about 460 ms, with the same visible motion.
  • On iOS the first setting, "library default", shows the same 680 ms. On web it only does from v5.2.0, where the spring became the web default; the snack is on v5.1.7 (see the note below).

Reproduction sample

snack.expo.dev: gorhom spring ends late on Reanimated 4

Note on the snack's version: it pins v5.1.7 because Snack cannot bundle v5.1.8 or later. Its bundler answers Snack does not support non-semver version references, found: "react-native-reanimated" = ">=3.16.0 || >=4.0.0-", so no release that declares Reanimated 4 support loads in Snack at the moment. The spring config is the same in v5.1.7 and v5.2.14.

Relevant log output

No response

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions