fix fabric reanimated perf by introducing derived value
This commit is contained in:
+2
-2
@@ -376,8 +376,8 @@
|
|||||||
"reanimated": {
|
"reanimated": {
|
||||||
"staticFeatureFlags": {
|
"staticFeatureFlags": {
|
||||||
"DISABLE_COMMIT_PAUSING_MECHANISM": true,
|
"DISABLE_COMMIT_PAUSING_MECHANISM": true,
|
||||||
"ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS": true,
|
"ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS": false,
|
||||||
"IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS": true,
|
"IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS": false,
|
||||||
"USE_SYNCHRONIZABLE_FOR_MUTABLES": true,
|
"USE_SYNCHRONIZABLE_FOR_MUTABLES": true,
|
||||||
"USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true,
|
"USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true,
|
||||||
"FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS": true
|
"FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS": true
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
Extrapolation,
|
|
||||||
interpolate,
|
interpolate,
|
||||||
type SharedValue,
|
type SharedValue,
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
@@ -24,30 +23,28 @@ export function GrowableAvatar({
|
|||||||
return <View style={style}>{children}</View>
|
return <View style={style}>{children}</View>
|
||||||
}
|
}
|
||||||
|
|
||||||
const {scrollY} = pagerContext
|
const {clampedScrollY} = pagerContext
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GrowableAvatarInner scrollY={scrollY} style={style}>
|
<GrowableAvatarInner clampedScrollY={clampedScrollY} style={style}>
|
||||||
{children}
|
{children}
|
||||||
</GrowableAvatarInner>
|
</GrowableAvatarInner>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function GrowableAvatarInner({
|
function GrowableAvatarInner({
|
||||||
scrollY,
|
clampedScrollY,
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
}: {
|
}: {
|
||||||
scrollY: SharedValue<number>
|
clampedScrollY: SharedValue<number>
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}) {
|
}) {
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(scrollY.get(), [-150, 0], [1.2, 1], {
|
scale: interpolate(clampedScrollY.get(), [-150, 0], [1.2, 1]),
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -44,34 +44,37 @@ export function GrowableBanner({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {scrollY} = pagerContext
|
const {clampedScrollY} = pagerContext
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GrowableBannerInner scrollY={scrollY} backButton={backButton}>
|
<GrowableBannerInner
|
||||||
|
clampedScrollY={clampedScrollY}
|
||||||
|
backButton={backButton}>
|
||||||
{children}
|
{children}
|
||||||
</GrowableBannerInner>
|
</GrowableBannerInner>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function GrowableBannerInner({
|
function GrowableBannerInner({
|
||||||
scrollY,
|
clampedScrollY,
|
||||||
backButton,
|
backButton,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
scrollY: SharedValue<number>
|
clampedScrollY: SharedValue<number>
|
||||||
backButton?: React.ReactNode
|
backButton?: React.ReactNode
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const {top: topInset} = useSafeAreaInsets()
|
const {top: topInset} = useSafeAreaInsets()
|
||||||
const isFetching = useIsProfileFetching()
|
const isFetching = useIsProfileFetching()
|
||||||
const animateSpinner = useShouldAnimateSpinner({isFetching, scrollY})
|
const animateSpinner = useShouldAnimateSpinner({
|
||||||
|
isFetching,
|
||||||
|
clampedScrollY,
|
||||||
|
})
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => ({
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
scale: interpolate(scrollY.get(), [-150, 0], [2, 1], {
|
scale: interpolate(clampedScrollY.get(), [-150, 0], [2, 1]),
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
@@ -79,7 +82,7 @@ function GrowableBannerInner({
|
|||||||
const animatedBlurViewProps = useAnimatedProps(() => {
|
const animatedBlurViewProps = useAnimatedProps(() => {
|
||||||
return {
|
return {
|
||||||
intensity: interpolate(
|
intensity: interpolate(
|
||||||
scrollY.get(),
|
clampedScrollY.get(),
|
||||||
[-300, -65, -15],
|
[-300, -65, -15],
|
||||||
[50, 40, 0],
|
[50, 40, 0],
|
||||||
Extrapolation.CLAMP,
|
Extrapolation.CLAMP,
|
||||||
@@ -88,7 +91,7 @@ function GrowableBannerInner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const animatedSpinnerStyle = useAnimatedStyle(() => {
|
const animatedSpinnerStyle = useAnimatedStyle(() => {
|
||||||
const scrollYValue = scrollY.get()
|
const scrollYValue = clampedScrollY.get()
|
||||||
return {
|
return {
|
||||||
display: scrollYValue < 0 ? 'flex' : 'none',
|
display: scrollYValue < 0 ? 'flex' : 'none',
|
||||||
opacity: interpolate(
|
opacity: interpolate(
|
||||||
@@ -98,11 +101,7 @@ function GrowableBannerInner({
|
|||||||
Extrapolation.CLAMP,
|
Extrapolation.CLAMP,
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{translateY: interpolate(scrollYValue, [-150, 0], [-75, 0])},
|
||||||
translateY: interpolate(scrollYValue, [-150, 0], [-75, 0], {
|
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{rotate: '90deg'},
|
{rotate: '90deg'},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -111,9 +110,7 @@ function GrowableBannerInner({
|
|||||||
const animatedBackButtonStyle = useAnimatedStyle(() => ({
|
const animatedBackButtonStyle = useAnimatedStyle(() => ({
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: interpolate(scrollY.get(), [-150, 10], [-150, 10], {
|
translateY: Math.max(-150, Math.min(10, clampedScrollY.get())),
|
||||||
extrapolateRight: Extrapolation.CLAMP,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
@@ -172,10 +169,10 @@ function useIsProfileFetching() {
|
|||||||
|
|
||||||
function useShouldAnimateSpinner({
|
function useShouldAnimateSpinner({
|
||||||
isFetching,
|
isFetching,
|
||||||
scrollY,
|
clampedScrollY,
|
||||||
}: {
|
}: {
|
||||||
isFetching: boolean
|
isFetching: boolean
|
||||||
scrollY: SharedValue<number>
|
clampedScrollY: SharedValue<number>
|
||||||
}) {
|
}) {
|
||||||
const [isOverscrolled, setIsOverscrolled] = useState(false)
|
const [isOverscrolled, setIsOverscrolled] = useState(false)
|
||||||
// HACK: it reports a scroll pos of 0 for a tick when fetching finishes
|
// HACK: it reports a scroll pos of 0 for a tick when fetching finishes
|
||||||
@@ -183,13 +180,13 @@ function useShouldAnimateSpinner({
|
|||||||
const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10)
|
const stickyIsOverscrolled = useStickyToggle(isOverscrolled, 10)
|
||||||
|
|
||||||
useAnimatedReaction(
|
useAnimatedReaction(
|
||||||
() => scrollY.get() < -5,
|
() => clampedScrollY.get() < -5,
|
||||||
(value, prevValue) => {
|
(value, prevValue) => {
|
||||||
if (value !== prevValue) {
|
if (value !== prevValue) {
|
||||||
runOnJS(setIsOverscrolled)(value)
|
runOnJS(setIsOverscrolled)(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[scrollY],
|
[clampedScrollY],
|
||||||
)
|
)
|
||||||
|
|
||||||
const [isAnimating, setIsAnimating] = useState(isFetching)
|
const [isAnimating, setIsAnimating] = useState(isFetching)
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export function StatusBarShadow() {
|
|||||||
const pagerContext = usePagerHeaderContext()
|
const pagerContext = usePagerHeaderContext()
|
||||||
|
|
||||||
if (isIOS && pagerContext) {
|
if (isIOS && pagerContext) {
|
||||||
const {scrollY} = pagerContext
|
const {clampedScrollY} = pagerContext
|
||||||
return <StatusBarShadowInnner scrollY={scrollY} />
|
return <StatusBarShadowInnner clampedScrollY={clampedScrollY} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,14 +30,18 @@ export function StatusBarShadow() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusBarShadowInnner({scrollY}: {scrollY: SharedValue<number>}) {
|
function StatusBarShadowInnner({
|
||||||
|
clampedScrollY,
|
||||||
|
}: {
|
||||||
|
clampedScrollY: SharedValue<number>
|
||||||
|
}) {
|
||||||
const {top: topInset} = useSafeAreaInsets()
|
const {top: topInset} = useSafeAreaInsets()
|
||||||
|
|
||||||
const animatedStyle = useAnimatedStyle(() => {
|
const animatedStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
transform: [
|
transform: [
|
||||||
{
|
{
|
||||||
translateY: Math.min(0, scrollY.get()),
|
translateY: Math.min(0, clampedScrollY.get()),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, {useContext} from 'react'
|
import {createContext, useContext, useMemo} from 'react'
|
||||||
import {type SharedValue} from 'react-native-reanimated'
|
import {type SharedValue, useDerivedValue} from 'react-native-reanimated'
|
||||||
|
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
|
|
||||||
export const PagerHeaderContext = React.createContext<{
|
export const PagerHeaderContext = createContext<{
|
||||||
scrollY: SharedValue<number>
|
scrollY: SharedValue<number>
|
||||||
|
clampedScrollY: SharedValue<number>
|
||||||
headerHeight: number
|
headerHeight: number
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
PagerHeaderContext.displayName = 'PagerHeaderContext'
|
PagerHeaderContext.displayName = 'PagerHeaderContext'
|
||||||
@@ -24,9 +25,16 @@ export function PagerHeaderProvider({
|
|||||||
headerHeight: number
|
headerHeight: number
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const value = React.useMemo(
|
// bit of a hackfix - most of the usage of scrollY here is for the pull-to-refresh
|
||||||
() => ({scrollY, headerHeight}),
|
// behaviour in the header, and after scrolling it's not needed anymore.
|
||||||
[scrollY, headerHeight],
|
// to improve performance, we create a derived value that clamps up-front,
|
||||||
|
// therefore removing the need for the downstream animated styles to run on every frame
|
||||||
|
// when they won't actually be used -sfn
|
||||||
|
const clampedScrollY = useDerivedValue(() => Math.min(100, scrollY.get()))
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({scrollY, clampedScrollY, headerHeight}),
|
||||||
|
[scrollY, headerHeight, clampedScrollY],
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<PagerHeaderContext.Provider value={value}>
|
<PagerHeaderContext.Provider value={value}>
|
||||||
|
|||||||
Reference in New Issue
Block a user