跳转到内容

组件 ​

适合对工具栏有定制需求、动态渲染 Markdown 或 HTML 等未知图片内容的复杂场景。默认监听组件内部 <img> 元素的增删和属性变化,自动更新图库。

旧版本升级提醒

Vue 3 的 5.x 是包含破坏性变更的主版本重构。现在可以在组件上直接使用 Viewer.js 的属性、事件和方法。旧的 options 属性保留兼容,但不推荐继续使用,现有项目可以逐步迁移。

演练场 ​

演练场

vue
<script setup>
import { ref } from 'vue'
import { Viewer } from 'v-viewer'
const sourceImages = [
  '/images/photo-01.jpg',
  '/images/photo-02.jpg',
  '/images/photo-03.jpg',
  '/images/photo-04.jpg',
  '/images/photo-05.jpg',
  '/images/photo-06.jpg',
  '/images/photo-07.jpg',
  '/images/photo-08.jpg',
  '/images/photo-09.jpg',
  '/images/photo-10.jpg',
].map((original) => ({
  src: original.replace('/images/', '/images/thumbnails/'),
  'data-source': original,
}))
const images = ref(sourceImages.slice(0, 5))
const toolbar = ref(true)
function onViewed(event) {
  console.log(event.detail.index)
}
const gallery = ref(null)
function show() {
  gallery.value.show()
}
function add() {
  images.value.push(sourceImages[images.value.length])
}
function remove() {
  images.value.pop()
}
</script>

<template>
  <main class="viewer-example">
    <div class="controls">
      <button :disabled="images.length === sourceImages.length" @click="add">
        添加图片
      </button>
      <button :disabled="images.length === 0" @click="remove">删除图片</button>
      <button class="open" :disabled="images.length === 0" @click="show()">
        显示viewer
      </button>
      <div class="toolbar" role="group" aria-label="切换工具栏">
        <span>切换工具栏</span>
        <button
          :aria-pressed="toolbar ? 'true' : 'false'"
          @click="toolbar = true"
        >
          开启
        </button>
        <button
          :aria-pressed="toolbar ? 'false' : 'true'"
          @click="toolbar = false"
        >
          关闭
        </button>
      </div>
    </div>
    <Viewer
      ref="gallery"
      :toolbar="toolbar"
      url="data-source"
      @viewed="onViewed"
      class="gallery"
      ><img
        v-for="(image, index) in images"
        :key="image.src"
        :src="image.src"
        :data-source="image['data-source']"
        :alt="`Landscape ${index + 1}`"
    /></Viewer>
  </main>
</template>

属性 ​

Viewer.js 原生属性 ​

对应 Viewer.js 配置项,点击属性名可查看原文档。可用配置和默认值以安装的 Viewer.js 版本为准;配置及其嵌套值变化后,组件会在 DOM 更新后重建实例。

