Angular Lightbox

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

查看在线演示 →

特性

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

安装

bash
npm install @reelkit/angular-lightbox @reelkit/angular lucide-angular

注册一次样式表:

typescript
// Anywhere that runs at startup — main.ts, app.config.ts, a component file.
import '@reelkit/angular-lightbox/styles.css';

// Or, if you keep CSS out of TypeScript, from your global stylesheet:
//   @import '@reelkit/angular-lightbox/styles.css';
// Or register it in angular.json:
//   "styles": ["node_modules/@reelkit/angular-lightbox/styles.css"]
图标

默认控件使用 lucide-angular 作为图标。如果你想换一套图标库,可以用 rkLightboxControls 和 rkLightboxNavigation 模板插槽提供自己的实现。

快速上手

把样式和独立组件 RkLightboxOverlayComponent 引入组件的 imports 数组。

gallery.component.ts
import { Component } from '@angular/core';
import {
  RkLightboxOverlayComponent,
  type LightboxItem,
} from '@reelkit/angular-lightbox';
import '@reelkit/angular-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',
  },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RkLightboxOverlayComponent],
  template: `
    <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
      @for (img of images; track img.src; let i = $index) {
        <button (click)="openIndex = i" style="aspect-ratio:4/3;cursor:pointer">
          <img [src]="img.src" style="width:100%;height:100%;object-fit:cover" />
        </button>
      }
    </div>

    <rk-lightbox-overlay
      [isOpen]="openIndex !== null"
      [items]="images"
      [initialIndex]="openIndex ?? 0"
      (closed)="openIndex = null"
    />
  `,
})
export class AppComponent {
  images = images;
  openIndex: number | null = null;
}
覆盖层渲染在哪里

React 和 Vue 的 lightbox 会把覆盖层移到 document.body 末尾,而 rk-lightbox-overlay 就渲染在你放置它的位置,并用 position: fixed 铺满屏幕。不要把它放在带有 transform、perspective、filter 或 will-change: transform 的祖先元素里:这样的祖先会成为覆盖层的包含块,lightbox 的尺寸会跟随它而不是视口,并被它的 overflow 裁剪。创建了自身层叠上下文的祖先,也会限制 --rk-lightbox-overlay-z 能把 lightbox 抬到多高。反过来,设置在任意祖先上的 --rk-lightbox-* 令牌都能作用到 lightbox。

模板插槽

四个模板插槽指令让你完全定制浮层界面,无需 fork 组件。每个插槽都会收到带完整类型的上下文对象。

指令上下文类型说明
[rkLightboxControls]LightboxControlsContext替换顶部控件栏(关闭按钮、计数、全屏开关)
[rkLightboxNavigation]LightboxNavContext替换上一张 / 下一张导航箭头
[rkLightboxInfo]LightboxInfoContext替换底部的标题 / 描述渐变浮层
[rkLightboxSlide]LightboxSlideContext替换单张幻灯片的内容(视频幻灯片必须提供)
[rkLightboxLoading]LightboxLoadingContext自定义加载提示;指令类为 RkLightboxLoadingDirective
[rkLightboxError]LightboxErrorContext自定义错误提示;指令类为 RkLightboxErrorDirective
typescript
import {
  RkLightboxOverlayComponent,
  RkLightboxControlsDirective,
  RkLightboxNavigationDirective,
  RkLightboxInfoDirective,
  RkCloseButtonComponent,
  RkCounterComponent,
  RkFullscreenButtonComponent,
  type LightboxItem,
  type LightboxControlsContext,
  type LightboxNavContext,
} from '@reelkit/angular-lightbox';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RkLightboxOverlayComponent,
    RkLightboxControlsDirective,
    RkLightboxNavigationDirective,
    RkLightboxInfoDirective,
    RkCloseButtonComponent,
    RkCounterComponent,
    RkFullscreenButtonComponent,
  ],
  template: `
    <rk-lightbox-overlay [isOpen]="isOpen" [items]="images" (closed)="isOpen = false">

      <!-- Custom controls bar -->
      <ng-template rkLightboxControls
                   let-onClose="onClose"
                   let-activeIndex="activeIndex"
                   let-count="count"
                   let-isFullscreen="isFullscreen"
                   let-onToggleFullscreen="onToggleFullscreen">
        <div style="position:absolute;top:0;left:0;right:0;padding:12px;
                    display:flex;align-items:center;justify-content:space-between">
          <rk-close-button (clicked)="onClose()" />
          <rk-counter [currentIndex]="activeIndex + 1" [count]="count" />
          <rk-fullscreen-button
            [isFullscreen]="isFullscreen"
            (toggled)="onToggleFullscreen()" />
        </div>
      </ng-template>

      <!-- Custom navigation -->
      <ng-template rkLightboxNavigation
                   let-onPrev="onPrev"
                   let-onNext="onNext"
                   let-activeIndex="activeIndex"
                   let-count="count">
        <button (click)="onPrev()" [disabled]="activeIndex === 0">&#8592;</button>
        <button (click)="onNext()" [disabled]="activeIndex === count - 1">&#8594;</button>
      </ng-template>

      <!-- Custom info overlay -->
      <ng-template rkLightboxInfo let-item let-index="index">
        <div style="position:absolute;bottom:0;left:0;right:0;padding:16px;
                    background:linear-gradient(transparent,rgba(0,0,0,0.6))">
          <h3 style="color:#fff">{{ item.title }}</h3>
          <p style="color:rgba(255,255,255,0.7)">{{ item.description }}</p>
        </div>
      </ng-template>

    </rk-lightbox-overlay>
  `,
})
export class AppComponent {
  images: LightboxItem[] = [];
  isOpen = false;
}

