Add <DeferReveal> to defer expensive views

This commit is contained in:
Dan Abramov
2024-11-24 21:52:58 +00:00
parent b9406aa011
commit df9a0a075f
6 changed files with 202 additions and 159 deletions
+23
View File
@@ -0,0 +1,23 @@
import {ReactNode, useEffect, useState} from 'react'
export function DeferReveal({
children,
defer,
// To verify, toggle `forceReveal` between `true` and `false`.
iVerifiedThereAreNoLayoutJumps: _unused,
forceReveal,
}: {
children: ReactNode
defer: boolean
iVerifiedThereAreNoLayoutJumps: true
forceReveal?: never // DEV-only
}) {
const [isReady, setIsReady] = useState(!defer)
useEffect(() => {
if (!isReady) {
setIsReady(true)
}
}, [isReady])
const finalIsReady = forceReveal ?? isReady
return finalIsReady ? children : null
}
+73 -69
View File
@@ -8,8 +8,10 @@ import {useLingui} from '@lingui/react'
import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef'
import type {Dimensions} from '#/lib/media/types'
import {isNative} from '#/platform/detection'
import {isAndroid} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {DeferReveal} from '#/components/DeferReveal'
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
@@ -107,84 +109,86 @@ export function AutoSizedImage({
const contents = (
<View ref={containerRef} collapsable={false} style={{flex: 1}}>
<Image
style={[a.w_full, a.h_full]}
source={image.thumb}
accessible={true} // Must set for `accessibilityLabel` to work
accessibilityIgnoresInvertColors
accessibilityLabel={image.alt}
accessibilityHint=""
onLoad={e => {
fetchedDimsRef.current = {
width: e.source.width,
height: e.source.height,
}
}}
/>
<MediaInsetBorder />
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
<Image
style={[a.w_full, a.h_full]}
source={image.thumb}
accessible={true} // Must set for `accessibilityLabel` to work
accessibilityIgnoresInvertColors
accessibilityLabel={image.alt}
accessibilityHint=""
onLoad={e => {
fetchedDimsRef.current = {
width: e.source.width,
height: e.source.height,
}
}}
/>
<MediaInsetBorder />
{(hasAlt || isCropped) && !hideBadge ? (
<View
accessible={false}
style={[
a.absolute,
a.flex_row,
{
bottom: a.p_xs.padding,
right: a.p_xs.padding,
gap: 3,
},
largeAlt && [
{(hasAlt || isCropped) && !hideBadge ? (
<View
accessible={false}
style={[
a.absolute,
a.flex_row,
{
gap: 4,
bottom: a.p_xs.padding,
right: a.p_xs.padding,
gap: 3,
},
],
]}>
{isCropped && (
<View
style={[
a.rounded_xs,
t.atoms.bg_contrast_25,
largeAlt && [
{
padding: 3,
opacity: 0.8,
gap: 4,
},
largeAlt && [
],
]}>
{isCropped && (
<View
style={[
a.rounded_xs,
t.atoms.bg_contrast_25,
{
padding: 5,
padding: 3,
opacity: 0.8,
},
],
]}>
<Fullscreen
fill={t.atoms.text_contrast_high.color}
width={largeAlt ? 18 : 12}
/>
</View>
)}
{hasAlt && (
<View
style={[
a.justify_center,
a.rounded_xs,
t.atoms.bg_contrast_25,
{
padding: 3,
opacity: 0.8,
},
largeAlt && [
largeAlt && [
{
padding: 5,
},
],
]}>
<Fullscreen
fill={t.atoms.text_contrast_high.color}
width={largeAlt ? 18 : 12}
/>
</View>
)}
{hasAlt && (
<View
style={[
a.justify_center,
a.rounded_xs,
t.atoms.bg_contrast_25,
{
padding: 5,
padding: 3,
opacity: 0.8,
},
],
]}>
<Text
style={[a.font_heavy, largeAlt ? a.text_xs : {fontSize: 8}]}>
ALT
</Text>
</View>
)}
</View>
) : null}
largeAlt && [
{
padding: 5,
},
],
]}>
<Text
style={[a.font_heavy, largeAlt ? a.text_xs : {fontSize: 8}]}>
ALT
</Text>
</View>
)}
</View>
) : null}
</DeferReveal>
</View>
)
+18 -14
View File
@@ -7,9 +7,11 @@ import {useLingui} from '@lingui/react'
import {HandleRef} from '#/lib/hooks/useHandleRef'
import {Dimensions} from '#/lib/media/types'
import {isAndroid} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useTheme} from '#/alf'
import {DeferReveal} from '#/components/DeferReveal'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {Text} from '#/components/Typography'
@@ -70,20 +72,22 @@ export function GalleryItem({
accessibilityRole="button"
accessibilityLabel={image.alt || _(msg`Image`)}
accessibilityHint="">
<Image
source={{uri: image.thumb}}
style={[a.flex_1]}
accessible={true}
accessibilityLabel={image.alt}
accessibilityHint=""
accessibilityIgnoresInvertColors
onLoad={e => {
thumbDimsRef.current[index] = {
width: e.source.width,
height: e.source.height,
}
}}
/>
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
<Image
source={{uri: image.thumb}}
style={[a.flex_1]}
accessible={true}
accessibilityLabel={image.alt}
accessibilityHint=""
accessibilityIgnoresInvertColors
onLoad={e => {
thumbDimsRef.current[index] = {
width: e.source.width,
height: e.source.height,
}
}}
/>
</DeferReveal>
<MediaInsetBorder style={insetBorderStyle} />
</Pressable>
{hasAlt && !hideBadges ? (
@@ -9,12 +9,13 @@ import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
import {shareUrl} from '#/lib/sharing'
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
import {toNiceDomain} from '#/lib/strings/url-helpers'
import {isNative} from '#/platform/detection'
import {isAndroid, isNative} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
import {atoms as a, useTheme} from '#/alf'
import {DeferReveal} from '#/components/DeferReveal'
import {Divider} from '#/components/Divider'
import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {Link} from '#/components/Link'
@@ -87,13 +88,18 @@ export const ExternalLinkEmbed = ({
: t.atoms.border_contrast_low,
]}>
{imageUri && !embedPlayerParams ? (
<Image
<View
style={{
aspectRatio: 1.91,
}}
source={{uri: imageUri}}
accessibilityIgnoresInvertColors
/>
}}>
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
<Image
style={{flex: 1}}
source={{uri: imageUri}}
accessibilityIgnoresInvertColors
/>
</DeferReveal>
</View>
) : undefined}
{embedPlayerParams?.isGif ? (
@@ -23,9 +23,10 @@ import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from '#/lib/routes/types'
import {EmbedPlayerParams, getPlayerAspect} from '#/lib/strings/embed-player'
import {isNative} from '#/platform/detection'
import {isAndroid, isNative} from '#/platform/detection'
import {useExternalEmbedsPrefs} from '#/state/preferences'
import {atoms as a, useTheme} from '#/alf'
import {DeferReveal} from '#/components/DeferReveal'
import {useDialogControl} from '#/components/Dialog'
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
import {Fill} from '#/components/Fill'
@@ -217,43 +218,45 @@ export function ExternalPlayer({
ref={viewRef}
collapsable={false}
style={[aspect, a.overflow_hidden]}>
{link.thumb && (!isPlayerActive || isLoading) ? (
<>
<Image
style={[a.flex_1]}
source={{uri: link.thumb}}
accessibilityIgnoresInvertColors
/>
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
{link.thumb && (!isPlayerActive || isLoading) ? (
<>
<Image
style={[a.flex_1]}
source={{uri: link.thumb}}
accessibilityIgnoresInvertColors
/>
<Fill
style={[
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
{
opacity: 0.3,
},
]}
/>
</>
) : (
<Fill
style={[
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
{
backgroundColor:
t.name === 'light' ? t.palette.contrast_975 : 'black',
opacity: 0.3,
},
]}
/>
</>
) : (
<Fill
style={[
{
backgroundColor:
t.name === 'light' ? t.palette.contrast_975 : 'black',
opacity: 0.3,
},
]}
)}
<PlaceholderOverlay
isLoading={isLoading}
isPlayerActive={isPlayerActive}
onPress={onPlayPress}
/>
)}
<PlaceholderOverlay
isLoading={isLoading}
isPlayerActive={isPlayerActive}
onPress={onPlayPress}
/>
<Player
isPlayerActive={isPlayerActive}
params={params}
onLoad={onLoad}
/>
<Player
isPlayerActive={isPlayerActive}
params={params}
onLoad={onLoad}
/>
</DeferReveal>
</Animated.View>
</>
)
+43 -40
View File
@@ -12,10 +12,11 @@ import {useLingui} from '@lingui/react'
import {HITSLOP_20} from '#/lib/constants'
import {EmbedPlayerParams} from '#/lib/strings/embed-player'
import {isWeb} from '#/platform/detection'
import {isAndroid, isWeb} from '#/platform/detection'
import {useAutoplayDisabled} from '#/state/preferences'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {atoms as a, useTheme} from '#/alf'
import {DeferReveal} from '#/components/DeferReveal'
import {Fill} from '#/components/Fill'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
@@ -121,46 +122,48 @@ export function GifEmbed({
{aspectRatio: params.dimensions!.width / params.dimensions!.height},
style,
]}>
<View
style={[
a.absolute,
/*
* Aspect ratio was being clipped weirdly on web -esb
*/
{
top: -2,
bottom: -2,
left: -2,
right: -2,
},
]}>
<PlaybackControls
onPress={onPress}
isPlaying={playerState.isPlaying}
isLoaded={playerState.isLoaded}
/>
<GifView
source={params.playerUri}
placeholderSource={thumb}
style={[a.flex_1]}
autoplay={!autoplayDisabled}
onPlayerStateChange={onPlayerStateChange}
ref={playerRef}
accessibilityHint={_(msg`Animated GIF`)}
accessibilityLabel={altText}
/>
{!playerState.isPlaying && (
<Fill
style={[
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
{
opacity: 0.3,
},
]}
<DeferReveal defer={isAndroid} iVerifiedThereAreNoLayoutJumps>
<View
style={[
a.absolute,
/*
* Aspect ratio was being clipped weirdly on web -esb
*/
{
top: -2,
bottom: -2,
left: -2,
right: -2,
},
]}>
<PlaybackControls
onPress={onPress}
isPlaying={playerState.isPlaying}
isLoaded={playerState.isLoaded}
/>
)}
{!hideAlt && isPreferredAltText && <AltText text={altText} />}
</View>
<GifView
source={params.playerUri}
placeholderSource={thumb}
style={[a.flex_1]}
autoplay={!autoplayDisabled}
onPlayerStateChange={onPlayerStateChange}
ref={playerRef}
accessibilityHint={_(msg`Animated GIF`)}
accessibilityLabel={altText}
/>
{!playerState.isPlaying && (
<Fill
style={[
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
{
opacity: 0.3,
},
]}
/>
)}
{!hideAlt && isPreferredAltText && <AltText text={altText} />}
</View>
</DeferReveal>
</View>
)
}