Vue Lightbox

面向 Vue 3 的全屏图片与视频画廊Lightbox,基于 @reelkit/vue-lightbox.

查看在线演示 →

特性

图片与视频
内置视频幻灯片支持
触摸手势
滑动翻页
滑动关闭
上滑关闭
键盘导航
方向键 + Escape
全屏
跨浏览器 API
过渡动画
滑动、淡入、翻转、放大
预加载
预取前后各 2 张
声音开关
逐张静音 / 取消静音
加载状态
转圈动画 + 自定义插槽
错误处理
错误图标 + 自定义插槽
作用域插槽
6 个可定制的插槽区域
v-model
v-model:is-open 双向绑定
URL 状态
可分享、可收藏的链接
幻灯片播放
按需开启的自动前进,可播放 / 暂停
下载
按需开启的按钮,保存当前项

安装

bash
npm install @reelkit/vue-lightbox @reelkit/vue lucide-vue-next

别忘了引入样式:

typescript
import '@reelkit/vue-lightbox/styles.css';
图标

默认控件使用 lucide-vue-next 作为图标。如果你想换一套图标库,可以用 #controls 和 #navigation 作用域插槽提供自己的实现。

快速上手

引入样式表和 LightboxOverlay 组件,然后用 v-model:is-open.

App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { LightboxOverlay, type LightboxItem } from '@reelkit/vue-lightbox';
import '@reelkit/vue-lightbox/styles.css';

const images: LightboxItem[] = [
  {
    src: '/cdn/samples/images/image-01.jpg',
    title: 'Mountain River',
    description: 'A beautiful mountain river',
  },
  {
    src: '/cdn/samples/images/image-02.jpg',
    title: 'Snowy Peaks',
  },
  {
    src: '/cdn/samples/images/image-03.jpg',
    title: 'Misty Forest',
    description: 'Morning fog over the forest canopy',
  },
  {
    src: '/cdn/samples/images/image-04.jpg',
    title: 'Autumn Trail',
  },
  {
    src: '/cdn/samples/images/image-05.jpg',
    title: 'Ocean Cliff',
    description: 'Dramatic cliffs above the Pacific',
  },
  {
    src: '/cdn/samples/images/image-06.jpg',
    title: 'Desert Dunes',
  },
];

const open = ref(false);
const startIndex = ref(0);

function openAt(i: number) {
  startIndex.value = i;
  open.value = true;
}
</script>

<template>
  <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
    <button
      v-for="(img, i) in images"
      :key="img.src"
      style="aspect-ratio:4/3;cursor:pointer"
      @click="openAt(i)"
    >
      <img :src="img.src" style="width:100%;height:100%;object-fit:cover" />
    </button>
  </div>

  <LightboxOverlay
    v-model:is-open="open"
    :items="images"
    :initial-index="startIndex"
  />
</template>

作用域插槽

六个具名作用域插槽让你完全定制浮层的各个界面。省略插槽即保留内置默认实现;插槽内部什么都不放(例如用 v-if="false")就能把该部分整个隐藏。

