diff --git a/src/components/wallet/wallet-card.test.tsx b/src/components/wallet/wallet-card.test.tsx index 3b53fcb..635a9fa 100644 --- a/src/components/wallet/wallet-card.test.tsx +++ b/src/components/wallet/wallet-card.test.tsx @@ -15,6 +15,11 @@ vi.mock('@/hooks/useCardInteraction', () => ({ }), })) +// Mock useMonochromeMask hook to avoid canvas/image dependency in jsdom +vi.mock('@/hooks/useMonochromeMask', () => ({ + useMonochromeMask: (iconUrl: string | undefined) => (iconUrl ? 'data:image/png;base64,AA==' : null), +})) + const createMockWallet = (overrides: Partial = {}): Wallet => ({ id: 'test-wallet-1', name: '我的钱包', @@ -41,6 +46,11 @@ describe('WalletCard (3D)', () => { address: '0x1234567890abcdef1234567890abcdef12345678', } + const originalUserAgent = navigator.userAgent + const setUserAgent = (ua: string) => { + Object.defineProperty(navigator, 'userAgent', { value: ua, configurable: true }) + } + it('renders wallet name', () => { render() expect(screen.getByRole('heading', { name: '我的钱包' })).toBeInTheDocument() @@ -170,4 +180,28 @@ describe('WalletCard (3D)', () => { expect(ref).toHaveBeenCalled() }) + + it('renders watermark refractions on non-Android when chainIconUrl provided', () => { + setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36') + + render() + + expect(screen.getByTestId('wallet-card-refraction-watermark-1')).toBeInTheDocument() + expect(screen.getByTestId('wallet-card-refraction-watermark-2')).toBeInTheDocument() + + setUserAgent(originalUserAgent) + }) + + it('disables refraction layers by default on Android to avoid flicker', () => { + setUserAgent('Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36') + + render() + + expect(screen.queryByTestId('wallet-card-refraction-pattern-1')).not.toBeInTheDocument() + expect(screen.queryByTestId('wallet-card-refraction-pattern-2')).not.toBeInTheDocument() + expect(screen.queryByTestId('wallet-card-refraction-watermark-1')).not.toBeInTheDocument() + expect(screen.queryByTestId('wallet-card-refraction-watermark-2')).not.toBeInTheDocument() + + setUserAgent(originalUserAgent) + }) }) diff --git a/src/components/wallet/wallet-card.tsx b/src/components/wallet/wallet-card.tsx index b1d2dad..779416f 100644 --- a/src/components/wallet/wallet-card.tsx +++ b/src/components/wallet/wallet-card.tsx @@ -49,6 +49,16 @@ export interface WalletCardProps { onOpenSettings?: (() => void) | undefined; className?: string | undefined; themeHue?: number | undefined; + /** + * 禁用“折射 Refraction”层(Android 部分机型/浏览器组合会出现页面闪动)。 + * 默认:在 Android 浏览器上自动禁用,其它平台启用。 + */ + disableRefraction?: boolean | undefined; + /** + * 是否启用陀螺仪倾斜(deviceorientation)。 + * 默认:在 Android 上关闭(降低高频重绘导致的闪动/掉帧风险)。 + */ + enableGyro?: boolean | undefined; } // 静态样式常量 - 避免每次渲染创建新对象 @@ -68,12 +78,23 @@ export const WalletCard = forwardRef(function W onOpenSettings, className, themeHue = 323, + disableRefraction, + enableGyro, }, ref, ) { const [copied, setCopied] = useState(false); const cardRef = useRef(null); + const isAndroid = useMemo(() => { + if (typeof navigator === 'undefined') return false; + return /Android/i.test(navigator.userAgent); + }, []); + + // Android 上:Refraction + mask + mix-blend-mode + 高频倾斜更新,容易触发浏览器合成层闪动(看起来像整页抖/闪)。 + const disableRefractionEffective = disableRefraction ?? isAndroid; + const enableGyroEffective = enableGyro ?? !isAndroid; + // 将链图标转为单色遮罩(黑白 -> 透明) const monoMaskUrl = useMonochromeMask(chainIconUrl, { size: watermarkLogoActualSize * 2, // 2x for retina @@ -84,6 +105,7 @@ export const WalletCard = forwardRef(function W const { pointerX, pointerY, isActive, bindElement } = useCardInteraction({ gyroStrength: 0.15, touchStrength: 0.8, + enableGyro: enableGyroEffective, }); useEffect(() => { @@ -191,50 +213,60 @@ export const WalletCard = forwardRef(function W transition: isActive ? 'none' : transitionConfig, }} > - {/* Refraction 1: 左下角 */} -
- {/* Refraction 2: 右上角 */} -
+ {!disableRefractionEffective && ( + <> + {/* Refraction 1: 左下角 */} +
+ {/* Refraction 2: 右上角 */} +
+ + )}
{/* 3. 防伪层2:Logo水印 (Watermark) + 双层折射 */} @@ -248,50 +280,60 @@ export const WalletCard = forwardRef(function W transition: isActive ? 'none' : transitionConfig, }} > - {/* Refraction 1: 左下角 */} -
- {/* Refraction 2: 右上角 */} -
+ {!disableRefractionEffective && ( + <> + {/* Refraction 1: 左下角 */} +
+ {/* Refraction 2: 右上角 */} +
+ + )}
)}