视频支持

视频幻灯片需要通过 rkLightboxSlide 模板插槽和 RkLightboxVideoSlideComponent显式启用。这样设计是为了让只需要图片的画廊不必打包视频播放器。

只有当 SoundStateService 提供在 <rk-lightbox-overlay> 之上时,内置声音按钮才会出现 —— 正是这个共享实例让切换能作用到视频,因为幻灯片由你的模板渲染。没有它就用 setLightboxVideoMuted 直接控制元素。

typescript
import {
  RkLightboxOverlayComponent,
  RkLightboxSlideDirective,
  RkLightboxVideoSlideComponent,
  type LightboxItem,
  type LightboxSlideContext,
} from '@reelkit/angular-lightbox';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RkLightboxOverlayComponent,
    RkLightboxSlideDirective,
    RkLightboxVideoSlideComponent,
  ],
  template: `
    <rk-lightbox-overlay [isOpen]="isOpen" [items]="items" (closed)="isOpen = false">
      <ng-template rkLightboxSlide
                   let-item
                   let-index="index"
                   let-size="size"
                   let-isActive="isActive"
                   let-autoAdvance="autoAdvance"
                   let-onReady="onReady"
                   let-onEnded="onEnded">
        @if (item.type === 'video') {
          <!-- With autoAdvance the video plays once and reports its end -->
          <rk-lightbox-video-slide
            [src]="item.src"
            [poster]="item.poster"
            [slideKey]="'lightbox-' + index"
            [size]="size"
            [isActive]="isActive"
            [loop]="!autoAdvance"
            [onReady]="onReady"
            [onEnded]="onEnded"
          />
        } @else {
          <img [src]="item.src"
               [style.width.px]="size[0]"
               [style.height.px]="size[1]"
               style="object-fit:contain" />
        }
      </ng-template>
    </rk-lightbox-overlay>
  `,
})
export class AppComponent {
  isOpen = false;
  items: LightboxItem[] = [
    { src: '/photo.jpg', title: 'Photo' },
    { src: '/clip.mp4', type: 'video', poster: '/clip-thumb.jpg', title: 'Video' },
  ];
}

全屏

使用 fullscreenSignal, requestFullscreen、 exitFullscreen from @reelkit/angular 来观察或切换全屏状态。

typescript
import { fullscreenSignal, requestFullscreen, exitFullscreen } from '@reelkit/angular';

@Component({ ... })
export class AppComponent {
  readonly isFullscreen = fullscreenSignal();

  toggle(container: HTMLElement): void {
    if (this.isFullscreen()) {
      exitFullscreen();
    } else {
      requestFullscreen(container);
    }
  }
}

URL 状态