插槽作用域说明
#slideSlideSlotScope替换单张幻灯片的内容(视频幻灯片必须提供)
#controlsControlsSlotScope替换顶部控件栏(关闭、计数、全屏)
#navigationNavigationSlotScope替换上一张 / 下一张导航箭头
#infoInfoSlotScope替换底部的标题 / 描述渐变浮层
#loadingLoadingSlotScope自定义加载提示
#errorErrorSlotScope自定义错误提示
vue
<template>
  <LightboxOverlay v-model:is-open="open" :items="items">
    <!-- Custom info overlay -->
    <template #info="{ item }">
      <div class="my-caption">
        <h2>{{ item.title }}</h2>
        <p>{{ item.description }}</p>
      </div>
    </template>

    <!-- Custom navigation -->
    <template #navigation="{ onPrev, onNext, activeIndex, count }">
      <div class="my-nav">
        <button :disabled="activeIndex === 0" @click="onPrev">Prev</button>
        <span>{{ activeIndex + 1 }} / {{ count }}</span>
        <button :disabled="activeIndex === count - 1" @click="onNext">Next</button>
      </div>
    </template>

    <!-- Custom controls -->
    <template #controls="{ onClose, isFullscreen, onToggleFullscreen }">
      <div class="my-controls">
        <button @click="onToggleFullscreen">
          {{ isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen' }}
        </button>
        <button @click="onClose">Close</button>
      </div>
    </template>
  </LightboxOverlay>
</template>

视频支持

视频幻灯片是按需开启的,默认产物里不会掺进音视频相关代码。调用 useVideoSlideRenderer(items) 并把返回的 VideoSlideRenderer / VideoControlsRenderer 接到浮层的 #slide 和 #controls 插槽上。再把浮层包进返回的 SoundProvider 里,内置的声音开关才有上下文。返回的对象类型为 UseVideoSlideRendererResult:包含 SoundProvider、hasVideo、VideoSlideRenderer 和 VideoControlsRenderer。

vue
<script setup lang="ts">
import { ref } from 'vue';
import {
  LightboxOverlay,
  useVideoSlideRenderer,
  type LightboxItem,
} from '@reelkit/vue-lightbox';
import '@reelkit/vue-lightbox/styles.css';

const open = ref(false);
const items: LightboxItem[] = [
  { src: '/image-01.jpg', title: 'Image' },
  {
    type: 'video',
    src: '/clip.mp4',
    poster: '/clip.jpg',
    title: 'Clip',
  },
];

const { VideoSlideRenderer, VideoControlsRenderer, SoundProvider } =
  useVideoSlideRenderer(items);
</script>

<template>
  <SoundProvider>
    <LightboxOverlay v-model:is-open="open" :items="items">
      <template #slide="scope">
        <VideoSlideRenderer v-bind="scope" />
      </template>
      <template #controls="scope">
        <VideoControlsRenderer v-bind="scope" />
      </template>
    </LightboxOverlay>
  </SoundProvider>
</template>

驱动视频幻灯片的共享 <video> 元素与 vue reel-player 用的是同一套模式 —— 在 iOS 上切换幻灯片时播放不会中断,也不需要每张都由用户手势触发。

全屏

使用 useFullscreen from @reelkit/vue 来观察或切换某个引用元素的全屏状态。Lightbox内置的全屏按钮用的就是同一个组合式函数。

vue
<script setup lang="ts">
import { shallowRef } from 'vue';
import { useFullscreen } from '@reelkit/vue';

const containerRef = shallowRef<HTMLDivElement | null>(null);
const { isFullscreen, toggle } = useFullscreen({ elementRef: containerRef });
</script>

<template>
  <div ref="containerRef">
    <button @click="toggle">
      {{ isFullscreen.value ? 'Exit fullscreen' : 'Enter fullscreen' }}
    </button>
  </div>
</template>

URL 状态

查看在线演示 →

用 useOverlayUrlState from @reelkit/vue 构建控制器,再把它交给 LightboxUrlOverlay 作为 controller,地址栏就拥有了这个画廊:参数指向某张幻灯片时它自己打开,参数消失时关闭。链接可以分享,返回键会关闭画廊。它是一个独立于 LightboxOverlay的组件,因此每个组件都只有一个打开状态的驱动源 —— 要么是 is-open 模型,要么是 URL controller,绝不会同时用两个。

内置的 key

可以用内置的 key 来寻址幻灯片 —— 把 urlIndexKey (按位置)或 urlStableIdKey (按稳定的 id)展开进控制器 —— 两者都从 @reelkit/vue. See the URL 状态指南 和 核心 API.

只有从应用内部打开时返回键才会关闭 —— 因为那次链接压入了一条记录,返回就会弹回画廊。在新标签页里直接打开的分享链接背后没有历史,浏览器返回会离开站点;这时关闭按钮或 Escape 会就地移除参数,把你留在画廊上。

vue
<script setup lang="ts">
import { LightboxUrlOverlay, type LightboxItem } from '@reelkit/vue-lightbox';
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/vue';
import '@reelkit/vue-lightbox/styles.css';

const props = defineProps<{ images: LightboxItem[] }>();

const photo = useOverlayUrlState({
  param: 'photo',
  ...urlIndexKey(() => props.images.length),
});
</script>

<template>
  <!-- Opening is a link — the href is the open action. No open flag, no
       handler: the overlay reads the URL and opens itself. -->
  <RouterLink v-for="(img, i) in props.images" :key="img.src" :to="`?photo=${i}`">
    <img :src="img.src" />
  </RouterLink>

  <LightboxUrlOverlay :controller="photo" :items="props.images" />
</template>

这个组合式函数接受一个选项对象,返回一个 UrlStateController (带 set, index, value)。留着它就能编程式地控制: set 是浮层内部使用的底层写入(切换幻灯片,以及用 set(null) 关闭)。它同样可以编程式驱动浮层 —— set(index) 会打开它,效果和导航到该参数一样。不过打开时更推荐用链接:href 可以分享、能在新标签页打开、返回键会关闭它 —— 全都不用写处理函数。

完整的 useOverlayUrlState 选项(param, adapter, codec, locator):见 Vue API 参考.

LightboxUrlOverlay 本身只接受 :controller (必填)、一个 @close 事件,外加所有视觉和行为属性 LightboxOverlay 所转发的(items, transition-fn、各种作用域插槽等等)—— 但没有 is-open.

  • 打开会花掉一条历史记录;翻页则是替换它,所以滑一百次也不会多出记录 —— 退一步永远就是离开画廊。
  • 像 ?photo=3 会把画廊直接打开到那一张。指向不存在幻灯片的参数会从 URL 中移除,而不是继续声称一张打不开的幻灯片。

带路由的应用请传入适配器。 直接写历史会让路由器自己的 location 过期,下一次导航就会把参数丢掉。

vue
<script setup lang="ts">
import { useVueRouterUrlAdapter } from '@reelkit/vue/vue-router-url-adapter';

const adapter = useVueRouterUrlAdapter();
const photo = useOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => images.length),
});
</script>