属性类型作用
backdropboolean / string弹窗模式的背景遮罩;设为 static 时点击遮罩不关闭弹窗。
buttonboolean显示右上角按钮。
classNamestring添加到查看器根元素的自定义类名。
containerstring / Element弹窗模式的挂载容器,可传元素或 CSS 选择器。
filterfunction筛选可查看的图片,返回 true 保留,false 忽略。
fullscreenboolean / FullscreenOptions播放时请求浏览器全屏,可传入 Fullscreen API 配置。
focusboolean初始化时聚焦缩略图导航的当前项,需要开启 keyboard。
inheritedAttributesarray从源图片额外继承的属性;src、alt 始终继承。
initialCoveragenumber图片初始覆盖比例,取值大于 0 且不超过 1。
initialViewIndexnumber初始图片下标,也作为 view() 的默认下标。
inlineboolean开启内嵌模式,在容器内显示;关闭时使用弹窗模式。
autoplayboolean播放时自动切换图片。
intervalnumber播放时自动切换图片的间隔,单位毫秒。
keyboardboolean启用键盘操作。
loadingboolean加载图片时显示加载指示器。
loopboolean循环浏览,首尾图片之间可继续切换。
magnifierboolean / object全屏查看时悬停显示放大镜,可设置尺寸、倍率和透明度;触屏禁用。
maxZoomRationumber / function最大放大比例,也可由函数根据图片和图片数据计算。
minHeightnumber内嵌模式下查看器的最小高度,单位像素。
minWidthnumber内嵌模式下查看器的最小宽度,单位像素。
minZoomRationumber / function最小缩放比例,也可由函数根据图片和图片数据计算。
movableboolean允许移动图片。
navbarboolean / number / string / object缩略图导航的显示、尺寸和位置;对象配置支持更多布局选项。
navigationboolean / number / object查看器左右两侧的上一张、下一张按钮,可分别设置显示和尺寸。
preloadboolean提前加载上一张或下一张图片。
rotatableboolean允许旋转图片。
rotateOnGestureboolean通过手势事件旋转图片,例如 Mac Safari 的双指手势。
rotateOnTouchboolean通过触屏双指手势旋转图片。
scalableboolean允许按轴缩放图片,可用于水平或垂直翻转。
slideOnTouchboolean通过触屏滑动切换上一张或下一张图片。
slideOnWheelboolean / string通过滚轮切图,可指定修饰键;在缩略图导航区域或滚轮缩放未生效时可用。
titleboolean / number / function / array图片标题的显示和内容;默认使用 alt 或从 URL 提取的文件名。
toggleOnDblclickboolean双击时在图片自然尺寸和初始尺寸之间切换。
toolbarboolean / number / object工具栏及按钮的显示、布局、尺寸和点击行为。
tooltipboolean缩放时显示当前图片比例的百分比提示。
transitionboolean / object启用 CSS 过渡动画,也可通过对象关闭指定操作的动画。
urlstring / function原图地址来源;字符串指定图片属性名,函数返回图片 URL。
zIndexnumber弹窗模式下查看器的 CSS z-index。
zIndexInlinenumber内嵌模式下查看器的 CSS z-index。
zoomOnGestureboolean通过手势事件缩放图片,例如 Mac Safari 的捏合手势。
zoomOnTouchboolean通过触屏拖动手势缩放图片。
zoomOnWheelboolean / string通过滚轮缩放,可指定修饰键或修饰键组合。
zoomRationumber滚轮缩放时每次变化的比例。
zoomableboolean允许缩放图片。

v-viewer 独有属性 ​

属性类型作用
optionsViewerOptions默认 {};保留兼容但不推荐,优先使用直接属性和 Vue 事件。
rebuildboolean默认 false;图片变化时是否销毁并重建实例。
observeboolean / number默认 true(50ms);false 关闭监听;非负数字指定合并更新的等待毫秒数。

组件自动监听内部 <img> 元素的增删和属性变化,包括整段 HTML 的替换;默认调用 update() 更新图库,设置 rebuild 时改为重建。

取值、更新时机及适用场景详见自动更新 observe。

事件 ​

以下均为 Viewer.js 原生事件,通过 Vue 事件使用;点击事件名可查看原文档。

事件参数为原始 DOM 事件,操作数据位于 event.detail。对于支持取消的操作,可使用 @show.prevent 或 event.preventDefault() 阻止执行。

名称触发时机 / 常用参数
ready查看器界面初始化完成;弹窗模式下可能在首次打开时才触发。
show / shown弹窗打开前 / 打开过渡动画完成后。
hide / hidden弹窗关闭前 / 关闭过渡动画完成后。
view / viewed图片显示前 / 显示完成后;detail.index 为从 0 开始的图片下标,image 和 originalImage 分别为预览图片与源图片元素。
move / moved移动前 / 后;detail.x、y、oldX、oldY 表示位置变化。
rotate / rotated旋转前 / 后;detail.degree、oldDegree 表示角度变化。
scale / scaled按轴缩放或翻转前 / 后;detail.scaleX、scaleY、oldScaleX、oldScaleY 表示变化。
zoom / zoomed缩放前 / 后;detail.ratio、oldRatio 表示缩放比例。
play / stop开始 / 停止幻灯片播放前。
playing幻灯片播放中显示每张图片之前。