查看在线演示 →

RkLightboxUrlOverlayComponent 是一个独立组件,它的打开状态存放在地址栏里。用 createOverlayUrlState 构建控制器,再作为 [controller]传进去:参数指向某张幻灯片时画廊自己打开,参数消失时关闭。链接可以分享,返回键关闭的是画廊而不是离开页面。

内置的 key

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

typescript
import { RkLightboxUrlOverlayComponent } from '@reelkit/angular-lightbox';
import { createOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/angular';

@Component({
  imports: [RkLightboxUrlOverlayComponent, RouterLink],
  template: `
    @for (image of images(); track image.src; let i = $index) {
      <a [routerLink]="[]" [queryParams]="{ photo: i }">
        <img [src]="image.src" alt="" />
      </a>
    }

    <rk-lightbox-url-overlay [controller]="photo" [items]="images()" />
  `,
})
export class GalleryComponent {
  protected readonly images = signal(photos);

  protected readonly photo = createOverlayUrlState({
    param: 'photo',
    ...urlIndexKey(() => this.images().length),
  });
}

请在注入上下文中调用它 —— 字段初始化器或构造函数。它会立即挂载,并通过 DestroyRef释放,因此画廊打开期间组件被销毁也不会留下监听器。完整选项见 Angular API 参考.

  • 打开时压入 一条 历史记录。翻页则是 替换 它,因此走 N 步也不会多出记录,退一步永远就是离开画廊。
  • 只有从应用内部打开画廊时返回键才会关闭它 —— 因为那次链接压入了一条记录。在新标签页里直接打开的分享链接背后没有历史,浏览器返回会离开站点;这时用 ✕ 按钮或 Escape 就地移除参数并留在页面上。
  • 指向不存在幻灯片的参数 —— 过期的书签、手改的值 —— 会从 URL 中移除,而不是继续声称一张打不开的幻灯片。
  • 模板插槽用法不变:url 组件自己执行六次插槽查询,并把每个模板转发给画廊,因此 rkLightboxControls 以及它的兄弟指令放在它内部,和放在 rk-lightbox-overlay.
  • 带路由的应用请传入基于 Router构建的适配器。绕过 Router 直接写历史会让它的 location 过期,下一次导航就会把参数丢掉。

带路由的应用请传入适配器。 绕过 Router 直接写历史会让它的 location 过期,下一次导航就会把参数丢掉,所以请基于 Router 构建适配器并作为 adapter:

typescript
import { createRouterUrlAdapter } from '@reelkit/angular/ng-router-url-adapter';

const adapter = createRouterUrlAdapter();

const photo = createOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => this.images().length),
});

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

typescript
const photo = createOverlayUrlState({
  param: 'photo',
  ...urlStableIdKey({ items: () => this.images() }),
});

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

想按别的字段(比如 slug)来寻址,或者用 locateAsync给无限信息流翻页,就自己构建 codec (传输格式)和 locator (查找):

typescript
const photo = createOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => this.images().findIndex((x) => x.id === id),
    identify: (index) => this.images()[index].id,
  },
});

无限或分页画廊。 locate 是同步的,因此只能回答已经加载过的图片 —— 只加载了 20 张时,指向第 400 张的分享链接就查不到。 locateAsync 是兜底,只有在 locate 未命中时才调用:把需要的页拉进来,再返回该身份最终对应的索引。在它未完成期间,画廊保持关闭,参数也不动,因此深链能熬过这次请求; null 或请求被拒绝则会移除参数。

快捷键

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

typescript
const photo = createOverlayUrlState({
  param: 'photo',
  codec: { decode: (raw) => raw, encode: (id) => id },
  locator: {
    locate: (id) => this.images().findIndex((x) => x.id === id),
    identify: (index) => this.images()[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
      this.images.set(loaded); // commit; the overlay renders from this
      return loaded.findIndex((x) => x.id === id); // wherever it landed
    },
  },
});