<template>
  <LightboxUrlOverlay :controller="photo" :items="images" />
</template>

稳定的链接。 索引是按位置的,所以列表重新排序后书签会打开另一张图片。 urlStableIdKey 按每个条目稳定的 id来寻址,扫描当前列表 —— 一次调用就覆盖了常见场景。

vue
<script setup lang="ts">
const photo = useOverlayUrlState({
  param: 'photo',
  ...urlStableIdKey({ items: () => images }),
});
</script>

<template>
  <LightboxUrlOverlay :controller="photo" :items="images" />
</template>

传入 hashCodec: base64UrlCodec 即可把 URL 中的 id 做 base64url 编码 —— 这是可逆的混淆,不是加密哈希。

想按别的字段(比如 slug)来寻址,或者用 locateAsync给无限信息流翻页,就自己构建 codec/locator : codec 把身份写进 URL, locator 负责找到它现在在哪。

vue
<script setup lang="ts">
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => images.findIndex((x) => x.slug === id),
    identify: (index) => images[index].slug,
  },
});
</script>

<template>
  <LightboxUrlOverlay :controller="photo" :items="images" />
</template>

无限 / 分页画廊。 locate 是同步的,因此只能回答已经加载过的条目 —— 只加载了 20 张时,指向第 400 张的分享链接就查不到。 locateAsync 是兜底,只有在未命中时才调用:把需要的页拉进来,再返回该身份最终对应的索引。

快捷键

只想按条目的 id来寻址?那就不必手写编解码器和定位器 —— 直接把 locateAsync 传给 urlStableIdKey({ items, locateAsync }) (未命中时它会去拉取,然后返回索引)。下面更完整的写法是给按别的字段寻址、或者需要完全掌控的场景准备的。

