Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamic snap points cause flickering of backdrop #436

Open
schiller-manuel opened this issue May 10, 2021 · 20 comments · May be fixed by #562
Open

dynamic snap points cause flickering of backdrop #436

schiller-manuel opened this issue May 10, 2021 · 20 comments · May be fixed by #562
Assignees
Labels
bug Something isn't working v3 Written in Reanimated v2

Comments

@schiller-manuel
Copy link

flashing-backdrop.mp4

Bug

I added a backdrop to the "Dynamic Snap Point" example and experienced flickering of the backdrop when:

  • bottom sheet is mounted
  • content height is increased

Please see the attached screen recording.

The flickering is caused by the opacity of the backdrop which is derived from animatedIndex:

opacity: interpolate(
animatedIndex.value,
[-1, disappearsOnIndex, appearsOnIndex],
[0, 0, opacity],
Extrapolate.CLAMP

When the bottom sheet is mounted, contentHeight is set to 0 and thus the animatedIndex is calculated as 1.
This initializes the backdrop with its target opacity.

When contentHeight is updated in handleOnLayout and thus the snap points change, animatedIndex drops to a value below 1 and then reaches 1. This causes a change in the backdrop's opacity which looks like it's flickering.

LOG  animatedIndex 1
LOG  animatedIndex 0.303923371864135
LOG  animatedIndex 0.44752361701454846
LOG  animatedIndex 0.5614992061630881
LOG  animatedIndex 0.6519616911085544
LOG  animatedIndex 0.7237618123876004
LOG  animatedIndex 0.7807496059801955
LOG  animatedIndex 0.8259808478549384
LOG  animatedIndex 0.8618809080198362
LOG  animatedIndex 0.8903748046205889
LOG  animatedIndex 0.9129904250777997
LOG  animatedIndex 0.9309404549799996
LOG  animatedIndex 0.9451874030349574
LOG  animatedIndex 0.9564952131859606
LOG  animatedIndex 0.9654702279750407
LOG  animatedIndex 0.9725937019024556
LOG  animatedIndex 0.9782476068805629
LOG  animatedIndex 0.9827351142300406
LOG  animatedIndex 0.9862968511437163
LOG  animatedIndex 0.9891238036020468
LOG  animatedIndex 0.9913675572291475
LOG  animatedIndex 0.993148425662441
LOG  animatedIndex 0.994561901872919
LOG  animatedIndex 0.9956837786752039
LOG  animatedIndex 0.9965742128821735
LOG  animatedIndex 0.9972809509724073
LOG  animatedIndex 0.9978418893679171
LOG  animatedIndex 0.9982871064637325
LOG  animatedIndex 0.9986404755064244
LOG  animatedIndex 0.9989209446991162
LOG  animatedIndex 1

When contentHeight is increased, the same behavior is shown:

LOG  animatedIndex 1
LOG  animatedIndex 0.8632851491723874
LOG  animatedIndex 0.891489351466483
LOG  animatedIndex 0.9138750416347815
LOG  animatedIndex 0.9316425755464124
LOG  animatedIndex 0.9457446764953674
LOG  animatedIndex 0.9569375213867082
LOG  animatedIndex 0.9658212882250738
LOG  animatedIndex 0.9728723386287468
LOG  animatedIndex 0.978468760995804
LOG  animatedIndex 0.9829106443525916
LOG  animatedIndex 0.9864361694936971
LOG  animatedIndex 0.9892343806491268
LOG  animatedIndex 0.9914553222963229
LOG  animatedIndex 0.9932180848477181
LOG  animatedIndex 0.9946171903957283
LOG  animatedIndex 0.9957276612081754
LOG  animatedIndex 0.9966090424686902
LOG  animatedIndex 0.9973085952378943
LOG  animatedIndex 0.9978638306340945
LOG  animatedIndex 0.9983045212581616
LOG  animatedIndex 0.9986542976367383
LOG  animatedIndex 0.998931915331168
LOG  animatedIndex 0.9991522606409889
LOG  animatedIndex 0.9993271488278208
LOG  animatedIndex 0.9994659576730858
LOG  animatedIndex 0.9995761303260984
LOG  animatedIndex 0.9996635744186362
LOG  animatedIndex 0.9997329788402937
LOG  animatedIndex 0.9997880651662013
LOG  animatedIndex 0.999831787211542
LOG  animatedIndex 1 

When contentHeight is decreased, animatedIndex stays at 1 and no flickering occurs:

 LOG  animatedIndex 1
 LOG  animatedIndex 1

Environment info

Library Version
@gorhom/bottom-sheet 3.6.4
react-native 0.64.0
react-native-reanimated 2.1.0
react-native-gesture-handler 1.9.0

Steps To Reproduce

  1. set backdropComponent={BottomSheetBackdrop}
  2. modify snap points dynamically
  3. backdrop flickers

Describe what you expected to happen:

no flickering of backdrop

Ideally, animatedIndex would not change when the snap points are modified as done here.
Maybe when the number of snap points stay the same, animatedIndex should stay constant?

Reproducible sample code

I modified the example app by adding the backdrop to the dynamic snap point example, please have a look: schiller-manuel@86b628e

@schiller-manuel schiller-manuel added the bug Something isn't working label May 10, 2021
@gorhom
Copy link
Owner

gorhom commented May 11, 2021

thanks @schiller-manuel for submitting this bug , i will look into this

@gorhom gorhom added the v3 Written in Reanimated v2 label May 11, 2021
@gorhom gorhom self-assigned this May 11, 2021
@benjamin-sweney
Copy link

The below component will prevent the flicker on mount. It is by no means foolproof but works for a single snap point use case.

import React, { FunctionComponent } from 'react';
import { BottomSheetBackdrop, BottomSheetBackdropProps } from '@gorhom/bottom-sheet';
import { useAnimatedReaction, useSharedValue, withTiming } from 'react-native-reanimated';

import { OPEN_ANIMATION_DURATION } from './constants';

const AntiFlickerBottomSheetBackdrop: FunctionComponent<BottomSheetBackdropProps> = ({ animatedIndex, ...rest }) => {
  const adjustedAnimatedIndex = useSharedValue(0);
  const hasOpened = useSharedValue(false);

  useAnimatedReaction(() => animatedIndex.value, (data, prev) => {
    if (prev == null) {
      adjustedAnimatedIndex.value = withTiming(1, { duration: OPEN_ANIMATION_DURATION }, (isFinished) => {
        if (isFinished) hasOpened.value = true;
      });
    }

    if (hasOpened.value) adjustedAnimatedIndex.value = data;
  });

  return <BottomSheetBackdrop animatedIndex={adjustedAnimatedIndex} {...rest} />;
};

export default AntiFlickerBottomSheetBackdrop;

Ensure you also use the below animation config on the bottom sheet itself to align the timing.

const animationConfigs = useBottomSheetTimingConfigs({
  duration: OPEN_ANIMATION_DURATION,
});

@ozasadnyy
Copy link

FYI: still happening on v4

@somebody32
Copy link

And is not only V3 related, that happens in V2 too. One of the possible workarounds is to extend BackdropComponent with a new prop that conditionally turns off interpolation of opacity, and pass this prop when increasing the content height. It is super-hacky but works

@somebody32
Copy link

I just created a PR that fixes this for V2: #562, but I believe that it is going to be easy to migrate it to Reanimated 2 if anyone is interested in doing that

@gorhom
Copy link
Owner

gorhom commented Aug 5, 2021

this should be fixed on 4.0.0-alpha.24, i will look into your PR for v2 @somebody32

@Gutyn
Copy link

Gutyn commented Dec 1, 2022

Seems to still be broken for 4.4.5.

@Ig0reHa
Copy link

Ig0reHa commented Mar 6, 2023

@gorhom any update on this ? And are there going to be a new versions ?

@Ig0reHa
Copy link

Ig0reHa commented Mar 13, 2023

Slightly improved version of the @benjamin-sweney component. It didn't work from the start but when i changed <AntiFlickerBottomSheetBackdrop /> to the <AntiFlickerBottomSheetBackdrop disappearsOnIndex={-1} appearsOnIndex={0} /> and in my <BottomSheetModal index={0} /> it started to work properly.

if (!hasOpened.value && prev != null && prev > data) {
    adjustedAnimatedIndex.value = data;
}

This few lines above fix the bug when the modal is not fully opened and you press on the Backdrop to close it.

AntiFlickerBottomSheetBackdrop.tsx

import React, {FunctionComponent} from 'react';
import {
  BottomSheetBackdrop,
  BottomSheetBackdropProps,
} from '@gorhom/bottom-sheet';
import {
  Easing,
  useAnimatedReaction,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

import {MODAL_LOGIC} from '@/constants/modal';

interface AntiFlickerBottomSheetBackdropProps extends BottomSheetBackdropProps {
  disappearsOnIndex: number;
  appearsOnIndex: number;
}

const AntiFlickerBottomSheetBackdrop: FunctionComponent<
  AntiFlickerBottomSheetBackdropProps
> = ({animatedIndex, ...rest}) => {
  const {disappearsOnIndex, appearsOnIndex} = rest;

  const adjustedAnimatedIndex = useSharedValue(disappearsOnIndex);
  const hasOpened = useSharedValue(false);

  useAnimatedReaction(
    () => animatedIndex.value,
    (data, prev) => {
      if (!hasOpened.value && prev != null && prev > data) {
        adjustedAnimatedIndex.value = data;
      }

      if (prev == null) {
        adjustedAnimatedIndex.value = withTiming(
          appearsOnIndex,
          {
            duration: MODAL_LOGIC.OPEN_ANIMATION_DURATION,
            easing: Easing.out(Easing.exp),
          },
          isFinished => {
            if (isFinished) hasOpened.value = true;
          },
        );
      }

      if (hasOpened.value) adjustedAnimatedIndex.value = data;
    },
  );

  return (
    <BottomSheetBackdrop animatedIndex={adjustedAnimatedIndex} {...rest} />
  );
};

export default AntiFlickerBottomSheetBackdrop;

@mhsfh
Copy link

mhsfh commented Jan 16, 2024

the BottomSheetBackdropl was Flickering when the content height change and unfortunately nothing from the above solutions worked for me so I created my AntiFlickerBottomSheetBackdrop, may it help someone facing the same problem I faced

import {
  BottomSheetBackdrop,
  BottomSheetBackdropProps,
} from '@gorhom/bottom-sheet';
import React, { ComponentProps } from 'react';
import { useAnimatedReaction, useSharedValue } from 'react-native-reanimated';

export const AntiFlickerBottomSheetBackdrop = (
  props: BottomSheetBackdropProps & ComponentProps<typeof BottomSheetBackdrop>,
) => {
  const adjustedAnimatedIndex = useSharedValue(0);
  // when it opening it start from -1 to 0
  // when it closing it start from 0 to -1
  //  0: is open  -1: is close
  // when the error happen it jump directly from 0 to -1
  // when the problem end it jump back from -1 to 0
  useAnimatedReaction(
    () => props.animatedIndex.value,
    (prepared, pre) => {
      if (pre === null) {
        // initial state
        adjustedAnimatedIndex.value = prepared;
        return;
      }

      const jumpForward = prepared - pre === 1;
      const jumpBackward = pre - prepared === 1;

      if (pre !== prepared && !jumpBackward && !jumpForward) {
        adjustedAnimatedIndex.value = prepared;
      }
    },
  );
  return (
    <BottomSheetBackdrop {...props} animatedIndex={adjustedAnimatedIndex} />
  );
};



// usage

    const renderBackdrop: React.FC<BottomSheetBackdropProps> = useCallback(
      (p) => (
        <AntiFlickerBottomSheetBackdrop
          {...p}
          pressBehavior={pressBehavior}
          disappearsOnIndex={-1}
          appearsOnIndex={0}
        />
      ),
      [pressBehavior],
    );
    
    <BottomSheetModal
    keyboardBehavior="interactive"
        keyboardBlurBehavior="restore"
        android_keyboardInputMode="adjustResize"
        enableDynamicSizing
        enablePanDownToClose
        animateOnMount
         backdropComponent={renderBackdrop}
         />

@joshsmith
Copy link
Contributor

@mhsfh having tried your fix, there appears to be an initial flicker of the backdrop for me occasionally on presenting. I haven't been able to figure out a fix for that.

@joshsmith
Copy link
Contributor

@gorhom have you had a chance to look at this again?

@trickeyd
Copy link

+1 - I'm hitting this too.

@minhnndev
Copy link

I don't understand why they don't make things that are simple and that everyone needs instead of making things so complicated that even a lot of people can't use all of the things that they have created.

@TimeForRelax
Copy link

TimeForRelax commented Sep 6, 2024

https://github.com/gorhom/react-native-bottom-sheet/issues/436#issuecomment-1894476861

Great solution, but for avoiding issue with TextInput + autofocus you need listen () => props.animatedPosition.value,. And it should be great!

@TimeForRelax
Copy link

TimeForRelax commented Sep 6, 2024

Guys!
FULLY WORKED SOLUTION!!!

Doesn't care about using snap points or dynamic size here!

Description: Solution only for avoiding problems with backdrop flickering onAndroid or on IOS systems. This working for cases with backdrop permanently present on screen. Also you can update this solution for cases with changeable backdrop which depends on modal position or another flags

import { BottomSheetBackdrop, SCREEN_HEIGHT } from "@gorhom/bottom-sheet"
import { BottomSheetDefaultBackdropProps } from "@gorhom/bottom-sheet/lib/typescript/components/bottomSheetBackdrop/types"
import { memo } from "react"
import { Keyboard, Pressable } from "react-native"
import { useAnimatedReaction, useSharedValue } from "react-native-reanimated"
import { ANDROID_NAVIGATION_BAR_HEIGHT } from "shared/config"

interface SheetBackdropProps extends BottomSheetDefaultBackdropProps {}

export const ModalBackdrop = memo((props: SheetBackdropProps) => {
  const adjustedAnimatedIndex = useSharedValue(0)

  useAnimatedReaction(
    () => props.animatedPosition.value - 10,
    (current, prev) => {
      const viewableHeight = SCREEN_HEIGHT - ANDROID_NAVIGATION_BAR_HEIGHT

      if (prev === null) {
        adjustedAnimatedIndex.value = 0
        return
      }

      if (current >= viewableHeight) {
        adjustedAnimatedIndex.value = 1
      } else {
        adjustedAnimatedIndex.value = 0
      }
    }
  )

  return (
    <BottomSheetBackdrop
      {...props}
      opacity={0.3}
      appearsOnIndex={0}
      disappearsOnIndex={-1}
      enableTouchThrough={false}
      animatedIndex={adjustedAnimatedIndex}
      pressBehavior="close">
      <Pressable
        onPress={Keyboard.dismiss}
        style={{ flex: 1 }}
      />
    </BottomSheetBackdrop>
  )
})

@TimeForRelax
Copy link

@RUPESH1439
Copy link

I was able to fix this issue on v(4.6.4) by creating a patch:

diff --git a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx
index cd6ea6b..3c830f3 100644
--- a/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx
+++ b/node_modules/@gorhom/bottom-sheet/src/components/bottomSheet/BottomSheet.tsx
@@ -527,6 +527,10 @@ const BottomSheetComponent = forwardRef<BottomSheet, BottomSheetProps>(
         animatedAnimationSource.value === ANIMATION_SOURCE.SNAP_POINT_CHANGE &&
         animatedAnimationState.value === ANIMATION_STATE.RUNNING
       ) {
+        /*
+          This fixes flickering issue when the sheet is animating to a new snap point
+        */
+        return Math.max(animatedCurrentIndex.value, currentIndex);
         return animatedNextPositionIndex.value;
       }

@PiotrWszolek
Copy link

PiotrWszolek commented Oct 23, 2024

@RUPESH1439 Thanks! It works, I hope it doesn't break something :)

@Rag0n
Copy link

Rag0n commented Oct 30, 2024

The issue is still relevant for v5.

I'm using custom backdrop component and for quick workaround I used next solution:

const fixedAnimatedIndex = useSharedValue(animatedIndex.value)

useAnimatedReaction(
  () => ({ index: animatedIndex.value, position: animatedPosition.value }),
  (current, previous) => {
    if (previous && Math.abs(current.position - previous.position) < 0.01) {
      return
    }
    fixedAnimatedIndex.value = current.index
  }
)

const containerAnimatedStyle = useAnimatedStyle(() => ({
  opacity: interpolate(
    fixedAnimatedIndex.value,
    [-1, disappearsOnIndex, appearsOnIndex],
    [0, 0, opacity],
    Extrapolate.CLAMP
  ),
}))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working v3 Written in Reanimated v2
Projects
None yet
Development

Successfully merging a pull request may close this issue.