"Contain" images with missing dimensions instead of cropping them (#6828)

* Show unknown aspect as "contain" for autosize

* Fix a flash of wrong position when opening in lightbox

* Fix last frame flash on Android
This commit is contained in:
dan
2024-11-28 23:29:45 +00:00
committed by Dan Abramov
parent e1bf717e9d
commit 4e8b9c1da6
3 changed files with 35 additions and 20 deletions
@@ -392,9 +392,9 @@ const ImageItem = ({
<Animated.View style={imageCropStyle}>
<Animated.View style={imageStyle}>
<Image
contentFit="cover"
contentFit="contain"
source={{uri: imageSrc.uri}}
placeholderContentFit="cover"
placeholderContentFit="contain"
placeholder={{uri: imageSrc.thumbUri}}
accessibilityLabel={imageSrc.alt}
onLoad={
+26 -11
View File
@@ -8,7 +8,7 @@
// Original code copied and simplified from the link below as the codebase is currently not maintained:
// https://github.com/jobtoday/react-native-image-viewing
import React, {useCallback, useEffect, useState} from 'react'
import React, {useCallback, useEffect, useMemo, useState} from 'react'
import {
LayoutAnimation,
PixelRatio,
@@ -79,6 +79,15 @@ const FAST_SPRING: WithSpringConfig = {
restDisplacementThreshold: 0.01,
}
function canAnimate(lightbox: Lightbox): boolean {
return (
!PlatformInfo.getIsReducedMotionEnabled() &&
lightbox.images.every(
img => img.thumbRect && (img.dimensions || img.thumbDimensions),
)
)
}
export default function ImageViewRoot({
lightbox: nextLightbox,
onRequestClose,
@@ -104,23 +113,19 @@ export default function ImageViewRoot({
return
}
const canAnimate =
!PlatformInfo.getIsReducedMotionEnabled() &&
nextLightbox.images.every(
img => img.thumbRect && (img.dimensions || img.thumbDimensions),
)
const isAnimated = canAnimate(nextLightbox)
// https://github.com/software-mansion/react-native-reanimated/issues/6677
rAF_FIXED(() => {
openProgress.set(() =>
canAnimate ? withClampedSpring(1, SLOW_SPRING) : 1,
isAnimated ? withClampedSpring(1, SLOW_SPRING) : 1,
)
})
return () => {
// https://github.com/software-mansion/react-native-reanimated/issues/6677
rAF_FIXED(() => {
openProgress.set(() =>
canAnimate ? withClampedSpring(0, SLOW_SPRING) : 0,
isAnimated ? withClampedSpring(0, SLOW_SPRING) : 0,
)
})
}
@@ -185,6 +190,7 @@ function ImageView({
openProgress: SharedValue<number>
}) {
const {images, index: initialImageIndex} = lightbox
const isAnimated = useMemo(() => canAnimate(lightbox), [lightbox])
const [isScaled, setIsScaled] = useState(false)
const [isDragging, setIsDragging] = useState(false)
const [imageIndex, setImageIndex] = useState(initialImageIndex)
@@ -194,10 +200,19 @@ function ImageView({
const isFlyingAway = useSharedValue(false)
const containerStyle = useAnimatedStyle(() => {
if (openProgress.get() < 1 || isFlyingAway.get()) {
return {pointerEvents: 'none'}
if (openProgress.get() < 1) {
return {
pointerEvents: 'none',
opacity: isAnimated ? 1 : 0,
}
}
return {pointerEvents: 'auto'}
if (isFlyingAway.get()) {
return {
pointerEvents: 'none',
opacity: 1,
}
}
return {pointerEvents: 'auto', opacity: 1}
})
const backdropStyle = useAnimatedStyle(() => {
+7 -7
View File
@@ -85,10 +85,6 @@ export function AutoSizedImage({
if (Number.isNaN(aspectRatio)) {
aspectRatio = undefined
}
} else {
// If we don't know it synchronously, treat it like a square.
// We won't use fetched dimensions to avoid a layout shift.
aspectRatio = 1
}
let constrained: number | undefined
@@ -103,11 +99,13 @@ export function AutoSizedImage({
const cropDisabled = crop === 'none'
const isCropped = rawIsCropped && !cropDisabled
const isContain = aspectRatio === undefined
const hasAlt = !!image.alt
const contents = (
<View ref={containerRef} collapsable={false} style={{flex: 1}}>
<Image
contentFit={isContain ? 'contain' : 'cover'}
style={[a.w_full, a.h_full]}
source={image.thumb}
accessible={true} // Must set for `accessibilityLabel` to work
@@ -115,9 +113,11 @@ export function AutoSizedImage({
accessibilityLabel={image.alt}
accessibilityHint=""
onLoad={e => {
fetchedDimsRef.current = {
width: e.source.width,
height: e.source.height,
if (!isContain) {
fetchedDimsRef.current = {
width: e.source.width,
height: e.source.height,
}
}
}}
/>