vue
<script setup lang="ts">
const photo = useOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => items.value.findIndex((x) => x.id === id),
    identify: (index) => items.value[index].id,
    locateAsync: async (id) => {
      const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
      if (!loaded) return null; // exhausted — link names no item
      items.value = loaded; // commit; the overlay renders from this
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});
</script>

<template>
  <LightboxUrlOverlay :controller="photo" :items="items" />
</template>

在它未完成期间,Lightbox保持关闭,参数也不动,因此深链能熬过这次请求。 null 或请求被拒绝则会移除参数。如果结果在 URL 已经变化、Lightbox已关闭或组件已卸载之后才到达,就会被丢弃,因此慢请求不能打开一张没人要的幻灯片。它返回什么都以它为准 —— 它报告的是自己刚取到的数据的索引,Lightbox会直接采用,而不是再去读一遍 items,那里 Vue 还没有重新渲染。

自动前进

设置 auto-advance,Lightbox 打开时就会以幻灯片方式播放画廊。默认关闭,因此现有的 Lightbox 渲染和行为都与以前一样。

vue
<script setup lang="ts">
import { ref } from 'vue';
import { LightboxOverlay, type LightboxApi } from '@reelkit/vue-lightbox';

const open = ref(false);
const lightbox = ref<LightboxApi | null>(null);
</script>

<template>
  <LightboxOverlay
    ref="lightbox"
    v-model:is-open="open"
    :items="items"
    auto-advance
    :auto-advance-interval="4000"
  />
  <!-- The template ref drives it from outside: lightbox?.pause() -->
</template>
  • 图片 停留 auto-advance-interval(默认 5000 ms),从图片加载完成的那一刻开始计算,所以加载慢的图片绝不会在没人看到之前被跳过。加载失败的图片从出错时开始计算。
  • 视频 只播放一次而不循环,播放结束后前进。VideoSlideRenderer 会替你处理;自定义的 #slide 从作用域中读取 autoAdvance,并通过 onEnded 报告结束。
  • 它会等待:鼠标停在控件(按钮、链接或说明文字)上时、滑动过程中以及页面隐藏时,之后从停下的地方继续。
  • 手动切换(箭头、按键、滚轮、滑动)会让新幻灯片的间隔从零开始;幻灯片播放保持开启。
  • 在最后一张 时停止,除非设置了 loop。控件中的播放 / 暂停按钮显示当前状态,并让观看者切换它;LightboxApi 上的 play() 和 pause() 可以在代码中做同样的事。

自定义的 #controls 插槽会收到 isAutoAdvancing,并且仅在 auto-advance 开启时收到 onToggleAutoAdvance —— 有它时就渲染一个 PlayPauseButton。计时本身是 @reelkit/core 中的 createSlideshowController;见自动播放指南和核心 API。

下载

设置 show-download,即可在控件中添加下载按钮。它从当前项的 downloadUrl(没有时回退到 src)保存文件,文件名取 downloadName,没有时回退到 URL 的最后一段。

vue
<script setup lang="ts">
import { ref } from 'vue';
import {
  CloseButton,
  DownloadButton,
  LightboxOverlay,
  type LightboxItem,
} from '@reelkit/vue-lightbox';

const open = ref(false);
const items: LightboxItem[] = [
  {
    src: 'https://cdn.example.com/beach-1600.jpg',
    // Save the original rather than the resized preview
    downloadUrl: 'https://cdn.example.com/beach-original.jpg',
    downloadName: 'beach.jpg',
  },
];
</script>

<template>
  <LightboxOverlay v-model:is-open="open" :items="items" show-download>
    <!-- A custom bar shows the buttons only when their handler is there -->
    <template #controls="{ onClose, isDownloading, onDownload }">
      <DownloadButton
        v-if="onDownload"
        :is-downloading="isDownloading"
        :on-press="onDownload"
      />
      <CloseButton :on-press="onClose" />
    </template>
  </LightboxOverlay>
