// 自适应图片:统一生成后端变体地址(GET /api/img?u=&w=)。 // 宽度白名单与后端 backend/service/image_variants.go 同值,约 1.5x 步进。 // 源内容不可变(随机哈希文件名),变体可被浏览器/CDN 长期 immutable 缓存。 /** 宽度白名单(48~1920) */ export const VARIANT_WIDTHS = [48, 96, 160, 256, 384, 512, 768, 1080, 1536, 1920] as const; /** * pickVariantWidth 选「≥ 需求」的最小白名单档(保证清晰度); * 需求超过最大档时回落最大档(源图不够大时后端会原样透传,不会放大)。 */ export function pickVariantWidth(cssPx: number, dpr = 1): number { const need = Math.max(1, Math.round(cssPx * dpr)); for (const w of VARIANT_WIDTHS) { if (w >= need) return w; } return VARIANT_WIDTHS[VARIANT_WIDTHS.length - 1]; } /** variantURL 指定宽度的变体地址 */ export function variantURL(src: string, w: number): string { return `/api/img?u=${encodeURIComponent(src)}&w=${w}`; } /** isOwnImage 是否本站图片(/uploads 本地文件或 /api/media 对象),只有本站图能走变体端点 */ export function isOwnImage(src: string): boolean { return src.startsWith("/uploads/") || src.startsWith("/api/media/"); } /** * densitySrcSet 固定尺寸位的密度 srcset(如头像、固定广告位): * 返回 "url 1x, url 2x";不同 DPR 选中同档时自动去重。 */ export function densitySrcSet(src: string, cssPx: number, dprs: number[] = [1, 2]): string { const seen = new Set(); const parts: string[] = []; for (const dpr of dprs) { const w = pickVariantWidth(cssPx, dpr); if (seen.has(w)) continue; seen.add(w); parts.push(`${variantURL(src, w)} ${dpr}x`); } return parts.join(", "); } /** * widthSrcSet 可变宽度位的宽度 srcset(如封面、正文图),配合 sizes 使用。 * widths 请直接传白名单档位。 */ export function widthSrcSet(src: string, widths: readonly number[]): string { return widths.map((w) => `${variantURL(src, w)} ${w}w`).join(", "); }