搜索 K
主题
@vaultkit/vitepress-plugin-image-preview 是一个零运行时依赖的 VitePress 全屏图片查看器:点击正文图片即打开灯箱,支持滚轮缩放、1:1 复位、旋转、拖拽平移、同页画廊切换与下载,交互语义对齐 Element Plus Image Viewer。你现在就可以点击本站任意文章里的图片体验它 —— 比如 Markdown 语法示例 中的插图。
它与社区常见实现(vitepress-plugin-image-preview / ATQQ)最大的差异在安装方式:参考实现用 vite transform 改写 VitePress 内部 Layout.vue,依赖内部文件路径与 slot 标记,版本一升级就碎;本包改为 enhanceApp 一行 app.use(),在 body 上挂独立宿主 app,任意主题零侵入。
VitePress 默认不提供图片放大能力,正文里的截图、架构图在窄容器里经常看不清细节。社区方案大致两类:
本包取两者之长:功能对齐 Element Plus Image Viewer(缩放/旋转/拖拽/画廊/键盘),注入方式却只是一行 app.use()——不碰主题布局、不碰 VitePress 内部文件,peerDependencies 只有 vue ^3.5.0,没有任何运行时依赖(滚动锁自实现、图标内联 lucide SVG)。
pnpm add -D @vaultkit/vitepress-plugin-image-preview如果你用的是 @vaultkit/vitepress-theme-vault 主题,本插件开箱即带,无需单独安装。
在主题入口注册客户端插件:
import { ImagePreviewClient } from '@vaultkit/vitepress-plugin-image-preview'
import DefaultTheme from 'vitepress/theme'
export default {
extends: DefaultTheme,
enhanceApp({ app }) {
app.use(ImagePreviewClient, {
labels: { close: '关闭', prev: '上一张', next: '下一张' },
})
},
}本包 dist 内含 .vue 源文件,消费方必须把它加进 vite.ssr.noExternal,否则 SSR 构建会报错:
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
export default defineConfig({
vite: { ssr: { noExternal: ['@vaultkit/vitepress-plugin-image-preview'] } },
})就这两步。之后点击正文中任意命中选择器的图片即打开灯箱。
| 导出 | 形态 | 说明 |
|---|---|---|
ImagePreviewClient | Vue 插件(默认导出同此) | app.use() 安装,在 body 挂独立宿主 app |
ImagePreviewHost | Vue 组件 | 宿主组件:document 级点击委托 + 画廊收集 |
ImageViewer | Vue 组件 | 全屏查看器本体,可编程式单独使用 |
ImagePreviewOptions | 类型 | 插件配置项 |
ImagePreviewLabels | 类型 | 按钮文案注入 |
ImagePreviewToolbarAction | 类型 | 工具栏动作字面量联合 |
string,默认 '.vp-doc img:not(.no-preview)'默认值有两个细节:只匹配 .vp-doc(VitePress 正文区)内的图片,导航栏 logo、头像等不会误入画廊;带 no-preview 类的图片被排除,给单张图开「免预览」白名单:
{.no-preview}自定义站点结构时可以改选择器,比如把封面图也纳入:
app.use(ImagePreviewClient, {
selector: '.vp-doc img:not(.no-preview), .post-cover img',
})number,默认 1.2app.use(ImagePreviewClient, { zoomRatio: 1.1 }) // 更细腻的缩放步进number,默认 0.2 / 7[minScale, maxScale] 区间内,源码里经 Math.min/Math.max 双向钳制并保留三位小数,避免浮点误差累积。高分辨率长图站点可以放宽上界:
app.use(ImagePreviewClient, { maxScale: 12 })boolean,默认 falseis-disabled 半透明 + not-allowed 光标);开启后到尾再切回到头,取模运算实现环形索引。boolean,默认 truefalse,但博客场景「点空白处关闭」是主流预期(与 medium-zoom 行为对齐),所以本包缺省 true。若想强制用户走关闭按钮:app.use(ImagePreviewClient, { hideOnClickModal: false })boolean,默认 truewindow 上,随查看器 onMounted 绑定、onBeforeUnmount 解绑,不会泄漏。boolean,默认 trueImagePreviewToolbarAction[],默认全部六项'zoomOut' | 'zoomIn' | 'reset' | 'rotateLeft' | 'rotateRight' | 'download'。数组顺序即渲染顺序,传空数组则整条工具栏消失:// 只留缩放与下载,去掉旋转
app.use(ImagePreviewClient, {
toolbar: ['zoomOut', 'zoomIn', 'download'],
})ImagePreviewLabels,默认英文文案aria-label / title 注入,共九个字段,逐一说明:| 字段 | 默认值 | 对应控件 |
|---|---|---|
close | 'Close' | 右上角关闭按钮 |
prev | 'Previous image' | 左侧上一张箭头 |
next | 'Next image' | 右侧下一张箭头 |
zoomOut | 'Zoom out' | 工具栏缩小 |
zoomIn | 'Zoom in' | 工具栏放大 |
reset | 'Reset' | 工具栏 1:1 复位(清空平移/缩放/旋转) |
rotateLeft | 'Rotate left' | 工具栏逆时针旋转 90° |
rotateRight | 'Rotate right' | 工具栏顺时针旋转 90° |
download | 'Download' | 工具栏下载当前图 |
中文站点的完整注入示例:
app.use(ImagePreviewClient, {
labels: {
close: '关闭',
prev: '上一张',
next: '下一张',
zoomOut: '缩小',
zoomIn: '放大',
reset: '还原',
rotateLeft: '向左旋转',
rotateRight: '向右旋转',
download: '下载图片',
},
})这些文案同时写进 aria-label 与 title,屏幕阅读器与鼠标悬停提示同源,不需要分开配。
宿主组件把点击监听绑在 document 上而不是每张图片上。VitePress 是 SPA,路由切换后正文 DOM 整体重建——若逐图绑定,每次切页都要重新扫描绑定;委托到 document 则天然免重挂,新页面的图片点了照样能命中 target.matches(selector)。
另一个巧思是防「套娃」:查看器自身也渲染 <img>,如果它挂在 .vp-doc 里,会命中默认选择器、点击后再开一层查看器。本包把宿主挂在 body 直属节点(#vp-image-preview-host)下,天然不在 .vp-doc 里,结构上杜绝了递归。
传统写法是拼一个 transform: translate(...) scale(...) rotate(...) 字符串,三个分量互相覆写,还必须一起过渡。本包用 CSS 独立变换属性:
.ivp-img {
transition:
translate 0.3s,
scale 0.3s,
rotate 0.3s;
}
.ivp-img.is-dragging {
cursor: grabbing;
transition: none;
}三个属性各自独立过渡:旋转时平移不动,缩放时旋转角不变。拖拽期间加 is-dragging 类禁掉全部过渡——否则图片会「追着」指针跑而不是跟手。
拖拽用 pointerdown 里的 setPointerCapture:捕获之后 pointermove / pointerup 天然跟随该指针,即使指针移出图片区域也持续接收事件,无需在 window 上加兜底监听。松手(含 pointercancel)即释放。
@wheel.prevent——Vue 会把带 .prevent 的 wheel 编译为非 passive 监听,才能阻止页面随滚轮滚动body 滚动:保存旧的 overflow 值、关闭时恢复,自实现免引 @vueuse/corewatch(index, reset)),上一张的缩放旋转不会带到下一张ImageViewer 单独导出,可以脱离点击委托、自己控制打开时机:
<script setup lang="ts">
import { ImageViewer } from '@vaultkit/vitepress-plugin-image-preview'
import { ref } from 'vue'
const show = ref(false)
const items = [
{ src: '/a.png', alt: '图 A' },
{ src: '/b.png', alt: '图 B' },
]
</script>
<template>
<button @click="show = true">
打开相册
</button>
<ImageViewer
v-if="show"
:items="items"
:initial-index="0"
infinite
@close="show = false"
@switch="i => console.log('切到第', i + 1, '张')"
/>
</template>ImageViewer 支持事件两枚:close(请求关闭,显隐由宿主控制)与 switch(切图后携带新索引)。props 与插件 options 同名同义,另有 items({ src, alt? }[])与 initialIndex(初始索引,默认 0,越界自动钳到最后一张)。
画廊收集时取 img.currentSrc || img.src——响应式图片(srcset)场景下预览的是浏览器实际选用的那个源,而不是 src 兜底值。
下载按钮创建临时 <a download target="_blank"> 触发。注意:跨域图片若响应头没有 Access-Control-Allow-Origin 配合,浏览器会忽略 download 属性、退化为新窗口打开,这是浏览器安全策略而非插件缺陷。
Q:某些图片不想被点开怎么办? 给图片加 no-preview 类,或自定义 selector 排除。
Q:SSR 构建报 Unknown file extension ".vue"? 没配 vite.ssr.noExternal,回看上文安装一节的醒目提示。
Q:插件安装了但点图没反应? 检查图片是否在 .vp-doc 容器内(自定义布局可能没有这个类),必要时改 selector。ImagePreviewClient.install 内有幂等保护(检测 #vp-image-preview-host 已存在则跳过),重复 app.use 不会坏,但也不会应用第二份配置。
Q:移动端可以用吗? 可以。拖拽走 Pointer Events,触摸设备天然支持;但双指捏合缩放暂未实现,触屏缩放依赖工具栏按钮。