[Web] Fix splash flicker and add animation (#9860)

* manually hide non-React splash logo

* tweak comment

* animate out splash
This commit is contained in:
Samuel Newman
2026-02-12 01:52:22 +00:00
committed by GitHub
parent 93d2f3a8db
commit 8c4820b1f1
4 changed files with 151 additions and 84 deletions
+4 -4
View File
@@ -148,11 +148,11 @@
</head> </head>
<body> <body>
{%- block body_all %} {%- block body_all %}
<div id="splash">
<!-- Bluesky SVG -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 57"><path fill="#006AFF" d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"/></svg>
</div>
<div id="root"> <div id="root">
<div id="splash">
<!-- Bluesky SVG -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 57"><path fill="#006AFF" d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"/></svg>
</div>
</div> </div>
<noscript> <noscript>
+61 -62
View File
@@ -2,7 +2,7 @@ import '#/logger/sentry/setup' // must be near top
import '#/view/icons' import '#/view/icons'
import './style.css' import './style.css'
import React, {useEffect, useState} from 'react' import {Fragment, useEffect, useState} from 'react'
import {SafeAreaProvider} from 'react-native-safe-area-context' import {SafeAreaProvider} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -81,7 +81,7 @@ prefetchAgeAssuranceConfig()
prefetchLiveEvents() prefetchLiveEvents()
function InnerApp() { function InnerApp() {
const [isReady, setIsReady] = React.useState(false) const [isReady, setIsReady] = useState(false)
const {currentAccount} = useSession() const {currentAccount} = useSession()
const {resumeSession} = useSessionApi() const {resumeSession} = useSessionApi()
const theme = useColorModeTheme() const theme = useColorModeTheme()
@@ -116,68 +116,67 @@ function InnerApp() {
}) })
}, [_]) }, [_])
// wait for session to resume
if (!isReady || !hasCheckedReferrer) return <Splash isReady />
return ( return (
<Alf theme={theme}> <Alf theme={theme}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<ContextMenuProvider> <ContextMenuProvider>
<VideoVolumeProvider> <Splash isReady={isReady && hasCheckedReferrer}>
<ActiveVideoProvider> <VideoVolumeProvider>
<React.Fragment <ActiveVideoProvider>
// Resets the entire tree below when it changes: <Fragment
key={currentAccount?.did}> // Resets the entire tree below when it changes:
<AnalyticsFeaturesContext> key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}> <AnalyticsFeaturesContext>
<PolicyUpdateOverlayProvider> <QueryProvider currentDid={currentAccount?.did}>
<LiveEventsProvider> <PolicyUpdateOverlayProvider>
<AgeAssuranceV2Provider> <LiveEventsProvider>
<ComposerProvider> <AgeAssuranceV2Provider>
<MessagesProvider> <ComposerProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */} <MessagesProvider>
<LabelDefsProvider> {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<ModerationOptsProvider> <LabelDefsProvider>
<LoggedOutViewProvider> <ModerationOptsProvider>
<SelectedFeedProvider> <LoggedOutViewProvider>
<HiddenRepliesProvider> <SelectedFeedProvider>
<HomeBadgeProvider> <HiddenRepliesProvider>
<UnreadNotifsProvider> <HomeBadgeProvider>
<BackgroundNotificationPreferencesProvider> <UnreadNotifsProvider>
<MutedThreadsProvider> <BackgroundNotificationPreferencesProvider>
<SafeAreaProvider> <MutedThreadsProvider>
<ProgressGuideProvider> <SafeAreaProvider>
<ServiceConfigProvider> <ProgressGuideProvider>
<EmailVerificationProvider> <ServiceConfigProvider>
<HideBottomBarBorderProvider> <EmailVerificationProvider>
<IntentDialogProvider> <HideBottomBarBorderProvider>
<Shell /> <IntentDialogProvider>
<ToastOutlet /> <Shell />
</IntentDialogProvider> <ToastOutlet />
</HideBottomBarBorderProvider> </IntentDialogProvider>
</EmailVerificationProvider> </HideBottomBarBorderProvider>
</ServiceConfigProvider> </EmailVerificationProvider>
</ProgressGuideProvider> </ServiceConfigProvider>
</SafeAreaProvider> </ProgressGuideProvider>
</MutedThreadsProvider> </SafeAreaProvider>
</BackgroundNotificationPreferencesProvider> </MutedThreadsProvider>
</UnreadNotifsProvider> </BackgroundNotificationPreferencesProvider>
</HomeBadgeProvider> </UnreadNotifsProvider>
</HiddenRepliesProvider> </HomeBadgeProvider>
</SelectedFeedProvider> </HiddenRepliesProvider>
</LoggedOutViewProvider> </SelectedFeedProvider>
</ModerationOptsProvider> </LoggedOutViewProvider>
</LabelDefsProvider> </ModerationOptsProvider>
</MessagesProvider> </LabelDefsProvider>
</ComposerProvider> </MessagesProvider>
</AgeAssuranceV2Provider> </ComposerProvider>
</LiveEventsProvider> </AgeAssuranceV2Provider>
</PolicyUpdateOverlayProvider> </LiveEventsProvider>
</QueryProvider> </PolicyUpdateOverlayProvider>
</AnalyticsFeaturesContext> </QueryProvider>
</React.Fragment> </AnalyticsFeaturesContext>
</ActiveVideoProvider> </Fragment>
</VideoVolumeProvider> </ActiveVideoProvider>
</VideoVolumeProvider>
</Splash>
</ContextMenuProvider> </ContextMenuProvider>
</ThemeProvider> </ThemeProvider>
</Alf> </Alf>
@@ -187,14 +186,14 @@ function InnerApp() {
function App() { function App() {
const [isReady, setReady] = useState(false) const [isReady, setReady] = useState(false)
React.useEffect(() => { useEffect(() => {
Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() => Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() =>
setReady(true), setReady(true),
) )
}, []) }, [])
if (!isReady) { if (!isReady) {
return <Splash isReady /> return null
} }
/* /*
+82 -14
View File
@@ -4,26 +4,94 @@
* the app is ready to go. * the app is ready to go.
*/ */
import {View} from 'react-native' import {useEffect, useRef, useState} from 'react'
import Svg, {Path} from 'react-native-svg' import Svg, {Path} from 'react-native-svg'
import {atoms as a} from '#/alf' import {atoms as a, flatten} from '#/alf'
const size = 100 const size = 100
const ratio = 57 / 64 const ratio = 57 / 64
export function Splash() { export function Splash({
isReady,
children,
}: React.PropsWithChildren<{
isReady: boolean
}>) {
const [isAnimationComplete, setIsAnimationComplete] = useState(false)
const splashRef = useRef<HTMLDivElement>(null)
// hide the static one that's baked into the HTML - gets replaced by our React version below
useEffect(() => {
// double rAF ensures that the React version gets painted first
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const splash = document.getElementById('splash')
if (splash) {
splash.remove()
}
})
})
}, [])
// when ready, we fade/scale out
useEffect(() => {
if (!isReady) return
const reduceMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)',
).matches
const node = splashRef.current
if (!node || reduceMotion) {
setIsAnimationComplete(true)
return
}
const animation = node.animate(
[
{opacity: 1, transform: 'scale(1)'},
{opacity: 0, transform: 'scale(1.5)'},
],
{
duration: 300,
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
fill: 'forwards',
},
)
animation.onfinish = () => setIsAnimationComplete(true)
return () => {
animation.cancel()
}
}, [isReady])
return ( return (
<View style={[a.fixed, a.inset_0, a.align_center, a.justify_center]}> <>
<Svg {isReady && children}
fill="none"
viewBox="0 0 64 57" {!isAnimationComplete && (
style={[a.relative, {width: size, height: size * ratio, top: -50}]}> <div
<Path ref={splashRef}
fill="#006AFF" style={flatten([
d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z" a.fixed,
/> a.inset_0,
</Svg> a.flex,
</View> a.align_center,
a.justify_center,
// to compensate for the `top: -50px` below
{transformOrigin: 'center calc(50% - 50px)'},
])}>
<Svg
fill="none"
viewBox="0 0 64 57"
style={[a.relative, {width: size, height: size * ratio, top: -50}]}>
<Path
fill="#006AFF"
d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"
/>
</Svg>
</div>
)}
</>
) )
} }
+4 -4
View File
@@ -189,11 +189,11 @@
</noscript> </noscript>
<!-- The root element for your Expo app. --> <!-- The root element for your Expo app. -->
<div id="splash">
<!-- Bluesky SVG -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 57"><path fill="#006AFF" d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"/></svg>
</div>
<div id="root"> <div id="root">
<div id="splash">
<!-- Bluesky SVG -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 57"><path fill="#006AFF" d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"/></svg>
</div>
</div> </div>
</body> </body>
</html> </html>