</template>

文件会先被获取再保存,因为浏览器会忽略跨域链接上的 download 属性,转而直接打开文件。获取失败时 —— 通常是另一个源上的服务器没有发送 CORS 头 —— 文件会在新标签页中打开,观看者仍可在那里保存。为文件配置 Access-Control-Allow-Origin 即可一键保存。下载进行期间按钮处于禁用状态。

自定义的 #controls 插槽会收到 isDownloading,并且仅在 show-download 开启时收到 onDownload —— 有它时就渲染一个 DownloadButton。工具函数是 @reelkit/core 中的 downloadFile;见下载指南。

控件按钮

组件属性说明
PlayPauseButtonPlayPauseButtonProps: isPlaying, onPress, className?幻灯片播放开关(Play/Pause 图标,播放时带 aria-pressed)。设置 auto-advance 时由默认控件显示。
DownloadButtonDownloadButtonProps: isDownloading, onPress, className?下载按钮,isDownloading 时处于禁用状态。设置 show-download 时由默认控件显示。
CloseButtonCloseButtonProps: onPress, className?控件栏渲染的默认关闭按钮;放进你自己的 #controls 插槽即可保留内置外观。
CounterCounterProps: currentIndex, count, className?默认的 "3 / 10" 计数标签;放进你自己的 #controls 插槽即可保留内置外观。
FullscreenButtonFullscreenButtonProps: isFullscreen, onPress, className?默认的全屏开关;图标随 isFullscreen 变化。
SoundButtonSoundButtonProps: isMuted, onPress, className?视频条目的默认静音开关;图标随 isMuted 变化。

幻灯片与导航部件

组件属性说明
ImageSlideImageSlideProps: src, alt?, isActive, onReady?, onLoadError?浮层渲染的默认图片幻灯片;放进你自己的 #slide 插槽即可让图片条目保留内置外观。
LightboxVideoSlideLightboxVideoSlideProps: src, poster?, isActive, size, slideKey, loop?, onPlaying?, onWaiting?, onLoadError?默认的视频幻灯片,通过共享的 <video> 元素播放;useVideoSlideRenderer 会替你渲染它。
LightboxNavigationLightboxNavigationProps: activeIndex, count, loop?, onPrev, onNext默认的上一张 / 下一张箭头;放进你自己的 #navigation 插槽即可保留内置外观。

API 参考

LightboxOverlay 属性

LightboxOverlayProps

属性类型默认值说明
isOpenboolean必填控制显示;为 false 时浮层会从 DOM 中移除。可用 v-model:is-open 双向绑定。
itemsLightboxItem[]必填条目数组(图片或视频)
initialIndexnumber0初始可见条目的索引(从 0 开始)
transitionFnTransitionTransformFnslideTransition幻灯片过渡函数。可以引入内置的(slideTransition、flipTransition、lightboxFadeTransition、lightboxZoomTransition),也可以传自定义的。省略时默认为 slideTransition。
showInfobooleantrue是否渲染标题 / 描述信息浮层
showControlsbooleantrue是否渲染顶部控件栏(关闭、计数、全屏)
showNavigationbooleantrue是否渲染上一张 / 下一张导航箭头(仅桌面端)
transitionDurationnumber300幻灯片动画时长(毫秒)
swipeDistanceFactornumber0.12触发切换所需的最小滑动距离占比(0–1)
swipeToCloseDirection'up' | 'down''up'移动端滑动关闭手势的方向
loopbooleanfalse滑动器是否从最后一张绕回第一张
enableNavKeysbooleantrue启用键盘方向键导航
enableWheelbooleantrue启用鼠标滚轮导航
wheelDebounceMsnumber200滚轮事件的防抖时长(毫秒)
autoAdvancebooleanfalse以幻灯片方式播放画廊。见自动前进。
autoAdvanceIntervalnumber5000幻灯片播放期间每张图片停留的时长(毫秒),从图片加载完成后开始计算
showDownloadbooleanfalse为当前项添加下载按钮。见下载。
ariaLabelstring'Image gallery'对话框区域的无障碍标签

