Animate border radius in lightbox transition (#10272)
This commit is contained in:
@@ -29,6 +29,7 @@ export type ImageSource = {
|
||||
thumbDimensions: Dimensions | null
|
||||
thumbRect: MeasuredDimensions | null
|
||||
thumbRef?: AnimatedRef<any> | null
|
||||
thumbBorderRadius?: number
|
||||
alt?: string
|
||||
type: 'image' | 'circle-avi' | 'rect-avi'
|
||||
}
|
||||
@@ -37,3 +38,12 @@ export type Transform = Exclude<
|
||||
TransformsStyle['transform'],
|
||||
string | undefined
|
||||
>
|
||||
|
||||
export type LightboxTransforms = {
|
||||
scaleAndMoveTransform: Transform
|
||||
cropFrameTransform: Transform
|
||||
cropContentTransform: Transform
|
||||
borderRadius: number
|
||||
isResting: boolean
|
||||
isHidden: boolean
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {Image} from 'expo-image'
|
||||
import {
|
||||
type Dimensions as ImageDimensions,
|
||||
type ImageSource,
|
||||
type Transform,
|
||||
type LightboxTransforms,
|
||||
} from '../../@types'
|
||||
import {
|
||||
applyRounding,
|
||||
@@ -53,15 +53,7 @@ type Props = {
|
||||
imageAspect: number | undefined
|
||||
imageDimensions: ImageDimensions | undefined
|
||||
dismissSwipePan: PanGesture
|
||||
transforms: Readonly<
|
||||
SharedValue<{
|
||||
scaleAndMoveTransform: Transform
|
||||
cropFrameTransform: Transform
|
||||
cropContentTransform: Transform
|
||||
isResting: boolean
|
||||
isHidden: boolean
|
||||
}>
|
||||
>
|
||||
transforms: Readonly<SharedValue<LightboxTransforms>>
|
||||
}
|
||||
const ImageItem = ({
|
||||
imageSrc,
|
||||
@@ -339,11 +331,12 @@ const ImageItem = ({
|
||||
})
|
||||
|
||||
const imageCropStyle = useAnimatedStyle(() => {
|
||||
const {cropFrameTransform} = transforms.get()
|
||||
const {cropFrameTransform, borderRadius: br} = transforms.get()
|
||||
return {
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
transform: cropFrameTransform,
|
||||
borderRadius: br,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import {Image} from 'expo-image'
|
||||
import {
|
||||
type Dimensions as ImageDimensions,
|
||||
type ImageSource,
|
||||
type Transform,
|
||||
type LightboxTransforms,
|
||||
} from '../../@types'
|
||||
|
||||
const MAX_ORIGINAL_IMAGE_ZOOM = 2
|
||||
@@ -52,15 +52,7 @@ type Props = {
|
||||
imageAspect: number | undefined
|
||||
imageDimensions: ImageDimensions | undefined
|
||||
dismissSwipePan: PanGesture
|
||||
transforms: Readonly<
|
||||
SharedValue<{
|
||||
scaleAndMoveTransform: Transform
|
||||
cropFrameTransform: Transform
|
||||
cropContentTransform: Transform
|
||||
isResting: boolean
|
||||
isHidden: boolean
|
||||
}>
|
||||
>
|
||||
transforms: Readonly<SharedValue<LightboxTransforms>>
|
||||
}
|
||||
|
||||
const ImageItem = ({
|
||||
@@ -170,10 +162,11 @@ const ImageItem = ({
|
||||
|
||||
const imageCropStyle = useAnimatedStyle(() => {
|
||||
const screenSize = measureSafeArea()
|
||||
const {cropFrameTransform} = transforms.get()
|
||||
const {cropFrameTransform, borderRadius: br} = transforms.get()
|
||||
return {
|
||||
overflow: 'hidden',
|
||||
transform: cropFrameTransform,
|
||||
borderRadius: br,
|
||||
width: screenSize.width,
|
||||
maxHeight: screenSize.height,
|
||||
alignSelf: 'center',
|
||||
|
||||
@@ -9,7 +9,7 @@ import {type Dimensions} from '#/lib/media/types'
|
||||
import {
|
||||
type Dimensions as ImageDimensions,
|
||||
type ImageSource,
|
||||
type Transform,
|
||||
type LightboxTransforms,
|
||||
} from '../../@types'
|
||||
|
||||
type Props = {
|
||||
@@ -29,15 +29,7 @@ type Props = {
|
||||
imageAspect: number | undefined
|
||||
imageDimensions: ImageDimensions | undefined
|
||||
dismissSwipePan: PanGesture
|
||||
transforms: Readonly<
|
||||
SharedValue<{
|
||||
scaleAndMoveTransform: Transform
|
||||
cropFrameTransform: Transform
|
||||
cropContentTransform: Transform
|
||||
isResting: boolean
|
||||
isHidden: boolean
|
||||
}>
|
||||
>
|
||||
transforms: Readonly<SharedValue<LightboxTransforms>>
|
||||
}
|
||||
|
||||
const ImageItem = (_props: Props) => {
|
||||
|
||||
@@ -52,7 +52,11 @@ import {useTheme} from '#/alf'
|
||||
import {setSystemUITheme} from '#/alf/util/systemUI'
|
||||
import {IS_IOS} from '#/env'
|
||||
import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
|
||||
import {type ImageSource, type Transform} from './@types'
|
||||
import {
|
||||
type ImageSource,
|
||||
type LightboxTransforms,
|
||||
type Transform,
|
||||
} from './@types'
|
||||
import ImageDefaultHeader from './components/ImageDefaultHeader'
|
||||
import ImageItem from './components/ImageItem/ImageItem'
|
||||
|
||||
@@ -494,8 +498,8 @@ function LightboxImage({
|
||||
return safeArea
|
||||
}, [safeAreaRef, heightDelayedForJSThreadOnly, widthDelayedForJSThreadOnly])
|
||||
|
||||
const {thumbRect: thumbRectJS} = imageSrc
|
||||
const transforms = useDerivedValue(() => {
|
||||
const {thumbRect: thumbRectJS, thumbBorderRadius} = imageSrc
|
||||
const transforms = useDerivedValue<LightboxTransforms>(() => {
|
||||
'worklet'
|
||||
const safeArea = measureSafeArea()
|
||||
const openProgressValue = openProgress.get()
|
||||
@@ -506,6 +510,7 @@ function LightboxImage({
|
||||
return {
|
||||
isHidden: true,
|
||||
isResting: false,
|
||||
borderRadius: 0,
|
||||
scaleAndMoveTransform: [],
|
||||
cropFrameTransform: [],
|
||||
cropContentTransform: [],
|
||||
@@ -525,12 +530,14 @@ function LightboxImage({
|
||||
thumbRect,
|
||||
safeArea,
|
||||
imageAspect,
|
||||
thumbBorderRadius,
|
||||
)
|
||||
}
|
||||
}
|
||||
return {
|
||||
isHidden: false,
|
||||
isResting: dismissTranslateY === 0,
|
||||
borderRadius: 0,
|
||||
scaleAndMoveTransform: [{translateY: dismissTranslateY}],
|
||||
cropFrameTransform: [],
|
||||
cropContentTransform: [],
|
||||
@@ -772,10 +779,12 @@ function interpolateTransform(
|
||||
},
|
||||
safeArea: {width: number; height: number; x: number; y: number},
|
||||
imageAspect: number,
|
||||
thumbBorderRadius?: number,
|
||||
): {
|
||||
scaleAndMoveTransform: Transform
|
||||
cropFrameTransform: Transform
|
||||
cropContentTransform: Transform
|
||||
borderRadius: number
|
||||
isResting: boolean
|
||||
isHidden: boolean
|
||||
} {
|
||||
@@ -827,12 +836,23 @@ function interpolateTransform(
|
||||
[0, 1],
|
||||
[croppedFinalHeight / finalHeight, 1],
|
||||
)
|
||||
// The border radius in the source thumbnail needs to be scaled to account
|
||||
// for the crop frame and overall scale so it visually matches at progress=0.
|
||||
const sourceBorderRadius = thumbBorderRadius ?? 0
|
||||
const initialCropScaleX = croppedFinalWidth / finalWidth
|
||||
const borderRadius = interpolate(
|
||||
progress,
|
||||
[0, 1],
|
||||
[sourceBorderRadius / (initialScale * initialCropScaleX), 0],
|
||||
)
|
||||
|
||||
return {
|
||||
isHidden: false,
|
||||
isResting: progress === 1,
|
||||
scaleAndMoveTransform: [{translateX}, {translateY}, {scale}],
|
||||
cropFrameTransform: [{scaleX: cropScaleX}, {scaleY: cropScaleY}],
|
||||
cropContentTransform: [{scaleX: 1 / cropScaleX}, {scaleY: 1 / cropScaleY}],
|
||||
borderRadius,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user