回调 ​

兼容入口 options 仍支持 options.viewed 等 Viewer.js 回调,但不推荐继续使用,优先使用 @viewed 等 Vue 事件。回调参数与组件事件是同一个原始 DOM 事件;同时设置时,两者都会执行。

方法 ​

Viewer.js 原生方法 ​

通过组件 ref 调用,点击方法名可查看对应的 Viewer.js 原文档。

方法作用
show(immediate?)显示弹窗;true 表示立即显示。仅弹窗模式可用。
hide(immediate?)隐藏弹窗;true 表示立即隐藏。仅弹窗模式可用。
view(index?)按下标查看图片;省略时使用 initialViewIndex,隐藏时会先显示查看器。
prev(loop?)查看上一张;loop 控制是否从首张跳到末张,默认 false。
next(loop?)查看下一张;loop 控制是否从末张跳到首张,默认 false。
move(x, y?)按相对偏移量移动图片;省略 y 时使用 x。
moveTo(x, y?)将图片移动到绝对位置;省略 y 时使用 x。
rotate(degree)按相对角度旋转;正数向右,负数向左。
rotateTo(degree)旋转到指定的绝对角度。
scale(scaleX, scaleY?)按轴缩放;省略 scaleY 时使用 scaleX,负值可翻转图片。
scaleX(scaleX)按水平方向缩放;-1 水平翻转。
scaleY(scaleY)按垂直方向缩放;-1 垂直翻转。
zoom(ratio, showTooltip?, pivot?)按相对比例缩放;正数放大、负数缩小,可指定提示和缩放中心。
zoomTo(ratio, showTooltip?, pivot?)缩放到绝对比例,1 为自然尺寸;可指定提示和缩放中心。
play(fullscreen?)开始播放图片;参数控制是否请求浏览器全屏。
stop()停止播放图片。
full()从内嵌模式进入弹窗模式,仅内嵌查看器可用。
exit()退出弹窗模式并恢复内嵌显示,仅内嵌查看器可用。
showTooltip()显示当前图片比例的百分比,需要开启 tooltip;对应原生 tooltip(),为避免属性重名而改名。
toggle()在当前尺寸和自然尺寸之间切换图片大小。
reset()将图片恢复到初始状态。
update()源图片增删或排序变化后,更新查看器的图片列表。
destroy()销毁查看器并移除实例。

图片操作需等待 @viewed。除 update() 和 destroy() 外,上述方法返回 Viewer.js 实例;没有实例时返回 undefined。

update() 和 destroy() 均无返回值。update() 会在实例不存在时创建实例,不接收 options,配置通过属性修改;destroy() 同时断开监听并清空组件的实例引用,保留源图片。

v-viewer 独有方法 ​

方法作用
createViewer()使用当前属性和图片 DOM 创建实例;已有实例时不重复创建。
rebuildViewer()销毁当前实例,按最新属性和图片 DOM 重建;没有实例时直接创建。

需要访问底层实例数据时,可读取 gallery.value.viewer。

插槽 ​

默认插槽 ​

在默认插槽中渲染图片及自定义布局。

vue
<Viewer :toolbar="false">
  <img v-for="src in images" :key="src" :src="src" />
</Viewer>

渲染得到的 HTML 也可直接作为插槽内容:

vue
<Viewer>
  <article v-html="html" />
</Viewer>

工具栏插槽 ​

通过 Vue 模板、图标组件和事件绑定定制工具栏。

插槽作用
toolbar-append在工具栏后追加内容,也可与自定义 toolbar 同时使用。
toolbar替换全部内置工具栏按钮,自定义图标、排列和交互。

