VitePress 使用 Markdown 编写的源内容,生成可以轻松部署在任何地方的静态 HTML 页面。
安装依赖 在目录中打开终端输入初始化指令
初始化向导
::: details 文件位置存放到 ./
下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 T Welcome to VitePress! | o Where should VitePress initialize the config? | ./ | o Site title: | My Awesome Project | o Site description: | A VitePress Site | o Theme: | Default Theme | o Use TypeScript for config and theme files? | Yes | o Add VitePress npm scripts to package.json? | Yes | — Done! Now run npm run docs:dev and start writing.
修改脚本命令
1 2 3 4 5 "scripts" : { "docs:dev" : "vitepress dev" , "docs:build" : "vitepress build" , "docs:preview" : "vitepress preview" }
:::
git忽略项
添加 .gitignore
文件,主要用于上传到github时,忽略这些文件不上传
1 2 3 4 5 echo node_modules >> .gitignoreecho cache >> .gitignoreecho dist >> .gitignore
启动
::: details 修改启动端口
1 2 3 "scripts" : { "docs:preview" : "vitepress preview docs --port 8080" }
:::
配置 搭建完成后,可以看到一个已经完善的目录,在此基础上进行修改和新增。
::: details 编辑config.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 export default defineConfig({ title: "title" , description: "description" , head: [ [ "link" , { rel: "icon" , href: "/icon.png" } ] , ] , lang: 'zh-CN', lastUpdated: true , cleanUrls: true , markdown: { lineNumbers: true , math: true , image: { lazyLoading: true } , } , sitemap: { hostname: 'https: } , locales: { root: { label: '简体中文', lang: 'Zh_CN', } , en: { label: 'English', lang: 'en', link: '/en/', } , fr: { label: 'French', lang: 'fr', link: '/fr/', } } , themeConfig: { logo: '/icon.png', returnToTopLabel: '返回顶部', sidebarMenuLabel: '目录', darkModeSwitchLabel: '深浅模式', outline: { level: [ 1 , 4 ] , label: '当前页大纲' } , editLink: { pattern: 'https: text: '在GitHub编辑本页' } , docFooter: { prev: '上一页', next: '下一页', } , carbonAds: { code: '...', placement: '...', } , } } )
导航栏以及自动侧边栏配置
引入 ./vitepress/utils/AutoSidebar.js
脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import path from "node:path" ;import fs from "node:fs" ;const DIR_PATH = path.resolve ();const WHITE_LIST = [ "index.md" , ".vitepress" , "node_modules" , ".idea" , "assets" , ]; const isDirectory = (path ) => fs.lstatSync (path).isDirectory ();const intersections = (arr1, arr2 ) => Array .from (new Set (arr1.filter ((item ) => !new Set (arr2).has (item)))); function getList (params, path1, pathname ) { const res = []; for (let file in params) { const dir = path.join (path1, params[file]); const isDir = isDirectory (dir); if (isDir) { const files = fs.readdirSync (dir); res.push ({ text : params[file], collapsible : true , items : getList (files, dir, `${pathname} /${params[file]} ` ), }); } else { const name = path.basename (params[file]); const suffix = path.extname (params[file]); if (suffix !== ".md" ) { continue ; } res.push ({ text : name, link : `${pathname} /${name} ` , }); } } res.map ((item ) => { item.text = item.text .replace (/\.md$/ , "" ); }); return res; } export const AutoSidebar = (pathname ) => { const dirPath = path.join (DIR_PATH , pathname); const files = fs.readdirSync (dirPath); const items = intersections (files, WHITE_LIST ); return getList (items, dirPath, pathname); };
在config.ts
中导入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { AutoSidebar } from "./utils/AutoSidebar.mjs" ; nav : [ { text : 'Docs' , items : [ { text : 'Java' , link : '/docs/java/' }, { text : 'Golang' , link : '/docs/golang/' }, { text : '前端' , link : '/docs/frontend/' }, { text : '框架' , link : '/docs/framework/' }, { text : 'DevOps' , link : '/docs/devops/' }, { text : '中间件' , link : '/docs/middleware/' }, { text : 'Juc' , link : '/docs/juc/' }, { text : '其他' , link : '/docs/other/' }, ] }, ], sidebar : { "/docs/java/" : AutoSidebar ("/docs/java" ), "/docs/golang/" : AutoSidebar ("/docs/golang" ), "/docs/frontend/" : AutoSidebar ("/docs/frontend/" ), "/docs/framework/" : AutoSidebar ("/docs/framework" ), "/docs/devops/" : AutoSidebar ("/docs/devops" ), "/docs/middleware/" : AutoSidebar ("/docs/middleware" ), "/docs/juc/" : AutoSidebar ("/docs/juc" ), "/docs/other/" : AutoSidebar ("/docs/other" ), },
:::
社交链接
1 2 3 4 5 6 7 8 9 10 11 12 export default defineConfig ({ themeConfig : { socialLinks : [ { icon : 'github' , link : 'https://github.com/vuejs/vitepress' }, { icon : 'twitter' , link : 'https://twitter.com/' }, { icon : 'discord' , link : 'https://chat.vitejs.dev/' }, ], }, })
::: details vitepress自带的其他社交图标
1 2 3 4 5 6 7 8 9 10 11 12 13 export type SocialLinkIcon = | 'discord' | 'facebook' | 'github' | 'instagram' | 'linkedin' | 'mastodon' | 'slack' | 'twitter' | 'x' | 'youtube' | { svg : string }
:::
搜索框
::: code-group
1 2 3 4 5 6 7 export default defineConfig({ themeConfig: { search: { provider: 'local' }, }, })
1 2 3 4 5 6 7 8 9 10 11 12 export default defineConfig({ themeConfig: { search: { provider: 'algolia', options: { appId: '41AKMNBQQ4', apiKey: '9873076cd6001ca24a681f4295fc10ed', indexName: 'hougen' } }, }, })
:::
编辑本页
1 2 3 4 5 6 7 8 9 10 11 export default defineConfig ({ themeConfig : { editLink : { pattern : 'https://github.com/vuejs/vitepress/edit/main/docs/:path' , text : '在GitHub编辑本页' }, }, })
美化&插件 容器 可以通过其类型、标题和内容来定义
输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ::: info 这是一条信息,info后面的文字可修改 ::: ::: tip 说明 这是一个提示,tip后面的文字可修改 ::: ::: warning 警告 这是一条警告,warning后面的文字可修改 ::: ::: danger 危险 这是一个危险警告,danger后面的文字可修改 ::: ::: details **<font color =#6DD3E3 > 修改Badge颜色</font > ** :::
输出
::: info 这是一条信息,info后面的文字可修改 :::
::: tip 说明 这是一个提示,tip后面的文字可修改 :::
::: warning 警告 这是一条警告,warning后面的文字可修改 :::
::: danger 危险 这是一个危险警告,danger后面的文字可修改 :::
::: details 修改Badge颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 var .css :root { --vp-c-brand-1 : #18794e ; --vp-c-brand-2 : #299764 ; --vp-c-brand-3 : #30a46c ; } .dark { --vp-c-brand-1 : #3dd68c ; --vp-c-brand-2 : #30a46c ; --vp-c-brand-3 : #298459 ; } .vp-doc a { text-decoration : none; } h1 { background : #1352a0 ; background-clip : text; -webkit-background-clip : text; -webkit-text -fill -color : transparent; } .medium-zoom-overlay { z-index : 20 ; } .medium-zoom-image { z-index : 9999 !important ; } :root { --vp-custom-block-tip-bg : var (--vp-c-green-soft); } .custom-block .tip { border-color : var (--vp-c-green-2 ); } .custom-block .warning { border-color : var (--vp-c-yellow-2 ); } .custom-block .danger { border-color : var (--vp-c-red-2 ); } .custom-block .details { border-color : #6DD3E3 ; } .vp-doc blockquote { border-radius : 5px ; padding : 10px 16px ; background-color : var (--vp-badge-danger-bg); position : relative; border-left : 4px solid #e95f59 ; }
:::
Badge组件
输入
1 2 3 4 * VitePress <Badge type ="info" text ="default" /> * VitePress <Badge type ="tip" text ="^1.9.0" /> * VitePress <Badge type ="warning" text ="beta" /> * VitePress <Badge type ="danger" text ="caution" />
::: details 输出
VitePress
VitePress
VitePress
VitePress
:::
代码块
行高亮
1 2 3 4 5 6 7 ```ts{2-3,5} export default defineConfig({ lang: 'zh-CN', title: "VitePress", titleTemplate: '另起标题覆盖title', }) ```
::: details 输出
1 2 3 4 5 export default defineConfig({ lang: 'zh-CN', title: "VitePress", titleTemplate: '另起标题覆盖title', })
:::
聚焦代码 在某一行后添加 // [!code focus]
注释会聚焦该行,并模糊代码的其他部分。
1 2 3 4 5 6 7 ```ts export default defineConfig({ lang: 'zh-CN', title: "VitePress", //[!code focus] titleTemplate: '另起标题覆盖title', }) ```
::: details 输出
1 2 3 4 5 export default defineConfig ({ lang : 'zh-CN' , title : "VitePress" , titleTemplate : '另起标题覆盖title' , })
:::
视图过渡 扩展默认主题以在切换颜色模式时提供自定义过渡动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 <!-- .vitepress/theme/Layout.vue --> <script setup lang="ts"> import { useData } from 'vitepress' import DefaultTheme from 'vitepress/theme' import { nextTick, provide } from 'vue' const { isDark } = useData() const enableTransitions = () => 'startViewTransition' in document && window.matchMedia('(prefers-reduced-motion: no-preference)').matches provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => { if (!enableTransitions()) { isDark.value = !isDark.value return } const clipPath = [ `circle(0px at ${x}px ${y}px)`, `circle(${Math.hypot( Math.max(x, innerWidth - x), Math.max(y, innerHeight - y) )}px at ${x}px ${y}px)` ] await document.startViewTransition(async () => { isDark.value = !isDark.value await nextTick() }).ready document.documentElement.animate( { clipPath: isDark.value ? clipPath.reverse() : clipPath }, { duration: 300, easing: 'ease-in', pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)` } ) }) </script> <template> <DefaultTheme.Layout /> </template> <style> ::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; } ::view-transition-old(root), .dark::view-transition-new(root) { z-index: 1; } ::view-transition-new(root), .dark::view-transition-old(root) { z-index: 9999; } .VPSwitchAppearance { width: 22px !important; } .VPSwitchAppearance .check { transform: none !important; } </style
::: details 效果
:::
视频播放器组件 安装vue
以及 less
1 2 npm add -D vue npm add -D less
在 theme
目录中 创建 components
文件夹,然后创建 Video.vue
::: details 查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 <script setup lang="ts"> import { ref, onMounted } from 'vue' interface Props { src: string // 视频文件地址,支持网络地址 https 和相对地址 poster?: string // 视频封面地址,支持网络地址 https 和相对地址 second?: number // 在未设置封面时,自动截取视频第 second 秒对应帧作为视频封面 autoplay?: boolean // 视频就绪后是否马上播放,优先级高于preload controls?: boolean // 是否向用户显示控件,比如进度条,全屏等 loop?: boolean // 视频播放完成后,是否循环播放 muted?: boolean // 是否静音 preload?: 'auto'|'metadata'|'none' // 是否在页面加载后载入视频,如果设置了autoplay属性,则preload将被忽略 showPlay?: boolean // 播放暂停时是否显示播放器中间的暂停图标 fit?: 'none'|'fill'|'contain'|'cover' // video的poster默认图片和视频内容缩放规则 } const props = withDefaults(defineProps<Props>(), { src: '', poster: '', second: 0.5, /* 参考 MDN 自动播放指南:https://developer.mozilla.org/zh-CN/docs/Web/Media/Autoplay_guide Autoplay 功能 据新政策,媒体内容将在满足以下至少一个的条件下自动播放: 1.音频被静音或其音量设置为 0 2.用户和网页已有交互行为(包括点击、触摸、按下某个键等等) 3.网站已被列入白名单;如果浏览器确定用户经常与媒体互动,这可能会自动发生,也可能通过首选项或其他用户界面功能手动发生 4.自动播放策略应用到<iframe>或者其文档上 autoplay:由于目前在最新版的Chrome浏览器(以及所有以Chromium为内核的浏览器)中, 已不再允许自动播放音频和视频。就算你为video或audio标签设置了autoplay属性也一样不能自动播放! 解决方法:设置视频 autoplay 时,视频必须设置为静音 muted: true 即可实现自动播放, 然后用户可以使用控制栏开启声音,类似某宝商品自动播放的宣传视频逻辑 */ autoplay: false, controls: true, loop: false, muted: false, /* preload可选属性: auto: 一旦页面加载,则开始加载视频; metadata: 当页面加载后仅加载视频的元数据(例如长度),建议使用metadata,以便视频自动获取第一帧作为封面poster none: 页面加载后不应加载视频 */ preload: 'auto', showPlay: true, /* fit可选属性: none: 保存原有内容,不进行缩放; fill: 不保持原有比例,内容拉伸填充整个内容容器; contain: 保存原有比例,内容以包含方式缩放; cover: 保存原有比例,内容以覆盖方式缩放 */ fit: 'contain' }) // 参考文档:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video const veoPoster = ref(props.poster) const originPlay = ref(true) const hidden = ref(false) // 是否隐藏播放器中间的播放按钮 // 为模板引用标注类型 const veo = ref<HTMLVideoElement | null>(null) // 声明一个同名的模板引用 /* loadeddata 事件在媒体当前播放位置的视频帧(通常是第一帧)加载完成后触发 preload为none时不会触发 */ function getPoster () { // 在未设置封面时,自动截取视频0.5s对应帧作为视频封面 // 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图 veo.value!.currentTime = props.second // 创建canvas元素 const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') // canvas画图 canvas.width = veo.value!.videoWidth canvas.height = veo.value!.videoHeight ctx?.drawImage(veo.value!, 0, 0, canvas.width, canvas.height) // 把canvas转成base64编码格式 veoPoster.value = canvas.toDataURL('image/png') } function onPlay () { if (originPlay.value) { veo.value!.currentTime = 0 originPlay.value = false } if (props.autoplay) { veo.value?.pause() } else { hidden.value = true veo.value?.play() } } function onPause () { hidden.value = false } function onPlaying () { hidden.value = true } onMounted(() => { if (props.autoplay) { hidden.value = true originPlay.value = false } /* 自定义设置播放速度,经测试: 在vue2中需设置:this.$refs.veo.playbackRate = 2 在vue3中需设置:veo.value.defaultPlaybackRate = 2 */ // veo.value.defaultPlaybackRate = 2 }) </script> <template> <div class="m-video" :class="{'u-video-hover': !hidden}"> <video ref="veo" :style="`object-fit: ${fit};`" :src="src" :poster="veoPoster" :autoplay="autoplay" :controls="!originPlay && controls" :loop="loop" :muted="autoplay || muted" :preload="preload" crossorigin="anonymous" @loadeddata="poster ? () => false : getPoster()" @pause="showPlay ? onPause() : () => false" @playing="showPlay ? onPlaying() : () => false" @click.prevent.once="onPlay" v-bind="$attrs"> 您的浏览器不支持video标签。 </video> <span v-show="originPlay || showPlay" class="m-icon-play" :class="{'hidden': hidden}"> <svg class="u-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 34"> <path d="M28.26,11.961L11.035,0.813C7.464-1.498,3,1.391,3,6.013v21.974c0,4.622,4.464,7.511,8.035,5.2L28.26,22.039 C31.913,19.675,31.913,14.325,28.26,11.961z"></path> </svg> </span> </div> </template> <style lang="less" scoped> .m-video { display: inline-block; position: relative; background: #000; cursor: pointer; width: 100%; height: 100%; .m-icon-play { display: inline-block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; width: 80px; height: 80px; border-radius: 50%; background-color: rgba(0, 0, 0, .6); pointer-events: none; transition: background-color .3s; .u-svg { display: inline-block; fill: #FFF; width: 29px; height: 34px; margin-top: 23px; margin-left: 27px; } } .hidden { opacity: 0; } } .u-video-hover { &:hover { .m-icon-play { background-color: rgba(0, 0, 0, .7); } } } </style>
在index.ts
中注册全局组件
1 2 3 4 5 6 7 8 9 10 import Video from "./components/Video.vue" export default { extends : DefaultTheme , enhanceApp ({app} ) { app.component ('Video' , Video ) } }
:::
输入
1 2 3 4 5 <Video src ="lol.mp4" :width ="666.67" :height ="375" :second ="3" />
时间线 1 npm add -D vitepress-markdown-timeline
在 config.mts
中注册 markdown 解析插件
1 2 3 4 5 6 7 8 9 10 import timeline from "vitepress-markdown-timeline" ; export default { markdown : { config : (md ) => { md.use (timeline); }, }, }
在 .vitepress/theme/index.ts
中引入时间线样式
1 2 3 4 5 6 7 8 9 import DefaultTheme from 'vitepress/theme' import "vitepress-markdown-timeline/dist/theme/index.css" ;export default { extends : DefaultTheme , }
使用示例
1 2 3 4 5 6 7 8 9 ::: timeline 2023-04-24 - 一个非常棒的开源项目 H5-Dooring 目前 star 3.1k - 开源地址 https://github.com/MrXujiang/h5-Dooring - 基本介绍 http://h5.dooring.cn/doc/zh/guide/- 《深入浅出webpack》 http://webpack.wuhaolin.cn/::: ::: timeline 2023-04-23 :::
::: timeline 2023-04-24
::: timeline 2023-04-23
:::
谷歌分析 仓库:https://github.com/ZhongxuYang/vitepress-plugin-google-analytics
1 npm add -D vitepress-plugin-google-analytics
图片缩放
::: details 插件配置
在 .vitepress/theme/index.ts
添加如下代码,并保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import DefaultTheme from 'vitepress/theme' import mediumZoom from 'medium-zoom' ;import { onMounted, watch, nextTick } from 'vue' ;import { useRoute } from 'vitepress' ;export default { extends : DefaultTheme , setup ( ) { const route = useRoute (); const initZoom = ( ) => { mediumZoom ('.main img' , { background : 'var(--vp-c-bg)' }); }; onMounted (() => { initZoom (); }); watch ( () => route.path , () => nextTick (() => initZoom ()) ); }, }
点击图片后,还是能看到导航栏,加一个遮挡样式,在 .vitepress/theme/style/var.css
中加入如下代码,并保存。
1 2 3 4 5 6 7 8 9 .medium-zoom-overlay { z-index : 20 ; } .medium-zoom-image { z-index : 9999 !important ; }
:::
评论
Giscus 是一个基于 GitHub Discussion 的评论系统,启用简便,进 Giscus App官网:https://github.com/apps/giscus 点击 Install
安装。
选择 Only select repositories
,再指定一个开启讨论的仓库。
::: tip 注意
仓库必须是公开的,私有的不行,想单独放评论,可以新建一个仓库。
:::
进入要开启讨论的仓库,点设置 - 勾选讨论 Settings - discussions
生成数据 输入自己的仓库链接,满足条件会提示可用。下拉到 Discussion 分类我们按推荐的选 Announcements
,懒加载评论也可以勾选下。
保存脚本数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script src="https://giscus.app/client.js" data-repo="Yiov/vitepress-doc" data-repo-id="R_******" data-category="Announcements" data-category-id="DIC_******" data-mapping="pathname" data-strict="0" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="bottom" data-theme="preferred_color_scheme" data-lang="zh-CN" data-loading="lazy" crossorigin="anonymous" async> </script>
安装
1 npm add -D vitepress-plugin-comment-with-giscus
::: details 在 .vitepress/theme/index.ts
中配置
将之前获取的4个关键数据填入,其他保持默认保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import DefaultTheme from 'vitepress/theme' ;import giscusTalk from 'vitepress-plugin-comment-with-giscus' ;import { useData, useRoute } from 'vitepress' ;export default { extends : DefaultTheme , setup ( ) { const { frontmatter } = useData (); const route = useRoute (); giscusTalk ({ repo : 'your github repository' , repoId : 'your repository id' , category : 'Announcements' , categoryId : 'your category id' , mapping : 'pathname' , inputPosition : 'bottom' , lang : 'zh-CN' , }, { frontmatter, route }, true ); }
:::
插槽配置 参考博客: https://vitepress.yiov.top/layout.html