一个获取 LOGO 位置的组件
一个获取 LOGO 位置的组件
来源:
1"use client";2import { getDomain } from "@/lib/utils";3import { useEffect, useState } from "react";45interface IProps {6 url: string;7 size?: number;8 className?: string;9 timeout?: number;10}1112const WebsiteLogo = ({13 url,14 size = 32,15 className = "",16 timeout = 1000, // 1 second17}: IProps) => {18 const domain = getDomain(url);19 const [imgSrc, setImgSrc] = useState(`https://${domain}/logo.svg`);20 const [fallbackIndex, setFallbackIndex] = useState(0);21 const [isLoading, setIsLoading] = useState(true);22 const [hasError, setHasError] = useState(false);2324 const fallbackSources = [25 `https://${domain}/logo.svg`,26 `https://${domain}/logo.png`,27 `https://${domain}/apple-touch-icon.png`,28 `https://${domain}/apple-touch-icon-precomposed.png`,29 `https://www.google.com/s2/favicons?domain=${domain}&sz=64`,30 `https://icons.duckduckgo.com/ip3/${domain}.ico`,31 `https://${domain}/favicon.ico`,32 ];3334 useEffect(() => {35 let timeoutId: any;3637 if (isLoading) {38 timeoutId = setTimeout(() => {39 handleError();40 }, timeout);41 }4243 return () => {44 if (timeoutId) {45 clearTimeout(timeoutId);46 }47 };48 }, [imgSrc, isLoading]);4950 const handleError = () => {51 const nextIndex = fallbackIndex + 1;52 if (nextIndex < fallbackSources.length) {53 setFallbackIndex(nextIndex);54 setImgSrc(fallbackSources[nextIndex]);55 setIsLoading(true);56 } else {57 setHasError(true);58 setIsLoading(false);59 }60 };6162 const handleLoad = () => {63 setIsLoading(false);64 setHasError(false);65 };6667 return (68 <div69 className={`relative inline-block ${className}`}70 style={{ width: size, height: size }}71 >72 {/* placeholder */}73 {isLoading && (74 <div className="absolute inset-0 animate-pulse">75 <div className="w-full h-full rounded-md bg-gray-200/60" />76 </div>77 )}7879 <img80 src={imgSrc}81 alt={`${domain} logo`}82 width={size}83 height={size}84 onError={handleError}85 onLoad={handleLoad}86 className={`inline-block transition-opacity duration-300 ${87 isLoading ? "opacity-0" : "opacity-100"88 }`}89 style={{90 objectFit: "contain",91 display: hasError ? "none" : "inline-block",92 }}93 />9495 {/* Fallback: Display first letter of domain when all image sources fail */}96 {hasError && (97 <div98 className="w-full h-full flex items-center justify-center bg-gray-100 rounded-md"99 style={{ fontSize: `${size * 0.5}px` }}100 >101 {domain.charAt(0).toUpperCase()}102 </div>103 )}104 </div>105 );106};107108export default WebsiteLogo;