| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- import React, { useEffect, useLayoutEffect, useState, useMemo, useRef, useCallback } from 'react'
- import history from '@/utils/history'
- import styles from './index.module.scss'
- import A0_home_1 from '@/assets/img/A0_home_1.jpg'
- import A0_home_2 from '@/assets/img/A0_home_2.jpg'
- import A0_home_3 from '@/assets/img/A0_home_3.jpg'
- import homeLogo from '@/assets/img/A0_home_logo.png'
- import { baseOssUrl } from '@/utils/http'
- const VIDEO_URL = `${baseOssUrl}myData/media/siji.mp4`
- const MIN_STAY_MS = 3000 // 至少停留3秒
- const EARLY_NAVIGATE_PROGRESS = 50 // 实际下载到50%即可尝试跳转(后台继续下载完整视频)
- function A0base() {
- const [currentIndex, setCurrentIndex] = useState(0)
- const [progress, setProgress] = useState(0)
- const [assetsReady, setAssetsReady] = useState(false)
- const images = useMemo(() => [A0_home_1, A0_home_2, A0_home_3], [])
- const slide0Ref = useRef<HTMLImageElement>(null)
- const slide1Ref = useRef<HTMLImageElement>(null)
- const slide2Ref = useRef<HTMLImageElement>(null)
- const logoRef = useRef<HTMLImageElement>(null)
- const pendingRef = useRef(4)
- const hasNavigatedRef = useRef(false)
- const markOneReady = useCallback(() => {
- pendingRef.current -= 1
- if (pendingRef.current <= 0) setAssetsReady(true)
- }, [])
- // 图片加载
- useDomImageReady(slide0Ref, markOneReady)
- useDomImageReady(slide1Ref, markOneReady)
- useDomImageReady(slide2Ref, markOneReady)
- useDomImageReady(logoRef, markOneReady)
- useEffect(() => {
- if (!assetsReady) return
- const abortController = new AbortController()
- let rafId: number | null = null
- let timeoutId: number | null = null
- let minStayReached = false
- let reachedNavigateProgress = false
- const startTime = performance.now()
- const goHomeOnce = () => {
- if (hasNavigatedRef.current) return
- hasNavigatedRef.current = true
- history.push('/home')
- }
- const tryGoHome = () => {
- if (minStayReached && reachedNavigateProgress) goHomeOnce()
- }
- // 最小停留时间
- timeoutId = window.setTimeout(() => {
- minStayReached = true
- tryGoHome()
- }, MIN_STAY_MS)
- // 3秒后平滑动画到100%
- const animateTo100 = () => {
- const elapsed = performance.now() - startTime
- if (elapsed >= MIN_STAY_MS) {
- setProgress(100)
- return
- }
- const next = Math.min(99, Math.round(60 + (elapsed / MIN_STAY_MS) * 40))
- setProgress(next)
- rafId = requestAnimationFrame(animateTo100)
- }
- const preloadFullVideo = async () => {
- try {
- const response = await fetch(VIDEO_URL, {
- signal: abortController.signal,
- cache: 'force-cache'
- })
- if (!response.ok) throw new Error('Fetch failed')
- const contentLength = response.headers.get('content-length')
- const total = contentLength ? parseInt(contentLength, 10) : 0
- if (!total) {
- handleFallback()
- return
- }
- const reader = response.body?.getReader()
- if (!reader) throw new Error('No reader')
- let loaded = 0
- while (true) {
- const { done, value } = await reader.read()
- if (done) break
- loaded += value.length
- const realProgress = Math.floor((loaded / total) * 100)
- // 显示进度策略:
- // 前3秒内最多显示60%,之后显示真实下载进度
- const displayProgress =
- performance.now() - startTime < MIN_STAY_MS ? Math.min(realProgress, 60) : realProgress
- setProgress(displayProgress)
- // 下载到50%即可尝试跳转(但后台继续完整下载)
- if (realProgress >= EARLY_NAVIGATE_PROGRESS) {
- reachedNavigateProgress = true
- tryGoHome()
- }
- }
- // 完整下载完成
- reachedNavigateProgress = true
- const elapsed = performance.now() - startTime
- if (elapsed < MIN_STAY_MS) {
- setProgress(60)
- rafId = requestAnimationFrame(animateTo100)
- } else {
- setProgress(100)
- }
- tryGoHome()
- } catch (err: any) {
- if (err.name !== 'AbortError') {
- console.error('Video preload failed:', err)
- }
- setProgress(100)
- reachedNavigateProgress = true
- tryGoHome()
- }
- }
- const handleFallback = () => {
- setProgress(60)
- rafId = requestAnimationFrame(animateTo100)
- reachedNavigateProgress = true
- tryGoHome()
- }
- preloadFullVideo()
- return () => {
- abortController.abort()
- if (timeoutId) clearTimeout(timeoutId)
- if (rafId) cancelAnimationFrame(rafId)
- }
- }, [assetsReady])
- // 进度切换背景图
- useEffect(() => {
- if (progress >= 65) setCurrentIndex(2)
- else if (progress >= 30) setCurrentIndex(1)
- else setCurrentIndex(0)
- }, [progress])
- return (
- <div className={styles.A0base}>
- {images.map((src, index) => (
- <img
- key={src}
- ref={index === 0 ? slide0Ref : index === 1 ? slide1Ref : slide2Ref}
- src={src}
- alt={`slide ${index + 1}`}
- className={`${styles.slide} ${index === currentIndex ? styles.active : ''}`}
- />
- ))}
- <div className={styles.homeLogo}>
- <img ref={logoRef} src={homeLogo} alt='home logo' />
- <div className='process'>{Math.floor(progress)}%</div>
- </div>
- </div>
- )
- }
- function useDomImageReady(ref: React.RefObject<HTMLImageElement | null>, onReady: () => void) {
- const onReadyRef = useRef(onReady)
- onReadyRef.current = onReady
- const firedRef = useRef(false)
- useLayoutEffect(() => {
- const el = ref.current
- if (!el) return
- const fire = () => {
- if (firedRef.current) return
- firedRef.current = true
- void (async () => {
- try {
- if (typeof el.decode === 'function') await el.decode()
- } catch {}
- requestAnimationFrame(() => requestAnimationFrame(() => onReadyRef.current()))
- })()
- }
- if (el.complete && el.naturalWidth > 0) {
- fire()
- } else {
- el.addEventListener('load', fire, { once: true })
- el.addEventListener('error', fire, { once: true })
- }
- return () => {
- el.removeEventListener('load', fire)
- el.removeEventListener('error', fire)
- }
- }, [ref])
- }
- const MemoA0base = React.memo(A0base)
- export default MemoA0base
|