自动前进

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

  • 图片 停留 autoAdvanceInterval(默认 5000 ms),从图片加载完成的那一刻开始计算,所以加载慢的图片绝不会在没人看到之前被跳过。加载失败的图片从出错时开始计算。
  • 视频 只播放一次而不循环,播放结束后前进。在 rkLightboxSlide 模板中,给 rk-lightbox-video-slide 绑定 [loop]="!autoAdvance" 和 [onEnded]="onEnded",与视频支持中一样。
  • 它会等待:鼠标停在控件(按钮、链接或说明文字)上时、滑动过程中以及页面隐藏时,之后从停下的地方继续。
  • 手动切换(箭头、按键、滚轮、滑动)会让新幻灯片的间隔从零开始;幻灯片播放保持开启。
  • 在最后一张 时停止,除非设置了 loop。控件中的播放 / 暂停按钮显示当前状态,并让观看者切换它。

幻灯片播放运行在 Angular zone 之外,因此它的动画帧不会触发变更检测。自定义的 rkLightboxControls 模板会收到 isAutoAdvancing,并且仅在 autoAdvance 开启时收到 onToggleAutoAdvance —— 有它时就渲染 rk-play-pause-button。计时本身是 @reelkit/core 中的 createSlideshowController;见自动播放指南和核心 API。

typescript
import { Component } from '@angular/core';
import {
  RkCloseButtonComponent,
  RkLightboxControlsDirective,
  RkLightboxOverlayComponent,
  RkPlayPauseButtonComponent,
  type LightboxItem,
} from '@reelkit/angular-lightbox';

@Component({
  selector: 'app-gallery',
  imports: [
    RkLightboxOverlayComponent,
    RkLightboxControlsDirective,
    RkPlayPauseButtonComponent,
    RkCloseButtonComponent,
  ],
  template: `
    <rk-lightbox-overlay
      [isOpen]="isOpen"
      [items]="items"
      [autoAdvance]="true"
      [autoAdvanceInterval]="4000"
      (closed)="isOpen = false"
    >
      <!-- Optional: a custom bar shows the toggle only when its handler is there -->
      <ng-template rkLightboxControls let-ctx>
        @if (ctx.onToggleAutoAdvance) {
          <rk-play-pause-button
            [playing]="ctx.isAutoAdvancing"
            (toggled)="ctx.onToggleAutoAdvance()"
          />
        }
        <rk-close-button (clicked)="ctx.onClose()" />
      </ng-template>
    </rk-lightbox-overlay>
  `,
})
export class GalleryComponent {
  isOpen = false;
  items: LightboxItem[] = [{ src: '/beach.jpg' }, { src: '/forest.jpg' }];
}

下载

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

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

自定义的 rkLightboxControls 模板会收到 isDownloading,并且仅在 showDownload 开启时收到 onDownload —— 有它时就渲染 rk-download-button。工具函数是 @reelkit/core 中的 downloadFile;见下载指南。

typescript
import { Component } from '@angular/core';
import {
  RkCloseButtonComponent,
  RkDownloadButtonComponent,
  RkLightboxControlsDirective,
  RkLightboxOverlayComponent,
  type LightboxItem,
} from '@reelkit/angular-lightbox';

@Component({
  selector: 'app-gallery',
  imports: [
    RkLightboxOverlayComponent,
    RkLightboxControlsDirective,
    RkDownloadButtonComponent,
    RkCloseButtonComponent,
  ],
  template: `
    <rk-lightbox-overlay
      [isOpen]="isOpen"
      [items]="items"
      [showDownload]="true"
      (closed)="isOpen = false"
    >
      <!-- Optional: a custom bar shows the button only when its handler is there -->
      <ng-template rkLightboxControls let-ctx>
        @if (ctx.onDownload) {
          <rk-download-button
            [downloading]="ctx.isDownloading"
            (pressed)="ctx.onDownload()"
          />
        }
        <rk-close-button (clicked)="ctx.onClose()" />
      </ng-template>
    </rk-lightbox-overlay>
  `,
})
export class GalleryComponent {
  isOpen = false;
  items: LightboxItem[] = [
    {
      src: '/beach-1600.jpg',
      // Save the original rather than the resized preview
      downloadUrl: '/beach-original.jpg',
      downloadName: 'beach.jpg',
    },
  ];
}

控件按钮

