update
This commit is contained in:
179
src/components/GalleryLightbox.astro
Normal file
179
src/components/GalleryLightbox.astro
Normal file
@ -0,0 +1,179 @@
|
||||
---
|
||||
// 走秀 / 街拍详情页共享的全屏大图灯箱:左栏缩略图条 + 大图 + 上/下·左/右翻页 + 单图收藏。
|
||||
//
|
||||
// 视觉遵循全站「黑白编辑风」:直角细框、mono 小字 + 宽字距、零填充计数、hover 反色(bg-black + 白字)。
|
||||
// 交互逻辑全部在 src/lib/gallery.ts(createGalleryView);本组件只负责标记。
|
||||
//
|
||||
// 各页独有的附加层通过具名插槽 details 注入(走秀页塞「当前 look 的细节图簇」,街拍页不传)。
|
||||
import { getI18n } from "@/lib/i18n"
|
||||
|
||||
interface Props {
|
||||
/** SSR 首屏图片(前 PREVIEW_LIMIT 张),需带 thumb 供左栏缩略图条使用 */
|
||||
images: { thumb: string }[]
|
||||
/** 缩略图比例:走秀 look 为 2/3,街拍为 3/4 */
|
||||
aspect?: "2/3" | "3/4"
|
||||
}
|
||||
const { images, aspect = "2/3" } = Astro.props
|
||||
const { t } = getI18n(Astro)
|
||||
|
||||
// 两个字面量都出现在本文件源码里,保证 Tailwind v4 JIT 能扫描到
|
||||
const thumbAspect = aspect === "3/4" ? "aspect-[3/4]" : "aspect-[2/3]"
|
||||
---
|
||||
|
||||
<div
|
||||
x-show="lbOpen"
|
||||
x-cloak
|
||||
@keydown.window.escape="lbOpen && closeLb()"
|
||||
@keydown.window.arrow-right="lbOpen && nextLb()"
|
||||
@keydown.window.arrow-left="lbOpen && prevLb()"
|
||||
class="fixed inset-0 z-[100] bg-white flex flex-col select-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* 计数:mono + 0.22em 字距(全站「小标与计数」档),直角细框,零填充 */}
|
||||
<div
|
||||
class="absolute top-5 left-1/2 -translate-x-1/2 z-30 border border-black/15 bg-white/90 px-3 py-1 font-mono text-[11px] tracking-[0.22em] text-[#111] backdrop-blur-md"
|
||||
x-text="`${String(lbIndex + 1).padStart(2, '0')} / ${String(imageCount).padStart(2, '0')}`"
|
||||
></div>
|
||||
|
||||
{/* 单图收藏:圆形,与列表卡片 .fav-toggle / 详情页右下角 #fav-fab 的全站「收藏=圆形」语义保持一致 */}
|
||||
<button
|
||||
login-required
|
||||
type="button"
|
||||
@click.stop="toggleFav(currentFavId(), currentFavUrl())"
|
||||
:class="isFav(currentFavId()) ? 'is-fav' : ''"
|
||||
class="fav-btn absolute top-5 right-[68px] z-30 w-10 h-10 rounded-full border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('save')}
|
||||
>
|
||||
<svg class="w-[18px] h-[18px]" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 21C12 21 4 14.5 4 8.8C4 6.1 6.1 4 8.7 4C10.3 4 11.5 4.8 12 6C12.5 4.8 13.7 4 15.3 4C17.9 4 20 6.1 20 8.8C20 14.5 12 21 12 21Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="closeLb()"
|
||||
class="absolute top-5 right-5 z-30 w-10 h-10 border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Close')}
|
||||
>
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="flex-1 min-h-0 flex items-stretch overflow-hidden relative">
|
||||
<div
|
||||
id="lb-thumbs"
|
||||
class="w-[76px] sm:w-[118px] flex-none overflow-y-auto overflow-x-hidden relative scroll-smooth p-[32px_8px] sm:p-[38px_18px_38px_20px] border-r border-black/10 no-scrollbar"
|
||||
>
|
||||
{images.map((im, i) => (
|
||||
<button
|
||||
type="button"
|
||||
data-lb-index={i}
|
||||
data-thumb-url={im.thumb}
|
||||
@click={`showLb(${i})`}
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[#eee] bg-cover bg-center`}
|
||||
x-bind:class="lbIndex === Number($el.dataset.lbIndex) ? 'opacity-100 ring-1 ring-black' : ''"
|
||||
aria-label={`${i + 1}`}
|
||||
>
|
||||
<span class="absolute left-1 top-1 z-10 bg-black/70 px-1 py-0.5 font-mono text-[8px] sm:text-[9px] tracking-[0.16em] text-white backdrop-blur-sm">
|
||||
{String(i + 1).padStart(2, "0")}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
|
||||
<template x-for="(im, i) in extraImages" :key="im.url">
|
||||
<button
|
||||
type="button"
|
||||
:data-lb-index="5 + i"
|
||||
:data-thumb-url="im.thumb"
|
||||
@click="showLb(5 + i)"
|
||||
class={`lb-thumb relative block w-full ${thumbAspect} mb-2 overflow-hidden cursor-pointer transition-opacity duration-200 opacity-40 hover:opacity-90 bg-[#eee] bg-cover bg-center`}
|
||||
x-bind:class="lbIndex === Number($el.dataset.lbIndex) ? 'opacity-100 ring-1 ring-black' : ''"
|
||||
:aria-label="String(5 + i + 1)"
|
||||
>
|
||||
<span class="absolute left-1 top-1 z-10 bg-black/70 px-1 py-0.5 font-mono text-[8px] sm:text-[9px] tracking-[0.16em] text-white backdrop-blur-sm" x-text="String(5 + i + 1).padStart(2, '0')"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="prevLb()"
|
||||
class="absolute left-3 sm:left-5 top-2 z-20 w-[52px] sm:w-[72px] h-[30px] sm:h-[34px] border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Previous image')}
|
||||
>
|
||||
<svg class="w-4 h-4 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
<path d="M6 14l6-6 6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="nextLb()"
|
||||
class="absolute left-3 sm:left-5 bottom-2 z-20 w-[52px] sm:w-[72px] h-[30px] sm:h-[34px] border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Next image')}
|
||||
>
|
||||
<svg class="w-4 h-4 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
<path d="M6 10l6 6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="prevLb()"
|
||||
class="absolute left-[82px] sm:left-[130px] top-1/2 -translate-y-1/2 z-20 w-10 h-10 border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Previous image')}
|
||||
>
|
||||
<svg class="w-5 h-5 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
<path d="M15 6l-6 6 6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="nextLb()"
|
||||
class="absolute right-2 sm:right-5 top-1/2 -translate-y-1/2 z-20 w-10 h-10 border border-black/15 bg-white/90 text-[#111] flex items-center justify-center backdrop-blur-md hover:bg-black hover:text-white transition-colors cursor-pointer"
|
||||
aria-label={t('Next image')}
|
||||
>
|
||||
<svg class="w-5 h-5 stroke-current fill-none stroke-2" viewBox="0 0 24 24">
|
||||
<path d="M9 6l6 6-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
id="lb-main"
|
||||
@click.self="closeLb()"
|
||||
class="flex-1 min-w-0 min-h-0 relative flex justify-center px-4 sm:px-18"
|
||||
x-bind:class="lbZoomed
|
||||
? 'overflow-y-auto overflow-x-hidden items-start'
|
||||
: 'items-center'"
|
||||
>
|
||||
<img
|
||||
id="lb-image"
|
||||
src=""
|
||||
alt=""
|
||||
@click="toggleZoom()"
|
||||
decoding="async"
|
||||
class="block select-none transition-opacity duration-200"
|
||||
x-bind:class="lbZoomed
|
||||
? 'w-full max-w-full h-auto max-h-none cursor-zoom-out'
|
||||
: 'max-w-full max-h-full object-contain cursor-zoom-in'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="absolute left-0 right-0 bottom-11 z-20 px-6 text-center font-mono text-[11px] uppercase tracking-[0.2em] text-black/60 pointer-events-none"
|
||||
x-text="currentImageName"
|
||||
></div>
|
||||
|
||||
<div
|
||||
x-show="!lbZoomed"
|
||||
class="absolute left-0 right-0 bottom-4 z-20 px-6 text-center font-mono text-[10px] uppercase tracking-[0.16em] text-black/40 pointer-events-none transition-opacity duration-200"
|
||||
>
|
||||
{t('Click image to zoom · Scroll to view · Esc to close')}
|
||||
</div>
|
||||
|
||||
{/* 各页独有的附加层(走秀页注入「当前 look 的细节图簇」;街拍页不传) */}
|
||||
<slot name="details" />
|
||||
</div>
|
||||
Reference in New Issue
Block a user