在VitePress 中实现一个文档图片查看器

29 阅读2分钟

背景:因在移动端文档中的图片较小,不清晰,无法放大查看

该方案无需安装任何额外依赖,通过扩展 VitePress 主题,添加自定义样式和交互逻辑即可实现核心的图片点击放大 / 关闭功能

实现步骤

  1. 创建独立组件 :
  • 在 docs/.vitepress/theme/components/ImageViewer.vue 中创建了新的图片查看器组件
  • 封装了所有与图片放大相关的 HTML 结构、JavaScript 逻辑和 CSS 样式
  1. 组件功能完整 :
  • 支持点击文档中任何图片放大查看
  • 自动适配屏幕大小,保持图片原始比例
  • 提供平滑的淡入和缩放动画效果
  • 支持点击遮罩层、关闭按钮或按 ESC 键关闭查看器
  • 响应式设计,适配移动端

代码实现

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

// 图片查看器状态管理
const showImageViewer = ref(false)
const currentImageUrl = ref('')
const currentImageAlt = ref('')

// 打开图片查看器
function openImageViewer(event) {
  const img = event.target
  if (img.tagName === 'IMG' && !img.closest('.image-viewer-container')) {
    currentImageUrl.value = img.src
    currentImageAlt.value = img.alt || ''
    showImageViewer.value = true
    document.body.style.overflow = 'hidden'
    document.documentElement.style.overflow = 'hidden'
  }
}

// 关闭图片查看器
function closeImageViewer() {
  showImageViewer.value = false
  currentImageUrl.value = ''
  currentImageAlt.value = ''
  document.body.style.overflow = ''
  document.documentElement.style.overflow = ''
}

// 为文档中的所有图片添加点击事件
function addImageClickListeners() {
  const images = document.querySelectorAll('.VPDoc img')
  images.forEach(img => {
    img.style.cursor = 'pointer'
    img.addEventListener('click', openImageViewer)
  })
}

// 移除所有图片的点击事件
function removeImageClickListeners() {
  const images = document.querySelectorAll('.VPDoc img')
  images.forEach(img => {
    img.style.cursor = ''
    img.removeEventListener('click', openImageViewer)
  })
}

// 组件挂载后初始化
onMounted(() => {
  // 为图片添加点击事件监听
  addImageClickListeners()
  
  // 监听路由变化,重新为新页面的图片添加点击事件
  const router = window.history
  const originalPushState = router.pushState
  router.pushState = function() {
    const result = originalPushState.apply(this, arguments)
    setTimeout(addImageClickListeners, 100)
    return result
  }
})

// 组件卸载前清理
onUnmounted(() => {
  removeImageClickListeners()
})
</script>

<template>
  <!-- 图片查看器模态框 -->
  <div v-if="showImageViewer" class="image-viewer-overlay" @click="closeImageViewer">
    <div class="image-viewer-container" @click.stop>
      <img :src="currentImageUrl" :alt="currentImageAlt" class="image-viewer-image" />
      <button class="image-viewer-close" @click="closeImageViewer">×</button>
    </div>
  </div>
</template>

<style scoped>
/* 图片查看器样式 */
.image-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.9);
  z-index: 2000;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
  box-sizing: border-box;
  animation: fadeIn 0.3s ease;
}

.image-viewer-container {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.image-viewer-image {
  max-width: 100%;
  max-height: 90vh;
  object-fit: contain;
  border-radius: 4px;
  animation: zoomIn 0.3s ease;
}

.image-viewer-close {
  position: absolute;
  top: -40px;
  right: 0;
  background-color: transparent;
  border: none;
  color: white;
  font-size: 32px;
  cursor: pointer;
  width: 30px;
  height: 30px;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 0;
  line-height: 1;
}

.image-viewer-close:hover {
  opacity: 0.8;
}

/* 动画效果 */
@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes zoomIn {
  from {
    transform: scale(0.8);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

/* 移动端适配 */
@media (max-width: 640px) {
  .image-viewer-overlay {
    padding: 10px;
  }
  
  .image-viewer-close {
    top: -30px;
    font-size: 28px;
  }
}
</style>
  • 在myLayout.vue 中展示
<script setup>
...
import ImageViewer from './ImageViewer.vue'
...

</script>

<template>
  <!-- 图片查看器组件 -->
  <ImageViewer />
 </template>

  • 在自定义主题中引入
// 自定义主题配置
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './custom.css'
import MyLayout from './components/MyLayout.vue'

const theme: Theme = {
  extends: DefaultTheme,
  Layout: MyLayout,
  enhanceApp({ app, router, siteData }) {
    console.log('Custom theme initialized')
  }
}

export default theme

实现效果

image.png