Animate border radius in lightbox transition (#10272)
This commit is contained in:
@@ -4,7 +4,7 @@ import {Image} from 'expo-image'
|
|||||||
|
|
||||||
import {useLightboxControls} from '#/state/lightbox'
|
import {useLightboxControls} from '#/state/lightbox'
|
||||||
import {type Dimensions} from '#/view/com/lightbox/ImageViewing/@types'
|
import {type Dimensions} from '#/view/com/lightbox/ImageViewing/@types'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a, tokens} from '#/alf'
|
||||||
import {AutoSizedImage} from '#/components/images/AutoSizedImage'
|
import {AutoSizedImage} from '#/components/images/AutoSizedImage'
|
||||||
import {Gallery} from '#/components/images/Gallery'
|
import {Gallery} from '#/components/images/Gallery'
|
||||||
import {ImageLayoutGrid} from '#/components/images/ImageLayoutGrid'
|
import {ImageLayoutGrid} from '#/components/images/ImageLayoutGrid'
|
||||||
@@ -42,6 +42,7 @@ export function ImageEmbed({
|
|||||||
thumbRect: null,
|
thumbRect: null,
|
||||||
thumbRef: refs[i] ?? null,
|
thumbRef: refs[i] ?? null,
|
||||||
thumbDimensions: fetchedDims[i] ?? null,
|
thumbDimensions: fetchedDims[i] ?? null,
|
||||||
|
thumbBorderRadius: tokens.borderRadius.md,
|
||||||
type: 'image',
|
type: 'image',
|
||||||
})),
|
})),
|
||||||
index,
|
index,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export type ImageSource = {
|
|||||||
thumbDimensions: Dimensions | null
|
thumbDimensions: Dimensions | null
|
||||||
thumbRect: MeasuredDimensions | null
|
thumbRect: MeasuredDimensions | null
|
||||||
thumbRef?: AnimatedRef<any> | null
|
thumbRef?: AnimatedRef<any> | null
|
||||||
|
thumbBorderRadius?: number
|
||||||
alt?: string
|
alt?: string
|
||||||
type: 'image' | 'circle-avi' | 'rect-avi'
|
type: 'image' | 'circle-avi' | 'rect-avi'
|
||||||
}
|
}
|
||||||
@@ -37,3 +38,12 @@ export type Transform = Exclude<
|
|||||||
TransformsStyle['transform'],
|
TransformsStyle['transform'],
|
||||||
string | undefined
|
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 {
|
import {
|
||||||
type Dimensions as ImageDimensions,
|
type Dimensions as ImageDimensions,
|
||||||
type ImageSource,
|
type ImageSource,
|
||||||
type Transform,
|
type LightboxTransforms,
|
||||||
} from '../../@types'
|
} from '../../@types'
|
||||||
import {
|
import {
|
||||||
applyRounding,
|
applyRounding,
|
||||||
@@ -53,15 +53,7 @@ type Props = {
|
|||||||
imageAspect: number | undefined
|
imageAspect: number | undefined
|
||||||
imageDimensions: ImageDimensions | undefined
|
imageDimensions: ImageDimensions | undefined
|
||||||
dismissSwipePan: PanGesture
|
dismissSwipePan: PanGesture
|
||||||
transforms: Readonly<
|
transforms: Readonly<SharedValue<LightboxTransforms>>
|
||||||
SharedValue<{
|
|
||||||
scaleAndMoveTransform: Transform
|
|
||||||
cropFrameTransform: Transform
|
|
||||||
cropContentTransform: Transform
|
|
||||||
isResting: boolean
|
|
||||||
isHidden: boolean
|
|
||||||
}>
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
const ImageItem = ({
|
const ImageItem = ({
|
||||||
imageSrc,
|
imageSrc,
|
||||||
@@ -339,11 +331,12 @@ const ImageItem = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const imageCropStyle = useAnimatedStyle(() => {
|
const imageCropStyle = useAnimatedStyle(() => {
|
||||||
const {cropFrameTransform} = transforms.get()
|
const {cropFrameTransform, borderRadius: br} = transforms.get()
|
||||||
return {
|
return {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
transform: cropFrameTransform,
|
transform: cropFrameTransform,
|
||||||
|
borderRadius: br,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {Image} from 'expo-image'
|
|||||||
import {
|
import {
|
||||||
type Dimensions as ImageDimensions,
|
type Dimensions as ImageDimensions,
|
||||||
type ImageSource,
|
type ImageSource,
|
||||||
type Transform,
|
type LightboxTransforms,
|
||||||
} from '../../@types'
|
} from '../../@types'
|
||||||
|
|
||||||
const MAX_ORIGINAL_IMAGE_ZOOM = 2
|
const MAX_ORIGINAL_IMAGE_ZOOM = 2
|
||||||
@@ -52,15 +52,7 @@ type Props = {
|
|||||||
imageAspect: number | undefined
|
imageAspect: number | undefined
|
||||||
imageDimensions: ImageDimensions | undefined
|
imageDimensions: ImageDimensions | undefined
|
||||||
dismissSwipePan: PanGesture
|
dismissSwipePan: PanGesture
|
||||||
transforms: Readonly<
|
transforms: Readonly<SharedValue<LightboxTransforms>>
|
||||||
SharedValue<{
|
|
||||||
scaleAndMoveTransform: Transform
|
|
||||||
cropFrameTransform: Transform
|
|
||||||
cropContentTransform: Transform
|
|
||||||
isResting: boolean
|
|
||||||
isHidden: boolean
|
|
||||||
}>
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageItem = ({
|
const ImageItem = ({
|
||||||
@@ -170,10 +162,11 @@ const ImageItem = ({
|
|||||||
|
|
||||||
const imageCropStyle = useAnimatedStyle(() => {
|
const imageCropStyle = useAnimatedStyle(() => {
|
||||||
const screenSize = measureSafeArea()
|
const screenSize = measureSafeArea()
|
||||||
const {cropFrameTransform} = transforms.get()
|
const {cropFrameTransform, borderRadius: br} = transforms.get()
|
||||||
return {
|
return {
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
transform: cropFrameTransform,
|
transform: cropFrameTransform,
|
||||||
|
borderRadius: br,
|
||||||
width: screenSize.width,
|
width: screenSize.width,
|
||||||
maxHeight: screenSize.height,
|
maxHeight: screenSize.height,
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {type Dimensions} from '#/lib/media/types'
|
|||||||
import {
|
import {
|
||||||
type Dimensions as ImageDimensions,
|
type Dimensions as ImageDimensions,
|
||||||
type ImageSource,
|
type ImageSource,
|
||||||
type Transform,
|
type LightboxTransforms,
|
||||||
} from '../../@types'
|
} from '../../@types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -29,15 +29,7 @@ type Props = {
|
|||||||
imageAspect: number | undefined
|
imageAspect: number | undefined
|
||||||
imageDimensions: ImageDimensions | undefined
|
imageDimensions: ImageDimensions | undefined
|
||||||
dismissSwipePan: PanGesture
|
dismissSwipePan: PanGesture
|
||||||
transforms: Readonly<
|
transforms: Readonly<SharedValue<LightboxTransforms>>
|
||||||
SharedValue<{
|
|
||||||
scaleAndMoveTransform: Transform
|
|
||||||
cropFrameTransform: Transform
|
|
||||||
cropContentTransform: Transform
|
|
||||||
isResting: boolean
|
|
||||||
isHidden: boolean
|
|
||||||
}>
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImageItem = (_props: Props) => {
|
const ImageItem = (_props: Props) => {
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ import {useTheme} from '#/alf'
|
|||||||
import {setSystemUITheme} from '#/alf/util/systemUI'
|
import {setSystemUITheme} from '#/alf/util/systemUI'
|
||||||
import {IS_IOS} from '#/env'
|
import {IS_IOS} from '#/env'
|
||||||
import {PlatformInfo} from '../../../../../modules/expo-bluesky-swiss-army'
|
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 ImageDefaultHeader from './components/ImageDefaultHeader'
|
||||||
import ImageItem from './components/ImageItem/ImageItem'
|
import ImageItem from './components/ImageItem/ImageItem'
|
||||||
|
|
||||||
@@ -494,8 +498,8 @@ function LightboxImage({
|
|||||||
return safeArea
|
return safeArea
|
||||||
}, [safeAreaRef, heightDelayedForJSThreadOnly, widthDelayedForJSThreadOnly])
|
}, [safeAreaRef, heightDelayedForJSThreadOnly, widthDelayedForJSThreadOnly])
|
||||||
|
|
||||||
const {thumbRect: thumbRectJS} = imageSrc
|
const {thumbRect: thumbRectJS, thumbBorderRadius} = imageSrc
|
||||||
const transforms = useDerivedValue(() => {
|
const transforms = useDerivedValue<LightboxTransforms>(() => {
|
||||||
'worklet'
|
'worklet'
|
||||||
const safeArea = measureSafeArea()
|
const safeArea = measureSafeArea()
|
||||||
const openProgressValue = openProgress.get()
|
const openProgressValue = openProgress.get()
|
||||||
@@ -506,6 +510,7 @@ function LightboxImage({
|
|||||||
return {
|
return {
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
isResting: false,
|
isResting: false,
|
||||||
|
borderRadius: 0,
|
||||||
scaleAndMoveTransform: [],
|
scaleAndMoveTransform: [],
|
||||||
cropFrameTransform: [],
|
cropFrameTransform: [],
|
||||||
cropContentTransform: [],
|
cropContentTransform: [],
|
||||||
@@ -525,12 +530,14 @@ function LightboxImage({
|
|||||||
thumbRect,
|
thumbRect,
|
||||||
safeArea,
|
safeArea,
|
||||||
imageAspect,
|
imageAspect,
|
||||||
|
thumbBorderRadius,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
isResting: dismissTranslateY === 0,
|
isResting: dismissTranslateY === 0,
|
||||||
|
borderRadius: 0,
|
||||||
scaleAndMoveTransform: [{translateY: dismissTranslateY}],
|
scaleAndMoveTransform: [{translateY: dismissTranslateY}],
|
||||||
cropFrameTransform: [],
|
cropFrameTransform: [],
|
||||||
cropContentTransform: [],
|
cropContentTransform: [],
|
||||||
@@ -772,10 +779,12 @@ function interpolateTransform(
|
|||||||
},
|
},
|
||||||
safeArea: {width: number; height: number; x: number; y: number},
|
safeArea: {width: number; height: number; x: number; y: number},
|
||||||
imageAspect: number,
|
imageAspect: number,
|
||||||
|
thumbBorderRadius?: number,
|
||||||
): {
|
): {
|
||||||
scaleAndMoveTransform: Transform
|
scaleAndMoveTransform: Transform
|
||||||
cropFrameTransform: Transform
|
cropFrameTransform: Transform
|
||||||
cropContentTransform: Transform
|
cropContentTransform: Transform
|
||||||
|
borderRadius: number
|
||||||
isResting: boolean
|
isResting: boolean
|
||||||
isHidden: boolean
|
isHidden: boolean
|
||||||
} {
|
} {
|
||||||
@@ -827,12 +836,23 @@ function interpolateTransform(
|
|||||||
[0, 1],
|
[0, 1],
|
||||||
[croppedFinalHeight / finalHeight, 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 {
|
return {
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
isResting: progress === 1,
|
isResting: progress === 1,
|
||||||
scaleAndMoveTransform: [{translateX}, {translateY}, {scale}],
|
scaleAndMoveTransform: [{translateX}, {translateY}, {scale}],
|
||||||
cropFrameTransform: [{scaleX: cropScaleX}, {scaleY: cropScaleY}],
|
cropFrameTransform: [{scaleX: cropScaleX}, {scaleY: cropScaleY}],
|
||||||
cropContentTransform: [{scaleX: 1 / cropScaleX}, {scaleY: 1 / cropScaleY}],
|
cropContentTransform: [{scaleX: 1 / cropScaleX}, {scaleY: 1 / cropScaleY}],
|
||||||
|
borderRadius,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user