Lazy load images
This commit is contained in:
@@ -4,7 +4,6 @@ import Animated, {
|
||||
type AnimatedRef,
|
||||
useAnimatedRef,
|
||||
} from 'react-native-reanimated'
|
||||
import {Image} from 'expo-image'
|
||||
import {type AppBskyEmbedImages} from '@atproto/api'
|
||||
import {utils} from '@bsky.app/alf'
|
||||
import {msg} from '@lingui/macro'
|
||||
@@ -13,6 +12,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {type Dimensions} from '#/lib/media/types'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||
import {LazyImage} from '#/view/com/util/images/LazyImage'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/components/icons/ArrowsDiagonal'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
@@ -111,7 +111,7 @@ export function AutoSizedImage({
|
||||
|
||||
const contents = (
|
||||
<Animated.View ref={containerRef} collapsable={false} style={{flex: 1}}>
|
||||
<Image
|
||||
<LazyImage
|
||||
contentFit={isContain ? 'contain' : 'cover'}
|
||||
style={[a.w_full, a.h_full]}
|
||||
source={image.thumb}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
import {type AnimatedRef} from 'react-native-reanimated'
|
||||
import {Image, type ImageStyle} from 'expo-image'
|
||||
import {type ImageStyle} from 'expo-image'
|
||||
import {type AppBskyEmbedImages} from '@atproto/api'
|
||||
import {utils} from '@bsky.app/alf'
|
||||
import {msg} from '@lingui/macro'
|
||||
@@ -8,6 +8,7 @@ import {useLingui} from '@lingui/react'
|
||||
|
||||
import {type Dimensions} from '#/lib/media/types'
|
||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||
import {LazyImage} from '#/view/com/util/images/LazyImage'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import {PostEmbedViewContext} from '#/components/Post/Embed/types'
|
||||
@@ -74,7 +75,7 @@ export function GalleryItem({
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={image.alt || _(msg`Image`)}
|
||||
accessibilityHint="">
|
||||
<Image
|
||||
<LazyImage
|
||||
source={{uri: image.thumb}}
|
||||
style={[a.flex_1]}
|
||||
accessible={true}
|
||||
@@ -82,6 +83,7 @@ export function GalleryItem({
|
||||
accessibilityHint=""
|
||||
accessibilityIgnoresInvertColors
|
||||
onLoad={e => {
|
||||
// eslint-disable-next-line react-compiler/react-compiler
|
||||
thumbDimsRef.current[index] = {
|
||||
width: e.source.width,
|
||||
height: e.source.height,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {Image, type ImageProps} from 'expo-image'
|
||||
|
||||
export type LazyImageProps = ImageProps & {
|
||||
onLoad?: (e: {source: {width: number; height: number}}) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Native implementation - just uses expo-image directly.
|
||||
* The web version adds loading="lazy" for browser-native lazy loading.
|
||||
*/
|
||||
export function LazyImage(props: LazyImageProps) {
|
||||
return <Image accessibilityIgnoresInvertColors {...props} />
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import {type StyleProp} from 'react-native'
|
||||
import {type ImageStyle} from 'expo-image'
|
||||
|
||||
import {type LazyImageProps} from '#/view/com/util/images/LazyImage'
|
||||
|
||||
function getUri(source: LazyImageProps['source']): string | undefined {
|
||||
if (typeof source === 'string') {
|
||||
return source
|
||||
}
|
||||
if (source && typeof source === 'object' && 'uri' in source) {
|
||||
return source.uri
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function styleToCSS(style: StyleProp<ImageStyle>): React.CSSProperties {
|
||||
if (!style) return {}
|
||||
const flat = Array.isArray(style)
|
||||
? Object.assign({}, ...style.filter(Boolean))
|
||||
: style
|
||||
const css: React.CSSProperties = {}
|
||||
if (flat.width !== undefined) css.width = flat.width
|
||||
if (flat.height !== undefined) css.height = flat.height
|
||||
if (flat.flex !== undefined) css.flex = flat.flex
|
||||
return css
|
||||
}
|
||||
|
||||
/**
|
||||
* Web implementation - uses native img with loading="lazy" for
|
||||
* browser-native lazy loading to improve Lighthouse scores.
|
||||
*/
|
||||
export function LazyImage({
|
||||
source,
|
||||
style,
|
||||
accessibilityLabel,
|
||||
onLoad,
|
||||
contentFit = 'cover',
|
||||
}: LazyImageProps) {
|
||||
const uri = getUri(source)
|
||||
|
||||
if (!uri) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={uri}
|
||||
alt={accessibilityLabel}
|
||||
loading="lazy"
|
||||
style={{
|
||||
...styleToCSS(style),
|
||||
objectFit: contentFit,
|
||||
}}
|
||||
onLoad={e => {
|
||||
const img = e.currentTarget
|
||||
onLoad?.({
|
||||
source: {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user