LightboxUrlOverlay 属性

LightboxUrlOverlayProps

接受上面所有视觉和行为属性,除了 is-open,并把它换成 controller。它会发出 close, slide-change 和 api-ready,但没有 update:is-open. initial-index 在这里会被忽略 —— 由控制器的 position 决定打开哪一张,所以同时传入的值每次打开都会被覆盖。

属性类型默认值说明
controllerUrlStateController必填来自 useOverlayUrlState 的控制器。它的 position 决定浮层是否打开、显示哪一张;浮层会在切换幻灯片和关闭时通过它写回。

LightboxOverlay 事件

事件负载说明
closevoid用户关闭Lightbox时发出
slide-changenumber切换后发出,带上新的活动幻灯片索引
api-readyLightboxApi滑动器就绪时发出一次,同时暴露命令式 API
update:is-openboolean关闭时发出;用于支持 v-model:is-open

LightboxApi

api-ready 发出的对象,也可通过组件的模板 ref 获取。滑动器只在覆盖层打开期间存在,所以关闭时滑动器的每个方法都不做任何事;close() 始终有效。

方法类型说明
next()() => void带动画切换到下一张。除非开启了 loop,否则停在最后一张。
prev()() => void带动画切换到上一张。除非开启了 loop,否则停在第一张。
goTo(index, animate?)(number, boolean?) => Promise<void>跳到指定幻灯片。index 会被限制在条目范围内,animate 默认为 false。移动结束后 resolve。
adjust()() => void在覆盖层自身未察觉的布局变化之后,重新计算幻灯片位置
observe()() => void在 unobserve() 之后重新开始监听手势、键盘和滚轮输入
unobserve()() => void停止监听手势、键盘和滚轮输入;幻灯片保持原位
close()() => void关闭 lightbox,与关闭按钮或 Escape 相同
play()() => void开始幻灯片播放,当前幻灯片从停下的地方继续。没有 auto-advance 时不做任何事。
pause()() => void停止幻灯片播放;当前幻灯片保留其进度。没有 auto-advance 时不做任何事。

LightboxItem 接口

字段类型必填说明
srcstringyes图片或视频的 URL
type'image' | 'video'no条目类型。默认为 'image'
posterstringno视频条目的缩略图
titlestringno信息浮层中显示的标题
descriptionstringno标题下方显示的描述
widthnumberno图片固有宽度(像素)
heightnumberno图片固有高度(像素)
downloadUrlstringno下载按钮保存的内容。默认为 src
downloadNamestringno保存时使用的文件名。默认为 URL 路径的最后一段

插槽作用域类型

类型字段
SlideSlotScope{ item, index, size: [number, number], isActive, autoAdvance, onReady, onWaiting, onError, onEnded }
ControlsSlotScope{ item, activeIndex, count, isFullscreen, isAutoAdvancing, isDownloading, onClose, onToggleFullscreen, onToggleAutoAdvance?, onDownload? }
NavigationSlotScope{ item, activeIndex, count, onPrev, onNext }
InfoSlotScope{ item, index }
LoadingSlotScope{ item, activeIndex }
ErrorSlotScope{ item, activeIndex }

过渡动画

把任意 TransitionTransformFn 通过 transition-fn 属性传入。只引入你用到的那个过渡,打包器就能把其余的摇掉。省略时默认为 slideTransition 。

函数说明
slideTransition默认。幻灯片之间横向平移;从 @reelkit/vue 重新导出。
lightboxFadeTransition交叉淡入淡出,带轻微的横向位移。仅存在于 @reelkit/vue-lightbox。
flipTransition绕 Y 轴的 3D 翻转;从 @reelkit/vue 重新导出。
lightboxZoomTransition进入的幻灯片从 70% 放大到 100% 并淡入。仅存在于 @reelkit/vue-lightbox。
vue
<script setup lang="ts">
import {
  LightboxOverlay,
  lightboxFadeTransition,
  lightboxZoomTransition,
} from '@reelkit/vue-lightbox';
</script>