组件选择器输入 / 输出说明
RkPlayPauseButtonComponentrk-play-pause-button[playing], (toggled)幻灯片播放开关(Play/Pause 图标,播放时带 aria-pressed)。设置 autoAdvance 时由默认控件显示。
RkDownloadButtonComponentrk-download-button[downloading], (pressed)下载按钮,downloading 时处于禁用状态。设置 showDownload 时由默认控件显示。
RkSoundButtonComponentrk-sound-button[muted], (toggled)视频条目的静音开关。在视频幻灯片上由默认控件显示;放进你自己的 rkLightboxControls 模板即可保留它。

RkLightboxUrlOverlayComponent 输入

接受 rk-lightbox-overlay 的所有输入,除了 isOpen,它被控制器取代。输出与 closed 和 slideChange相同;关闭由 URL 驱动,因此 closed 只是一个通知,而不是关闭机制本身。

输入类型默认值说明
controllerUrlStateController必填来自 createOverlayUrlState 的控制器。它的 position 决定画廊是否打开、显示哪一张;组件会在切换幻灯片和关闭时通过它写回。

RkLightboxOverlayComponent 输入

输入类型默认值说明
isOpenboolean必填控制显示;为 false 时浮层会从 DOM 中移除
itemsLightboxItem[]必填Lightbox条目数组(图片或视频)
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'对话框区域的无障碍标签

上面的滑动器输入(transitionDuration、swipeDistanceFactor、loop、enableNavKeys、enableWheel、wheelDebounceMs)的类型是 ReelProxyProps。为把它们转发给 Lightbox 的包装组件标注类型时使用它。

RkLightboxOverlayComponent 输出

输出类型说明
closedEventEmitter<void>用户关闭Lightbox时发出
slideChangeEventEmitter<number>当前幻灯片索引变化时发出

LightboxItem 接口

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

模板插槽上下文类型

类型字段
LightboxControlsContext{ item, activeIndex, count, isFullscreen, isAutoAdvancing, isDownloading, onClose, onToggleFullscreen, onToggleAutoAdvance?, onDownload? }
LightboxNavContext{ item, onPrev, onNext, activeIndex, count }
LightboxInfoContext{ $implicit: LightboxItem, index }
LightboxSlideContext{ $implicit: LightboxItem, index, size: [number, number], isActive, autoAdvance, onReady, onWaiting, onError, onEnded }
LightboxLoadingContext{ $implicit: activeIndex, item } —— rkLightboxLoading 模板收到的上下文
LightboxErrorContext{ $implicit: activeIndex, item } —— rkLightboxError 模板收到的上下文

过渡动画

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

函数来源说明
slideTransition@reelkit/angular-lightbox标准横向滑动(默认)
lightboxFadeTransition@reelkit/angular-lightbox图片之间交叉淡入淡出
flipTransition@reelkit/angular-lightbox3D 翻卡效果
lightboxZoomTransition@reelkit/angular-lightbox从缩小状态放大到正常尺寸
typescript
import {
  RkLightboxOverlayComponent,
  lightboxFadeTransition,
} from '@reelkit/angular-lightbox';

@Component({
  imports: [RkLightboxOverlayComponent],
  template: `
    <rk-lightbox-overlay
      [isOpen]="isOpen"
      [items]="images"
      [transitionFn]="lightboxFadeTransition"
      (closed)="isOpen = false"
    />
  `,
})
export class GalleryComponent {
  protected readonly lightboxFadeTransition = lightboxFadeTransition;
}

内容加载与错误处理

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

生命周期回调

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

在 rkLightboxSlide 中接上回调

html
<rk-lightbox-overlay [isOpen]="isOpen" [items]="items" (closed)="isOpen = false">
  <ng-template rkLightboxSlide
               let-item
               let-size="size"
               let-isActive="isActive"
               let-onReady="onReady"
               let-onWaiting="onWaiting"
               let-onError="onError">
    @if (item.type === 'video') {
      <rk-lightbox-video-slide
        [item]="item"
        [size]="size"
        [isActive]="isActive"
      />
    } @else {
      <img [src]="item.src"
           [style.width.px]="size[0]"
           [style.height.px]="size[1]"
           style="object-fit:contain"
           (load)="onReady()"
           (error)="onError()" />
    }
  </ng-template>
</rk-lightbox-overlay>

自定义加载模板

