react-native-bottom-sheet
react-native-bottom-sheet copied to clipboard
dynamic snap points cause flickering of backdrop
https://user-images.githubusercontent.com/6340397/117680607-9ca02200-b1b1-11eb-851b-f30ab92622b6.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
:
https://github.com/gorhom/react-native-bottom-sheet/blob/b5a6c659e2fd4a8ead5857d3f22c55c47f4fd82e/src/components/bottomSheetBackdrop/BottomSheetBackdrop.tsx#L77-L81
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
- set
backdropComponent={BottomSheetBackdrop}
- modify snap points dynamically
- 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: https://github.com/schiller-manuel/react-native-bottom-sheet/commit/86b628ec8c3a7f462346ecfadc461c85adcd0bbb
thanks @schiller-manuel for submitting this bug , i will look into this
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,
});
FYI: still happening on v4
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
I just created a PR that fixes this for V2: https://github.com/gorhom/react-native-bottom-sheet/pull/562, but I believe that it is going to be easy to migrate it to Reanimated 2 if anyone is interested in doing that
this should be fixed on 4.0.0-alpha.24
, i will look into your PR for v2
@somebody32
Seems to still be broken for 4.4.5.
@gorhom any update on this ? And are there going to be a new versions ?
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;
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}
/>
@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.
@gorhom have you had a chance to look at this again?