<template>
  <!-- Built-in transition -->
  <LightboxOverlay
    v-model:is-open="open"
    :items="items"
    :transition-fn="lightboxFadeTransition"
  />

  <!-- Different built-in -->
  <LightboxOverlay
    v-model:is-open="open"
    :items="items"
    :transition-fn="lightboxZoomTransition"
  />
</template>

内容加载与错误处理

当你通过 #slide 插槽接管渲染时,插槽作用域上有三个生命周期回调可用于上报加载状态。Lightbox会逐张跟踪状态,并据此显示转圈动画或错误图标。内容预加载器会缓存损坏的 URL,因此再次访问失败的幻灯片不会重试。

生命周期回调

回调类型说明
onReady() => void通知幻灯片内容已成功加载(例如图片解码完成)
onWaiting() => void通知幻灯片内容正在加载 / 缓冲(显示转圈动画)
onError() => void通知幻灯片内容加载失败(显示错误图标)

在 #slide 中接上回调

vue
<template>
  <LightboxOverlay v-model:is-open="open" :items="items">
    <template
      #slide="{ item, size, isActive, onReady, onWaiting, onError }"
    >
      <template v-if="item.type === 'video'">
        <video
          :src="item.src"
          :poster="item.poster"
          :autoplay="isActive"
          :style="{ width: `${size[0]}px`, height: `${size[1]}px`, objectFit: 'contain' }"
          @canplay="onReady"
          @waiting="onWaiting"
          @error="onError"
        />
      </template>
      <template v-else>
        <img
          :src="item.src"
          :style="{ width: `${size[0]}px`, height: `${size[1]}px`, objectFit: 'contain' }"
          @load="onReady"
          @error="onError"
        />
      </template>
    </template>
  </LightboxOverlay>
</template>

自定义加载插槽

Use the #loading 插槽替换默认的转圈动画。

vue
<template>
  <LightboxOverlay v-model:is-open="open" :items="items">
    <template #loading="{ item, activeIndex }">
      <div class="my-loading">
        <span>Loading image {{ activeIndex + 1 }}…</span>
        <span class="muted">{{ item.title }}</span>
      </div>
    </template>
  </LightboxOverlay>
</template>

自定义错误插槽

Use the #error 插槽替换默认的图片损坏图标。

vue
<template>
  <LightboxOverlay v-model:is-open="open" :items="items">
    <template #error="{ item, activeIndex }">
      <div class="my-error">
        <span>Failed to load</span>
        <span class="muted">{{ item.title ?? item.src }}</span>
      </div>
    </template>
  </LightboxOverlay>
</template>

CSS 类名

所有 CSS 类名都是普通类名(没有 scoped),因此可以在 @reelkit/vue-lightbox/styles.css之后加载的样式表里用更高优先级的选择器覆盖它们。若只是改颜色、尺寸和 z-index,请优先使用下面 主题定制 一节。

