Bluesky app fork with some witchin' additions 💫 witchsky.app
bluesky fork client

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

* manually hide non-React splash logo

* tweak comment

* animate out splash

authored by samuel.fm and committed by

GitHub 8c4820b1 93d2f3a8

+151 -84
+4 -4
bskyweb/templates/base.html
··· 148 148 </head> 149 149 <body> 150 150 {%- block body_all %} 151 + <div id="splash"> 152 + <!-- Bluesky SVG --> 153 + <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> 154 + </div> 151 155 <div id="root"> 152 - <div id="splash"> 153 - <!-- Bluesky SVG --> 154 - <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> 155 - </div> 156 156 </div> 157 157 158 158 <noscript>
+61 -62
src/App.web.tsx
··· 2 2 import '#/view/icons' 3 3 import './style.css' 4 4 5 - import React, {useEffect, useState} from 'react' 5 + import {Fragment, useEffect, useState} from 'react' 6 6 import {SafeAreaProvider} from 'react-native-safe-area-context' 7 7 import {msg} from '@lingui/macro' 8 8 import {useLingui} from '@lingui/react' ··· 81 81 prefetchLiveEvents() 82 82 83 83 function InnerApp() { 84 - const [isReady, setIsReady] = React.useState(false) 84 + const [isReady, setIsReady] = useState(false) 85 85 const {currentAccount} = useSession() 86 86 const {resumeSession} = useSessionApi() 87 87 const theme = useColorModeTheme() ··· 116 116 }) 117 117 }, [_]) 118 118 119 - // wait for session to resume 120 - if (!isReady || !hasCheckedReferrer) return <Splash isReady /> 121 - 122 119 return ( 123 120 <Alf theme={theme}> 124 121 <ThemeProvider theme={theme}> 125 122 <ContextMenuProvider> 126 - <VideoVolumeProvider> 127 - <ActiveVideoProvider> 128 - <React.Fragment 129 - // Resets the entire tree below when it changes: 130 - key={currentAccount?.did}> 131 - <AnalyticsFeaturesContext> 132 - <QueryProvider currentDid={currentAccount?.did}> 133 - <PolicyUpdateOverlayProvider> 134 - <LiveEventsProvider> 135 - <AgeAssuranceV2Provider> 136 - <ComposerProvider> 137 - <MessagesProvider> 138 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 139 - <LabelDefsProvider> 140 - <ModerationOptsProvider> 141 - <LoggedOutViewProvider> 142 - <SelectedFeedProvider> 143 - <HiddenRepliesProvider> 144 - <HomeBadgeProvider> 145 - <UnreadNotifsProvider> 146 - <BackgroundNotificationPreferencesProvider> 147 - <MutedThreadsProvider> 148 - <SafeAreaProvider> 149 - <ProgressGuideProvider> 150 - <ServiceConfigProvider> 151 - <EmailVerificationProvider> 152 - <HideBottomBarBorderProvider> 153 - <IntentDialogProvider> 154 - <Shell /> 155 - <ToastOutlet /> 156 - </IntentDialogProvider> 157 - </HideBottomBarBorderProvider> 158 - </EmailVerificationProvider> 159 - </ServiceConfigProvider> 160 - </ProgressGuideProvider> 161 - </SafeAreaProvider> 162 - </MutedThreadsProvider> 163 - </BackgroundNotificationPreferencesProvider> 164 - </UnreadNotifsProvider> 165 - </HomeBadgeProvider> 166 - </HiddenRepliesProvider> 167 - </SelectedFeedProvider> 168 - </LoggedOutViewProvider> 169 - </ModerationOptsProvider> 170 - </LabelDefsProvider> 171 - </MessagesProvider> 172 - </ComposerProvider> 173 - </AgeAssuranceV2Provider> 174 - </LiveEventsProvider> 175 - </PolicyUpdateOverlayProvider> 176 - </QueryProvider> 177 - </AnalyticsFeaturesContext> 178 - </React.Fragment> 179 - </ActiveVideoProvider> 180 - </VideoVolumeProvider> 123 + <Splash isReady={isReady && hasCheckedReferrer}> 124 + <VideoVolumeProvider> 125 + <ActiveVideoProvider> 126 + <Fragment 127 + // Resets the entire tree below when it changes: 128 + key={currentAccount?.did}> 129 + <AnalyticsFeaturesContext> 130 + <QueryProvider currentDid={currentAccount?.did}> 131 + <PolicyUpdateOverlayProvider> 132 + <LiveEventsProvider> 133 + <AgeAssuranceV2Provider> 134 + <ComposerProvider> 135 + <MessagesProvider> 136 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 137 + <LabelDefsProvider> 138 + <ModerationOptsProvider> 139 + <LoggedOutViewProvider> 140 + <SelectedFeedProvider> 141 + <HiddenRepliesProvider> 142 + <HomeBadgeProvider> 143 + <UnreadNotifsProvider> 144 + <BackgroundNotificationPreferencesProvider> 145 + <MutedThreadsProvider> 146 + <SafeAreaProvider> 147 + <ProgressGuideProvider> 148 + <ServiceConfigProvider> 149 + <EmailVerificationProvider> 150 + <HideBottomBarBorderProvider> 151 + <IntentDialogProvider> 152 + <Shell /> 153 + <ToastOutlet /> 154 + </IntentDialogProvider> 155 + </HideBottomBarBorderProvider> 156 + </EmailVerificationProvider> 157 + </ServiceConfigProvider> 158 + </ProgressGuideProvider> 159 + </SafeAreaProvider> 160 + </MutedThreadsProvider> 161 + </BackgroundNotificationPreferencesProvider> 162 + </UnreadNotifsProvider> 163 + </HomeBadgeProvider> 164 + </HiddenRepliesProvider> 165 + </SelectedFeedProvider> 166 + </LoggedOutViewProvider> 167 + </ModerationOptsProvider> 168 + </LabelDefsProvider> 169 + </MessagesProvider> 170 + </ComposerProvider> 171 + </AgeAssuranceV2Provider> 172 + </LiveEventsProvider> 173 + </PolicyUpdateOverlayProvider> 174 + </QueryProvider> 175 + </AnalyticsFeaturesContext> 176 + </Fragment> 177 + </ActiveVideoProvider> 178 + </VideoVolumeProvider> 179 + </Splash> 181 180 </ContextMenuProvider> 182 181 </ThemeProvider> 183 182 </Alf> ··· 187 186 function App() { 188 187 const [isReady, setReady] = useState(false) 189 188 190 - React.useEffect(() => { 189 + useEffect(() => { 191 190 Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(() => 192 191 setReady(true), 193 192 ) 194 193 }, []) 195 194 196 195 if (!isReady) { 197 - return <Splash isReady /> 196 + return null 198 197 } 199 198 200 199 /*
+82 -14
src/Splash.web.tsx
··· 4 4 * the app is ready to go. 5 5 */ 6 6 7 - import {View} from 'react-native' 7 + import {useEffect, useRef, useState} from 'react' 8 8 import Svg, {Path} from 'react-native-svg' 9 9 10 - import {atoms as a} from '#/alf' 10 + import {atoms as a, flatten} from '#/alf' 11 11 12 12 const size = 100 13 13 const ratio = 57 / 64 14 14 15 - export function Splash() { 15 + export function Splash({ 16 + isReady, 17 + children, 18 + }: React.PropsWithChildren<{ 19 + isReady: boolean 20 + }>) { 21 + const [isAnimationComplete, setIsAnimationComplete] = useState(false) 22 + const splashRef = useRef<HTMLDivElement>(null) 23 + 24 + // hide the static one that's baked into the HTML - gets replaced by our React version below 25 + useEffect(() => { 26 + // double rAF ensures that the React version gets painted first 27 + requestAnimationFrame(() => { 28 + requestAnimationFrame(() => { 29 + const splash = document.getElementById('splash') 30 + if (splash) { 31 + splash.remove() 32 + } 33 + }) 34 + }) 35 + }, []) 36 + 37 + // when ready, we fade/scale out 38 + useEffect(() => { 39 + if (!isReady) return 40 + 41 + const reduceMotion = window.matchMedia( 42 + '(prefers-reduced-motion: reduce)', 43 + ).matches 44 + const node = splashRef.current 45 + if (!node || reduceMotion) { 46 + setIsAnimationComplete(true) 47 + return 48 + } 49 + 50 + const animation = node.animate( 51 + [ 52 + {opacity: 1, transform: 'scale(1)'}, 53 + {opacity: 0, transform: 'scale(1.5)'}, 54 + ], 55 + { 56 + duration: 300, 57 + easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', 58 + fill: 'forwards', 59 + }, 60 + ) 61 + animation.onfinish = () => setIsAnimationComplete(true) 62 + 63 + return () => { 64 + animation.cancel() 65 + } 66 + }, [isReady]) 67 + 16 68 return ( 17 - <View style={[a.fixed, a.inset_0, a.align_center, a.justify_center]}> 18 - <Svg 19 - fill="none" 20 - viewBox="0 0 64 57" 21 - style={[a.relative, {width: size, height: size * ratio, top: -50}]}> 22 - <Path 23 - fill="#006AFF" 24 - 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" 25 - /> 26 - </Svg> 27 - </View> 69 + <> 70 + {isReady && children} 71 + 72 + {!isAnimationComplete && ( 73 + <div 74 + ref={splashRef} 75 + style={flatten([ 76 + a.fixed, 77 + a.inset_0, 78 + a.flex, 79 + a.align_center, 80 + a.justify_center, 81 + // to compensate for the `top: -50px` below 82 + {transformOrigin: 'center calc(50% - 50px)'}, 83 + ])}> 84 + <Svg 85 + fill="none" 86 + viewBox="0 0 64 57" 87 + style={[a.relative, {width: size, height: size * ratio, top: -50}]}> 88 + <Path 89 + fill="#006AFF" 90 + 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" 91 + /> 92 + </Svg> 93 + </div> 94 + )} 95 + </> 28 96 ) 29 97 }
+4 -4
web/index.html
··· 189 189 </noscript> 190 190 191 191 <!-- The root element for your Expo app. --> 192 + <div id="splash"> 193 + <!-- Bluesky SVG --> 194 + <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> 195 + </div> 192 196 <div id="root"> 193 - <div id="splash"> 194 - <!-- Bluesky SVG --> 195 - <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> 196 - </div> 197 197 </div> 198 198 </body> 199 199 </html>