Use the rkLightboxLoading 指令替换默认的转圈动画。

html
<rk-lightbox-overlay [isOpen]="isOpen" [items]="items" (closed)="isOpen = false">
  <ng-template rkLightboxLoading let-index let-item="item">
    <div style="display:flex;flex-direction:column;align-items:center;color:#fff">
      <span>Loading image {{ index + 1 }}...</span>
      <span style="opacity:0.6">{{ item.title }}</span>
    </div>
  </ng-template>
</rk-lightbox-overlay>

自定义错误模板

Use the rkLightboxError 指令替换默认的错误图标。

html
<rk-lightbox-overlay [isOpen]="isOpen" [items]="items" (closed)="isOpen = false">
  <ng-template rkLightboxError let-index let-item="item">
    <div style="display:flex;flex-direction:column;align-items:center;color:#ef4444">
      <span>Failed to load</span>
      <span style="opacity:0.6">{{ item.title ?? item.src }}</span>
    </div>
  </ng-template>
</rk-lightbox-overlay>

CSS 类名

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

类名组件说明
.rk-lightbox-overlayOverlay根容器(全屏背景层)
.rk-lightbox-top-shadeOverlay控件背后的顶部渐变遮罩
.rk-lightbox-spinnerOverlay默认的加载转圈动画
.rk-lightbox-img-errorOverlay错误状态容器(图片损坏)
.rk-lightbox-img-error-textOverlay错误状态文字标签
.rk-lightbox-swipe-hintOverlay移动端滑动提示
.rk-lightbox-emptyOverlay空状态文字
.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-titleInfo图片标题
.rk-lightbox-descriptionInfo图片描述
.rk-lightbox-slideSlide幻灯片容器
.rk-lightbox-imgSlide图片元素
.rk-lightbox-video-containerVideoSlide视频幻灯片容器(按需开启)
.rk-lightbox-video-elementVideoSlide视频元素(按需开启)
.rk-lightbox-video-posterVideoSlide视频封面图(按需开启)
.rk-lightbox-video-errorVideoSlide视频错误状态容器

主题定制

每一个颜色、尺寸、z-index 和过渡都放在 CSS 自定义属性里。在 :root (或Lightbox的任意祖先元素)上覆盖,即可在不改组件源码的情况下换主题。这些变量与 React Lightbox保持一致,因此覆盖样式可以在不同框架绑定之间通用。

变量默认值控制内容
--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-controls-gap12px左上角控件之间的间距
--rk-lightbox-transition0.2s按钮悬停过渡时长
--rk-lightbox-blur8px按钮与标签的背景模糊半径
--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-fg#fff计数文字颜色
--rk-lightbox-counter-bgrgba(0, 0, 0, 0.5)计数标签背景
--rk-lightbox-counter-size14px计数字号
--rk-lightbox-counter-padding6px 12px计数标签内边距
--rk-lightbox-counter-radius20px计数标签圆角半径
--rk-lightbox-spinner-size28px加载指示器的默认宽高
--rk-lightbox-spinner-duration0.8s加载指示器旋转时长
--rk-lightbox-error-fgrgba(255, 255, 255, 0.4)错误图标和文字颜色
--rk-lightbox-info-bglinear-gradient(transparent, rgba(0,0,0,0.8))说明文字遮罩渐变
--rk-lightbox-info-padding24px说明文字内边距
--rk-lightbox-title-size18px标题字号
--rk-lightbox-description-size14px描述字号
--rk-lightbox-hint-fgrgba(255, 255, 255, 0.5)滑动提示文字颜色
--rk-lightbox-hint-bgrgba(0, 0, 0, 0.3)滑动提示标签背景
--rk-lightbox-video-bg#000<video> 后方的留黑背景

把下面这段放进在 @reelkit/angular-lightbox/styles.css.

css
/* Brand the lightbox */
: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-nav-size: 56px;
  --rk-lightbox-counter-bg: rgba(99, 102, 241, 0.65);
  --rk-lightbox-info-bg: linear-gradient(
    transparent,
    rgba(99, 102, 241, 0.55) 60%,
    rgba(168, 85, 247, 0.85)
  );
}

无障碍

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

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

键盘快捷键

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