类名组件说明
.rk-lightbox-overlayOverlay根容器(全屏背景层)
.rk-lightbox-top-shadeOverlay控件背后的顶部渐变遮罩
.rk-lightbox-spinnerOverlay默认的加载转圈动画
.rk-lightbox-errorOverlay错误状态容器(图片损坏)
.rk-lightbox-error-textOverlay错误状态文字标签
.rk-lightbox-controls-left控制内容左上角控件容器
.rk-lightbox-btn控制内容控制按钮(全屏、声音等)
.rk-lightbox-play-pause控制内容幻灯片播放 / 暂停按钮(与 .rk-lightbox-btn 一起使用)
.rk-lightbox-download控制内容下载按钮(与 .rk-lightbox-btn 一起使用)
.rk-lightbox-close控制内容关闭按钮
.rk-lightbox-counter控制内容图片计数标签
.rk-lightbox-nav导航导航箭头(上一张和下一张)
.rk-lightbox-nav-prev导航上一张箭头
.rk-lightbox-nav-next导航下一张箭头
.rk-lightbox-infoInfo标题 / 描述容器
.rk-lightbox-info-titleInfo图片标题
.rk-lightbox-info-descriptionInfo图片描述
.rk-lightbox-slideSlide幻灯片容器
.rk-lightbox-imgSlide图片元素
.rk-lightbox-video-containerVideoSlide视频幻灯片容器(按需开启)
.rk-lightbox-video-elementVideoSlide视频元素(按需开启)
.rk-lightbox-video-posterVideoSlide视频封面图(按需开启)

主题定制

在 --rk-lightbox-* 上覆盖任意 :root (或 .rk-lightbox-overlay的任意祖先元素)即可换主题。直接写在 .rk-lightbox-overlay 上的声明会遮蔽继承下来的值,所以请把覆盖写在祖先选择器上。

变量默认值控制内容
--rk-lightbox-overlay-bg#000背景颜色
--rk-lightbox-overlay-z9999浮层 z-index
--rk-lightbox-top-shade-height80px顶部遮罩高度
--rk-lightbox-top-shade-bglinear-gradient(rgba(0,0,0,0.6), transparent)顶部遮罩渐变
--rk-lightbox-edge-padding16px关闭、导航和控件距边缘的内缩
--rk-lightbox-btn-bgrgba(0, 0, 0, 0.5)关闭、导航和小按钮的默认背景
--rk-lightbox-btn-bg-hoverrgba(255, 255, 255, 0.2)关闭、导航和小按钮的悬停背景
--rk-lightbox-btn-fg#fff关闭、导航和小按钮的图标颜色
--rk-lightbox-btn-size36px小按钮尺寸(全屏切换等)
--rk-lightbox-btn-disabled-opacity0.5禁用状态下小按钮的不透明度(下载进行中)
--rk-lightbox-close-size40px关闭按钮尺寸
--rk-lightbox-nav-size48px上一个 / 下一个箭头尺寸
--rk-lightbox-nav-opacity0.7上一个 / 下一个箭头的空闲不透明度
--rk-lightbox-counter-bgrgba(0, 0, 0, 0.5)计数标签背景
--rk-lightbox-counter-fg#fff计数文字颜色
--rk-lightbox-info-bglinear-gradient(transparent, rgba(0,0,0,0.8))说明文字遮罩渐变
--rk-lightbox-title-size18px标题字号
--rk-lightbox-description-size14px描述字号
--rk-lightbox-video-bg#000<video> 后方的留黑背景
css
<style>
:root {
  --rk-lightbox-overlay-bg: #0f172a;
  --rk-lightbox-btn-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-btn-bg-hover: rgba(168, 85, 247, 0.85);
  --rk-lightbox-info-bg: linear-gradient(
    transparent,
    rgba(99, 102, 241, 0.55) 60%,
    rgba(168, 85, 247, 0.85)
  );
}
</style>

无障碍

浮层根节点是一个模态对话框(role="dialog", aria-modal="true"). Set the aria-label 属性可以改变屏幕阅读器的播报内容,默认是 “Image gallery”。每张幻灯片都带有 role="group", aria-roledescription="slide",以及 aria-label ,由位置推导而来(例如 “Image 2 of 5”)。

Lightbox打开时捕获焦点,关闭时把焦点还给触发元素。Tab 和 Shift+Tab 在内部的可聚焦元素之间循环;跑出去的焦点(点击外部、程序化聚焦)会被拉回来。实现基于 captureFocusForReturn 和 createFocusTrap from @reelkit/vue.

键盘快捷键

Key作用
ArrowLeft上一张图片
ArrowRight下一张图片
Escape关闭 Lightbox(全屏时则退出全屏)