Skip to content

Commit 5880386

Browse files
committed
feat(ui): add WebGL fallback with gradient background
Implement graceful degradation for devices without WebGL support: - Add WebGL detection utility (isWebGLSupported, getWebGLVersion) - Create GradientTechBackground component as CSS-based fallback - Enhance AmbientLightBg with error handling and fallback events - Update ChromeBrowserBackground with auto-fallback logic When WebGL is unavailable or initialization fails, the system automatically switches to CSS gradient background, maintaining visual consistency and user experience.
1 parent 85acf8b commit 5880386

File tree

5 files changed

+246
-44
lines changed

5 files changed

+246
-44
lines changed

electron-builder.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ win:
4141
icon: assets/icons/logo.png
4242
target:
4343
- nsis
44+
- zip
4445
signDlls: false
4546
artifactName: ${name}-${version}-${os}-${arch}.${ext}
4647

src/components/fellou/AmbientLightBg.tsx

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,35 @@ export function AnimatedBackground({
4848
const handleLoaded = () => {
4949
if (isDestroyed || !containerRef.current || !window.AmbientLightBg) return;
5050

51-
// 创建 AmbientLightBg 实例
52-
colorBgRef.current = new window.AmbientLightBg({
53-
dom: containerRef.current.id,
54-
colors: ["#004880", "#CDCFCF", "#3390C0", "#2B71A1", "#005A92", "#004880"],
55-
//["#007FFE","#3099FE","#60B2FE","#90CCFE","#a8d3f0","#b7e1df"],
56-
loop: true,
57-
speed: speed,
58-
});
59-
60-
// 应用自定义参数
61-
if (colorBgRef.current && colorBgRef.current.update) {
62-
// Scale 参数对应 pattern scale
63-
colorBgRef.current.update("pattern scale", scale);
64-
65-
// Blur 参数对应 edge blur
66-
colorBgRef.current.update("edge blur", blur);
67-
68-
// Noise 参数
69-
colorBgRef.current.update("noise", noise);
51+
try {
52+
// 创建 AmbientLightBg 实例
53+
colorBgRef.current = new window.AmbientLightBg({
54+
dom: containerRef.current.id,
55+
colors: ["#004880", "#CDCFCF", "#3390C0", "#2B71A1", "#005A92", "#004880"],
56+
//["#007FFE","#3099FE","#60B2FE","#90CCFE","#a8d3f0","#b7e1df"],
57+
loop: true,
58+
speed: speed,
59+
});
60+
61+
// 应用自定义参数
62+
if (colorBgRef.current && colorBgRef.current.update) {
63+
// Scale 参数对应 pattern scale
64+
colorBgRef.current.update("pattern scale", scale);
65+
66+
// Blur 参数对应 edge blur
67+
colorBgRef.current.update("edge blur", blur);
68+
69+
// Noise 参数
70+
colorBgRef.current.update("noise", noise);
71+
}
72+
} catch (error) {
73+
// Handle initialization error (likely WebGL not supported)
74+
console.error('AmbientLightBg initialization failed:', error);
75+
76+
// Trigger fallback event to notify parent component
77+
window.dispatchEvent(new CustomEvent('ambientBgError', {
78+
detail: { error }
79+
}));
7080
}
7181
};
7282

src/components/fellou/ChromeBrowserBackground.tsx

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,46 @@
33
import { motion } from "framer-motion";
44
import { useState, useEffect } from "react";
55
import { AnimatedBackground } from "./AmbientLightBg";
6+
import { GradientTechBackground } from "./GradientTechBackground";
7+
import { isWebGLSupported } from "@/utils/webglDetect";
68

79
export function ChromeBrowserBackground() {
810
const [backgroundReady, setBackgroundReady] = useState(false);
11+
const [webglSupported, setWebglSupported] = useState<boolean | null>(null);
12+
const [useFallback, setUseFallback] = useState(false);
13+
14+
// Detect WebGL support on mount
15+
useEffect(() => {
16+
const supported = isWebGLSupported();
17+
setWebglSupported(supported);
18+
19+
if (!supported) {
20+
console.warn('WebGL not supported, using fallback gradient background');
21+
setUseFallback(true);
22+
setBackgroundReady(true);
23+
}
24+
}, []);
925

1026
// Listen for background animation ready state
1127
useEffect(() => {
1228
const handleBackgroundReady = () => {
1329
setBackgroundReady(true);
1430
};
1531

32+
const handleBackgroundError = (event: Event) => {
33+
const customEvent = event as CustomEvent;
34+
console.error('AnimatedBackground failed, falling back to gradient:', customEvent.detail?.error);
35+
setUseFallback(true);
36+
setBackgroundReady(true);
37+
};
38+
1639
// Listen for ambient background loaded event
1740
window.addEventListener("ambientBgLoaded", handleBackgroundReady);
41+
window.addEventListener("ambientBgError", handleBackgroundError);
1842

1943
return () => {
2044
window.removeEventListener("ambientBgLoaded", handleBackgroundReady);
45+
window.removeEventListener("ambientBgError", handleBackgroundError);
2146
};
2247
}, []);
2348

@@ -26,34 +51,49 @@ export function ChromeBrowserBackground() {
2651
{/* Black background - always visible as fallback */}
2752
<div className="fixed inset-0 bg-black z-0" />
2853

29-
{/* AnimatedBackground with loading-based opacity control */}
30-
<motion.div
31-
className="fixed inset-0 z-0 overflow-hidden"
32-
initial={{ opacity: 0 }}
33-
animate={{ opacity: backgroundReady ? 1 : 0 }}
34-
transition={{ duration: 0.8, ease: "easeInOut" }}
35-
style={{
36-
opacity: backgroundReady ? 1 : 0,
37-
}}
38-
>
39-
{/* Animated background - render at desktop size for mobile consistency */}
40-
<div
41-
className="absolute"
54+
{/* Render background based on WebGL support and fallback state */}
55+
{useFallback ? (
56+
/* Fallback: Gradient Tech Background (no WebGL required) */
57+
<motion.div
58+
className="fixed inset-0 z-0 overflow-hidden"
59+
initial={{ opacity: 0 }}
60+
animate={{ opacity: 1 }}
61+
transition={{ duration: 0.8, ease: "easeInOut" }}
62+
>
63+
<GradientTechBackground />
64+
{/* Black overlay with 40% opacity */}
65+
<div className="absolute inset-0 bg-black/40" />
66+
</motion.div>
67+
) : webglSupported !== false ? (
68+
/* WebGL supported: AnimatedBackground with loading-based opacity control */
69+
<motion.div
70+
className="fixed inset-0 z-0 overflow-hidden"
71+
initial={{ opacity: 0 }}
72+
animate={{ opacity: backgroundReady ? 1 : 0 }}
73+
transition={{ duration: 0.8, ease: "easeInOut" }}
4274
style={{
43-
width: "100vw",
44-
height: "100vh",
45-
minWidth: "1440px", // Desktop width for consistent appearance
46-
minHeight: "900px", // Desktop height for consistent appearance
47-
left: "50%",
48-
top: "50%",
49-
transform: "translate(-50%, -50%)"
75+
opacity: backgroundReady ? 1 : 0,
5076
}}
5177
>
52-
<AnimatedBackground speed={0.25} />
53-
</div>
54-
{/* Black overlay with 40% opacity */}
55-
<div className="absolute inset-0 bg-black/40" />
56-
</motion.div>
78+
{/* Animated background - render at desktop size for mobile consistency */}
79+
<div
80+
className="absolute"
81+
style={{
82+
width: "100vw",
83+
height: "100vh",
84+
minWidth: "1440px", // Desktop width for consistent appearance
85+
minHeight: "900px", // Desktop height for consistent appearance
86+
left: "50%",
87+
top: "50%",
88+
transform: "translate(-50%, -50%)"
89+
}}
90+
>
91+
<AnimatedBackground speed={0.25} />
92+
</div>
93+
{/* Black overlay with 40% opacity */}
94+
<div className="absolute inset-0 bg-black/40" />
95+
</motion.div>
96+
) : null}
5797
</>
5898
);
5999
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { cn } from "./utils";
5+
6+
interface GradientTechBackgroundProps {
7+
className?: string;
8+
}
9+
10+
/**
11+
* Gradient Tech Background Component
12+
* A fallback background for devices without WebGL support
13+
* Uses CSS gradients and animations for a tech-inspired look
14+
*/
15+
export function GradientTechBackground({ className }: GradientTechBackgroundProps) {
16+
return (
17+
<div className={cn("absolute inset-0 overflow-hidden", className)}>
18+
{/* Main gradient background with animation */}
19+
<div className="absolute inset-0 bg-gradient-tech animate-gradient-shift" />
20+
21+
{/* Overlay gradients for depth effect */}
22+
<div className="absolute inset-0 bg-gradient-radial opacity-60" />
23+
24+
{/* Animated glow spots */}
25+
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-500/20 rounded-full blur-3xl animate-pulse-slow" />
26+
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-cyan-500/20 rounded-full blur-3xl animate-pulse-slow animation-delay-2000" />
27+
28+
{/* Subtle grid overlay for tech feel */}
29+
<div className="absolute inset-0 opacity-10">
30+
<div
31+
className="w-full h-full"
32+
style={{
33+
backgroundImage: `
34+
linear-gradient(rgba(255, 255, 255, 0.1) 1px, transparent 1px),
35+
linear-gradient(90deg, rgba(255, 255, 255, 0.1) 1px, transparent 1px)
36+
`,
37+
backgroundSize: '50px 50px'
38+
}}
39+
/>
40+
</div>
41+
42+
{/* Add styles for custom animations */}
43+
<style jsx>{`
44+
@keyframes gradient-shift {
45+
0%, 100% {
46+
background-position: 0% 50%;
47+
}
48+
50% {
49+
background-position: 100% 50%;
50+
}
51+
}
52+
53+
@keyframes pulse-slow {
54+
0%, 100% {
55+
opacity: 0.6;
56+
transform: scale(1);
57+
}
58+
50% {
59+
opacity: 0.8;
60+
transform: scale(1.1);
61+
}
62+
}
63+
64+
.bg-gradient-tech {
65+
background: linear-gradient(
66+
135deg,
67+
#001a33 0%,
68+
#003366 25%,
69+
#004d7a 50%,
70+
#003d5c 75%,
71+
#001a33 100%
72+
);
73+
background-size: 400% 400%;
74+
}
75+
76+
.bg-gradient-radial {
77+
background: radial-gradient(
78+
circle at 50% 50%,
79+
transparent 0%,
80+
rgba(0, 26, 51, 0.8) 100%
81+
);
82+
}
83+
84+
.animate-gradient-shift {
85+
animation: gradient-shift 15s ease infinite;
86+
}
87+
88+
.animate-pulse-slow {
89+
animation: pulse-slow 8s ease-in-out infinite;
90+
}
91+
92+
.animation-delay-2000 {
93+
animation-delay: 2s;
94+
}
95+
`}</style>
96+
</div>
97+
);
98+
}

src/utils/webglDetect.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* WebGL Detection Utility
3+
* Detects if the browser supports WebGL 1.0 or WebGL 2.0
4+
*/
5+
6+
/**
7+
* Check if WebGL is supported in the current browser
8+
* @returns {boolean} true if WebGL is available, false otherwise
9+
*/
10+
export function isWebGLSupported(): boolean {
11+
try {
12+
// Create a temporary canvas element
13+
const canvas = document.createElement('canvas');
14+
15+
// Try to get WebGL context (WebGL 2.0 or 1.0)
16+
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
17+
18+
// If we got a context, WebGL is supported
19+
if (gl && gl instanceof WebGLRenderingContext) {
20+
return true;
21+
}
22+
23+
return false;
24+
} catch (e) {
25+
// If any error occurs during detection, assume WebGL is not supported
26+
console.warn('WebGL detection failed:', e);
27+
return false;
28+
}
29+
}
30+
31+
/**
32+
* Get WebGL version info
33+
* @returns {string} WebGL version or 'Not supported'
34+
*/
35+
export function getWebGLVersion(): string {
36+
try {
37+
const canvas = document.createElement('canvas');
38+
const gl2 = canvas.getContext('webgl2');
39+
40+
if (gl2) {
41+
return 'WebGL 2.0';
42+
}
43+
44+
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
45+
if (gl) {
46+
return 'WebGL 1.0';
47+
}
48+
49+
return 'Not supported';
50+
} catch (e) {
51+
return 'Not supported';
52+
}
53+
}

0 commit comments

Comments
 (0)