"use client"; import { useEffect, useRef, useState, type ReactNode } from "react"; /** 顶部横版槽位的最小门槛:自然宽度低于该值的横版图视为过小,不占顶部布局、归入左栏 */ const MIN_TOP_WIDTH = 600; /** * 封面自适应槽位:同一封面在两个位置各放一个槽位(左栏=竖版、顶部全宽=横版), * 组件用隐藏探测图读取自然宽高,只渲染与自身方向匹配的槽位。 * 竖版槽默认显示(书封多为竖版),探测为横版时两槽自动换位,避免闪空。 * 横版但过小(自然宽度 < MIN_TOP_WIDTH)的图同样只渲染在左栏槽位。 */ export default function AdaptiveCoverSlot({ src, orientation, children, }: { src: string; orientation: "portrait" | "landscape"; children: ReactNode; }) { const ref = useRef(null); const [match, setMatch] = useState(orientation === "portrait"); const measure = (img: HTMLImageElement) => { if (!img.naturalWidth || !img.naturalHeight) return; const landscape = img.naturalWidth > img.naturalHeight; // 仅“横版且足够大”才配得上顶部槽位;过小的横版图随竖版走左栏 const topWorthy = landscape && img.naturalWidth >= MIN_TOP_WIDTH; setMatch(orientation === "landscape" ? topWorthy : !topWorthy); }; useEffect(() => { // 缓存图可能在 hydration 前就完成加载而错过 onLoad if (ref.current?.complete) measure(ref.current); }, []); return ( <> {/* eslint-disable-next-line @next/next/no-img-element */} measure(e.currentTarget)} /> {match ? children : null} ); }