measure card width, keep thumbnail as video bg
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {useWindowDimensions, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {type AppBskyEmbedVideo} from '@atproto/api'
|
import {type AppBskyEmbedVideo} from '@atproto/api'
|
||||||
import {msg} from '@lingui/core/macro'
|
import {msg} from '@lingui/core/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -16,7 +16,6 @@ import {atoms as a, useTheme} from '#/alf'
|
|||||||
import {useIsWithinMessage} from '#/components/dms/MessageContext'
|
import {useIsWithinMessage} from '#/components/dms/MessageContext'
|
||||||
import {useFullscreen} from '#/components/hooks/useFullscreen'
|
import {useFullscreen} from '#/components/hooks/useFullscreen'
|
||||||
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
|
import {ConstrainedImage} from '#/components/images/AutoSizedImage'
|
||||||
import {CENTER_COLUMN_WIDTH} from '#/components/Layout'
|
|
||||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||||
import {
|
import {
|
||||||
HLSUnsupportedError,
|
HLSUnsupportedError,
|
||||||
@@ -97,16 +96,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
constrained = Math.max(aspectRatio, ratio)
|
constrained = Math.max(aspectRatio, ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {width: windowWidth} = useWindowDimensions()
|
const [containerWidth, setContainerWidth] = useState(0)
|
||||||
|
|
||||||
/*
|
|
||||||
* Estimated rather than measured with onLayout - a measurement lands after
|
|
||||||
* first paint, which flashed a full-width card for a frame before snapping
|
|
||||||
* narrow. The post column is capped at CENTER_COLUMN_WIDTH, and feed
|
|
||||||
* padding plus the avatar gutter inset the embed by roughly 100px.
|
|
||||||
*/
|
|
||||||
const estimatedContainerWidth =
|
|
||||||
Math.min(windowWidth, CENTER_COLUMN_WIDTH) - 100
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Portrait videos render at their own ratio instead of pillarboxed, but
|
* Portrait videos render at their own ratio instead of pillarboxed, but
|
||||||
@@ -115,7 +105,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
* ratio can't be fit, so both keep the full-width pillarbox - a narrow
|
* ratio can't be fit, so both keep the full-width pillarbox - a narrow
|
||||||
* card with black slices down the sides looks broken (see #9371).
|
* card with black slices down the sides looks broken (see #9371).
|
||||||
*/
|
*/
|
||||||
const cardWidth = estimatedContainerWidth * Math.min(aspectRatio ?? 1, 1)
|
const cardWidth = containerWidth * Math.min(aspectRatio ?? 1, 1)
|
||||||
const fullBleed =
|
const fullBleed =
|
||||||
aspectRatio === undefined ||
|
aspectRatio === undefined ||
|
||||||
aspectRatio < 1 / 2 ||
|
aspectRatio < 1 / 2 ||
|
||||||
@@ -130,34 +120,42 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
backgroundColor: t.palette.black,
|
backgroundColor: t.palette.black,
|
||||||
|
backgroundImage: `url(${embed.thumbnail})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
}}
|
}}
|
||||||
onClick={evt => evt.stopPropagation()}>
|
onClick={evt => evt.stopPropagation()}>
|
||||||
{/* blurred backdrop fills the bars when the video is boxed */}
|
|
||||||
{fullBleed && embed.thumbnail && (
|
{fullBleed && embed.thumbnail && (
|
||||||
<div
|
<>
|
||||||
aria-hidden
|
{/* blurred backdrop fills the bars when the video is boxed */}
|
||||||
style={{
|
<div
|
||||||
position: 'absolute',
|
aria-hidden
|
||||||
inset: 0,
|
style={{
|
||||||
backgroundImage: `url(${embed.thumbnail})`,
|
position: 'absolute',
|
||||||
backgroundSize: 'cover',
|
inset: 0,
|
||||||
backgroundPosition: 'center',
|
backgroundImage: `url(${embed.thumbnail})`,
|
||||||
filter: 'blur(32px)',
|
backgroundSize: 'cover',
|
||||||
// hide the transparent fade the blur creates at the edges
|
backgroundPosition: 'center',
|
||||||
transform: 'scale(1.2)',
|
filter: 'blur(32px)',
|
||||||
}}
|
// hide the transparent fade the blur creates at the edges
|
||||||
/>
|
transform: 'scale(1.2)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* redraw the sharp thumbnail above the blur */}
|
||||||
|
<div
|
||||||
|
aria-hidden
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
backgroundImage: `url(${embed.thumbnail})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
inset: 0,
|
|
||||||
backgroundImage: `url(${embed.thumbnail})`,
|
|
||||||
backgroundSize: 'contain',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
backgroundRepeat: 'no-repeat',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ErrorBoundary renderError={renderError} key={key}>
|
<ErrorBoundary renderError={renderError} key={key}>
|
||||||
<OnlyNearScreen>
|
<OnlyNearScreen>
|
||||||
<VideoEmbedInnerWeb
|
<VideoEmbedInnerWeb
|
||||||
@@ -173,7 +171,9 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.pt_xs]}>
|
<View
|
||||||
|
style={[a.pt_xs]}
|
||||||
|
onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}>
|
||||||
<ViewportObserver
|
<ViewportObserver
|
||||||
sendPosition={isGif ? noop : sendPosition}
|
sendPosition={isGif ? noop : sendPosition}
|
||||||
isAnyViewActive={currentActiveView !== null}>
|
isAnyViewActive={currentActiveView !== null}>
|
||||||
|
|||||||
Reference in New Issue
Block a user