mirror of
https://github.com/mue/mue.git
synced 2026-07-16 05:23:49 +02:00
refactor: optimize background loading and rendering logic
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import BackgroundImage from './components/BackgroundImage';
|
||||
import BackgroundVideo from './components/BackgroundVideo';
|
||||
|
||||
|
||||
@@ -115,24 +115,49 @@ function getColourBackground() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets API background with caching
|
||||
* Gets API background with caching and prefetching
|
||||
*/
|
||||
async function getAPIBackground(isOffline) {
|
||||
if (isOffline) return getOfflineImage('api');
|
||||
|
||||
// Use cached next image if available
|
||||
const cached = parseJSON('nextImage');
|
||||
const data = cached || (await fetchAPIImageData());
|
||||
const cachedQueue = parseJSON('imageQueue', []);
|
||||
let data;
|
||||
|
||||
if (cachedQueue.length > 0) {
|
||||
data = cachedQueue.shift();
|
||||
localStorage.setItem('imageQueue', JSON.stringify(cachedQueue));
|
||||
} else {
|
||||
data = await fetchAPIImageData();
|
||||
}
|
||||
|
||||
if (!data) return getOfflineImage('api');
|
||||
|
||||
localStorage.setItem('currentBackground', JSON.stringify(data));
|
||||
localStorage.setItem('nextImage', null);
|
||||
|
||||
// Pre-fetch next image
|
||||
fetchAPIImageData(data.photoInfo.pun).then((next) => {
|
||||
if (next) localStorage.setItem('nextImage', JSON.stringify(next));
|
||||
});
|
||||
// Pre-fetch next 3 images in the background
|
||||
const targetQueueSize = 3;
|
||||
const currentQueueSize = cachedQueue.length;
|
||||
|
||||
if (currentQueueSize < targetQueueSize) {
|
||||
const excludedPuns = [
|
||||
data.photoInfo.pun,
|
||||
...cachedQueue.map((img) => img.photoInfo?.pun).filter(Boolean),
|
||||
];
|
||||
|
||||
// Prefetch remaining images asynchronously
|
||||
Promise.all(
|
||||
Array.from({ length: targetQueueSize - currentQueueSize }, (_, i) =>
|
||||
fetchAPIImageData(excludedPuns[i] || data.photoInfo.pun),
|
||||
),
|
||||
).then((newImages) => {
|
||||
const validImages = newImages.filter(Boolean);
|
||||
if (validImages.length > 0) {
|
||||
const updatedQueue = [...cachedQueue, ...validImages];
|
||||
localStorage.setItem('imageQueue', JSON.stringify(updatedQueue));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -141,7 +166,7 @@ async function getAPIBackground(isOffline) {
|
||||
* Gets custom background
|
||||
*/
|
||||
function getCustomBackground(isOffline) {
|
||||
let backgrounds = parseJSON('customBackground');
|
||||
const backgrounds = parseJSON('customBackground');
|
||||
|
||||
if (backgrounds.length === 0) return null;
|
||||
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
*/
|
||||
export async function createBlobUrl(url) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const response = await fetch(url, {
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
} catch (error) {
|
||||
console.error('Failed to create blob URL:', error);
|
||||
} catch {
|
||||
// Silently fail - we'll use direct URL as fallback
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,19 @@ export function useBackgroundLoader(updateBackground, resetBackground) {
|
||||
|
||||
try {
|
||||
// Check for welcome tab first
|
||||
const welcomeImage = localStorage.getItem('welcomeImage');
|
||||
if (localStorage.getItem('welcomeTab') && welcomeImage) {
|
||||
updateBackground(JSON.parse(welcomeImage));
|
||||
return;
|
||||
const welcomeTab = localStorage.getItem('welcomeTab');
|
||||
if (welcomeTab) {
|
||||
const welcomeImage = localStorage.getItem('welcomeImage');
|
||||
if (welcomeImage) {
|
||||
updateBackground(JSON.parse(welcomeImage));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = await getBackgroundData();
|
||||
if (data) updateBackground(data);
|
||||
if (data) {
|
||||
updateBackground(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load background:', error);
|
||||
} finally {
|
||||
@@ -36,12 +41,14 @@ export function useBackgroundLoader(updateBackground, resetBackground) {
|
||||
// Initial load - only run once on mount
|
||||
useEffect(() => {
|
||||
const changeMode = localStorage.getItem('backgroundchange');
|
||||
if (changeMode === 'refresh' || !changeMode) {
|
||||
const hasStartTime = localStorage.getItem('backgroundStartTime');
|
||||
|
||||
if (!hasStartTime || changeMode === 'refresh') {
|
||||
localStorage.setItem('backgroundStartTime', Date.now());
|
||||
loadBackground();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
loadBackground();
|
||||
}, [loadBackground]);
|
||||
|
||||
return { loadBackground, refreshBackground };
|
||||
}
|
||||
|
||||
@@ -15,12 +15,6 @@ export function useBackgroundRenderer(backgroundData) {
|
||||
const element = document.getElementById('backgroundImage');
|
||||
if (!element) return;
|
||||
|
||||
// Cleanup previous blob
|
||||
if (blobRef.current) {
|
||||
URL.revokeObjectURL(blobRef.current);
|
||||
blobRef.current = null;
|
||||
}
|
||||
|
||||
const applyBackground = async () => {
|
||||
if (backgroundData.url) {
|
||||
const hasTransition = localStorage.getItem('bgtransition') !== 'false';
|
||||
@@ -31,26 +25,77 @@ export function useBackgroundRenderer(backgroundData) {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.background = null;
|
||||
// Create or get overlay element for smooth transitions
|
||||
let overlay = document.getElementById('backgroundOverlay');
|
||||
if (!overlay) {
|
||||
overlay = document.createElement('div');
|
||||
overlay.id = 'backgroundOverlay';
|
||||
// Insert right after the background element
|
||||
element.parentNode.insertBefore(overlay, element.nextSibling);
|
||||
}
|
||||
|
||||
// Apply blur hash placeholder
|
||||
if (backgroundData.photoInfo?.blur_hash && backgroundData.photoInfo?.colour) {
|
||||
// Set background color
|
||||
if (backgroundData.photoInfo?.colour) {
|
||||
element.style.backgroundColor = backgroundData.photoInfo.colour;
|
||||
element.classList.add('fade-in');
|
||||
|
||||
const blurHash = generateBlurHashDataUrl(backgroundData.photoInfo.blur_hash);
|
||||
if (blurHash) element.style.backgroundImage = `url(${blurHash})`;
|
||||
}
|
||||
|
||||
// Load full image
|
||||
// Generate and show blur hash immediately on main element
|
||||
if (backgroundData.photoInfo?.blur_hash) {
|
||||
const blurHashUrl = generateBlurHashDataUrl(backgroundData.photoInfo.blur_hash, 64, 64);
|
||||
if (blurHashUrl) {
|
||||
element.style.backgroundImage = `url(${blurHashUrl})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the full image
|
||||
const blobUrl = await createBlobUrl(backgroundData.url);
|
||||
const finalUrl = blobUrl || backgroundData.url;
|
||||
|
||||
if (blobUrl) {
|
||||
if (blobRef.current) {
|
||||
URL.revokeObjectURL(blobRef.current);
|
||||
}
|
||||
blobRef.current = blobUrl;
|
||||
element.classList.add('backgroundTransform');
|
||||
element.style.backgroundImage = `url(${blobUrl})`;
|
||||
} else {
|
||||
element.style.backgroundImage = `url(${backgroundData.url})`;
|
||||
}
|
||||
|
||||
// Preload the image
|
||||
const img = new Image();
|
||||
img.src = finalUrl;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
img.onerror = resolve;
|
||||
});
|
||||
|
||||
// CRITICAL: Reset overlay to invisible FIRST
|
||||
overlay.style.transition = 'none';
|
||||
overlay.style.opacity = '0';
|
||||
overlay.style.backgroundImage = '';
|
||||
|
||||
// Force a reflow to ensure opacity is actually 0
|
||||
void overlay.offsetHeight;
|
||||
|
||||
// Now set the new image
|
||||
overlay.style.backgroundImage = `url(${finalUrl})`;
|
||||
|
||||
// Force another reflow
|
||||
void overlay.offsetHeight;
|
||||
|
||||
// Re-enable transition
|
||||
overlay.style.transition = 'opacity 1.2s ease-in-out';
|
||||
|
||||
// Force another reflow before changing opacity
|
||||
void overlay.offsetHeight;
|
||||
|
||||
// Now fade it in
|
||||
overlay.style.opacity = '1';
|
||||
|
||||
// After fade completes, swap to main element and reset overlay
|
||||
setTimeout(() => {
|
||||
element.style.backgroundImage = `url(${finalUrl})`;
|
||||
overlay.style.opacity = '0';
|
||||
overlay.style.backgroundImage = '';
|
||||
}, 1300);
|
||||
} else if (backgroundData.style) {
|
||||
element.setAttribute('style', backgroundData.style);
|
||||
}
|
||||
|
||||
@@ -8,22 +8,23 @@
|
||||
background-position: center !important;
|
||||
background-attachment: fixed !important;
|
||||
zoom: 100% !important;
|
||||
position: relative;
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.backgroundTransform {
|
||||
@include animation('fadein2 2s');
|
||||
|
||||
transition: background 2s;
|
||||
}
|
||||
|
||||
@include keyframes(fadein2) {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
#backgroundOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-size: cover !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: center !important;
|
||||
background-attachment: fixed !important;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
transition: opacity 1.2s ease-in-out;
|
||||
}
|
||||
|
||||
.backgroundPreload {
|
||||
@@ -39,7 +40,7 @@
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
@include animation('fadein 1s');
|
||||
@include animation('fadein 0.8s ease-in-out');
|
||||
}
|
||||
|
||||
@include keyframes(fadein) {
|
||||
|
||||
Reference in New Issue
Block a user