两个插槽均提供 image(当前预览图片的 HTMLImageElement,图片显示前为 undefined)、index(从 0 开始),以及 zoom、rotate、reset、next 等组件上的 Viewer.js 方法。插槽参数随预览状态更新。

toolbar: false 会隐藏整个工具栏,包括插槽内容。不使用完整替换插槽时,原 toolbar 配置仍决定按钮是否显示。

示例代码
vue
<script setup>
import { ref } from 'vue'
import { Viewer } from 'v-viewer'
const images = [
  '/images/photo-01.jpg',
  '/images/photo-02.jpg',
  '/images/photo-03.jpg',
  '/images/photo-04.jpg',
  '/images/photo-05.jpg',
]
const inline = ref(true)
const custom = ref(false)
</script>

<template>
  <main class="viewer-example toolbar-example">
    <div class="controls">
      <label><input v-model="inline" type="checkbox" /> 内嵌模式</label>
      <label><input v-model="custom" type="checkbox" /> 替换整个工具栏</label>
    </div>
    <div class="toolbar-stage" :class="{ 'is-inline': inline }">
      <Viewer :inline="inline" :focus="!inline" :min-height="320">
        <div v-show="!inline" class="gallery">
          <img
            v-for="(src, index) in images"
            :key="src"
            :src="src"
            :alt="'Landscape ' + (index + 1)"
          />
        </div>
        <template v-if="custom" #toolbar="{ zoom, rotate, reset }">
          <button
            type="button"
            class="viewer-slot-button viewer-custom-button"
            title="放大"
            aria-label="放大"
            @click="zoom(0.1)"
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="1.75"
              stroke-linecap="round"
              stroke-linejoin="round"
              aria-hidden="true"
            >
              <circle cx="10" cy="10" r="6.5" />
              <path d="m15 15 5.5 5.5M7 10h6m-3-3v6" />
            </svg>
          </button>
          <button
            type="button"
            class="viewer-slot-button viewer-custom-button"
            title="旋转"
            aria-label="旋转"
            @click="rotate(90)"
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="1.75"
              stroke-linecap="round"
              stroke-linejoin="round"
              aria-hidden="true"
            >
              <rect x="4" y="11" width="9" height="9" rx="1.5" />
              <path d="M5 7a9 9 0 0 1 15 7m0-5v5h-5" />
            </svg>
          </button>
          <button
            type="button"
            class="viewer-slot-button viewer-custom-button"
            title="重置"
            aria-label="重置"
            @click="reset()"
          >
            <svg
              width="20"
              height="20"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="1.75"
              stroke-linecap="round"
              stroke-linejoin="round"
              aria-hidden="true"
            >
              <path
                d="M8 3H3v5m13-5h5v5M3 16v5h5m13-5v5h-5M8 12a4 4 0 1 0 1-2.6M8 7v3h3"
              />
            </svg>
          </button>
        </template>
        <template #toolbar-append="{ image, index }">
          <a
            v-if="image"
            class="viewer-slot-button"
            :class="{ 'viewer-custom-button': custom }"
            :href="image.src"
            :download="image.alt"
            :title="'下载图片 ' + (index + 1)"
            :aria-label="'下载图片 ' + (index + 1)"
          >
            <svg
              width="16"
              height="16"
              viewBox="0 0 20 20"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              aria-hidden="true"
            >
              <path d="M10 2v10m-4-4 4 4 4-4M3 12v5h14v-5" />
            </svg>
          </a>
        </template>
      </Viewer>
    </div>
  </main>
</template>

旧版本迁移 ​

vue
<!-- 旧写法:仍兼容,但不推荐 -->
<Viewer :options="{ toolbar: false, viewed: onViewed }">
  <img src="/photo.jpg" />
</Viewer>

<!-- 推荐写法 -->
<Viewer :toolbar="false" @viewed="onViewed">
  <img src="/photo.jpg" />
</Viewer>

基于 MIT 许可发布