Files
iotlcm/.vitepress/theme/index.ts
T
2026-10-26 15:30:14 +08:00

79 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import DefaultTheme from 'vitepress/theme'
import mediumZoom from 'medium-zoom'
import { onContentUpdated } from 'vitepress'
import { onMounted } from 'vue'
import type { Theme } from 'vitepress'
import './custom.css'
/**
* 参与「点击放大」的图片范围。
*
* `.vp-doc` 是 VitePress 默认主题给正文容器加的类名,使用手册 9 页与接口文档
* 全部页面共用这一套布局,所以在这里定义一次即对全站生效。
*
* 排除项:
* a img 图片本身是链接(如带外链的截图),点击应跳转而不是放大
* .no-zoom 需要单独关闭放大的图片,示例:
* `<img src="/images/manual/xx.png" class="no-zoom" alt="xx">`
*/
const ZOOM_SELECTOR = '.vp-doc img:not(a img):not(.no-zoom)'
/**
* 全站只保留一个 medium-zoom 实例,但【必须惰性创建】。
*
* 两个坑叠在一起,才得到这个写法:
*
* 1. 不能在模块顶层创建实例。medium-zoom 1.1.0 的工厂函数第一件事就是读
* `window.Promise`(dist/medium-zoom.min.js 里即 `var Promise = window.Promise || ...`),
* 而主题模块在 `vitepress build` 时会被 Node 端 SSR 打包并执行 —— 没有 window,
* 直接 `ReferenceError: window is not defined` 构建失败。
* 所以实例只能在浏览器生命周期回调里创建。
*
* 2. 也不能每次路由切换都 `mediumZoom(...)` 新建。每个实例都会在 document 上注册
* click / keyup / scroll、在 window 上注册 resize 监听器,且没有卸载入口
* (见 dist/medium-zoom.esm.js:576-581)。翻 N 个页面就会留下 N 组永不释放的
* 全局监听器。正确做法是复用实例,路由切换时只做 attach —— attach 内部会跳过
* 已挂载的图片(dist/medium-zoom.esm.js:199-204),因此重复调用是幂等的。
*/
let zoom: ReturnType<typeof mediumZoom> | null = null
const attachZoom = () => {
if (typeof window === 'undefined') return
if (!zoom) {
zoom = mediumZoom({
background: 'var(--vp-c-bg)',
margin: 24,
})
}
zoom.attach(ZOOM_SELECTOR)
}
const closeZoomOnClick = (event: MouseEvent) => {
const target = event.target as Element | null
if (!zoom || !target) return
if (target.closest('.medium-zoom-image--opened, .medium-zoom-overlay')) {
void zoom.close()
}
}
const theme: Theme = {
extends: DefaultTheme,
setup() {
// onContentUpdated 由 VitePress 在「新页面内容挂载完成后」触发,覆盖首次进入
// 与 SPA 客户端路由切换。它挂在路由组件的 onVnodeMounted 上
// (见 vitepress/dist/client/app/components/Content.js:13-20),
// 比 watch(route.path) + nextTick 更可靠 —— 后者可能在异步页面组件挂载前就执行,
// 导致某些页面图片没挂上监听、点击放大静默失效。
onContentUpdated(attachZoom)
// 兜底:主题 setup 时内容已就绪(不经过 onVnodeMounted 的极端情况)也能挂上
onMounted(attachZoom)
if (typeof document !== 'undefined') {
document.addEventListener('click', closeZoomOnClick, true)
}
},
}
export default theme