VitePress选项卡markdown-it插件安装及配置教程
发布于: 2026/02/22 19:38:17
内容概要
在VitePress中使用mdit-tabs插件,以实现选项卡功能。mdit-tabs插件本身只能将Markdown转换为html,所以后续需要手动添加css样式以及js代码。
我之前在写红魔乡图片解包这篇文章的过程中,因为需要展示图片对比效果,就想到用一个选项卡来实现。然而VitePress自带的类似功能只有代码组,虽然是选项卡,但只能用来展示代码。所以,就需要安装其他插件来实现这个功能。
plugin-tab插件
我选择了一个markdown-it插件,@mdit/plugin-tab(文档)。
这个插件还有一个实用的小功能:
为了支持全局标签切换状态,该插件允许你在
tabs容器中添加一个 id 后缀,它将用作标签 id,并且还允许你在@tab标记中添加一个 id 后缀,将被使用作为选项卡值。 因此,你可以让所有具有相同 ID 的选项卡共享相同的切换事件。
安装插件
使用npm安装:
npm i -D @mdit/plugin-tab加载插件:
import { tab } from "@mdit/plugin-tab";
export default defineConfig({
markdown: {
config: (md) => {
md.use(tab, {
name: 'tabs'
});
}
}
})开发过程中可能因为缺少markdown-it报错,安装后解决:
npm i -D markdown-it插件的局限性
然而,作为一个Markdown插件,plugin-tab只能将你的Markdown代码转换为html代码,却不能提供任何css样式或js代码。
也就是说上面那个小功能需要你自己进一步实现。或者照抄我的
下面介绍手动添加css样式以及js代码的方法。
添加CSS样式
编辑css文件:
.tabs-tabs-wrapper {
margin-top: 16px;
border: 1px solid var(--vp-code-tab-divider);
border-radius: 8px;
}
.tabs-tabs-wrapper .tabs-tabs-header {
position: relative;
display: flex;
padding: 0 12px;
background-color: var(--vp-code-tab-bg);
overflow-x: auto;
overflow-y: hidden;
}
.tabs-tabs-wrapper .tabs-tabs-header button.tabs-tab-button {
position: relative;
display: inline-block;
padding: 0 12px;
line-height: 48px;
font-size: 14px;
font-weight: 500;
color: var(--vp-code-tab-text-color);
white-space: nowrap;
cursor: pointer;
transition: color 0.25s;
&::after {
position: absolute;
right: 8px;
bottom: 0px;
left: 8px;
z-index: 1;
height: 2px;
border-radius: 2px;
content: '';
background-color: transparent;
transition: background-color 0.25s;
}
&.active {
color: var(--vp-code-tab-active-text-color);
&::after {
background-color: var(--vp-code-tab-active-bar-color);
}
}
}
.tabs-tabs-wrapper .tabs-tabs-container .tabs-tab-content {
position: relative;
padding: 16px 12px;
display: none;
&.active {
display: block;
}
}
@media (max-width: 640px) {
.tabs-tabs-wrapper {
.VPDoc & {
margin-left: -22px;
margin-right: -22px;
}
.VPDoc .tabs-tab-content & {
margin-left: -10px;
margin-right: -10px;
}
}
}引入css文件:
import './css/tabs.css'添加JS代码
编辑ts文件:
import { inBrowser, onContentUpdated } from 'vitepress'
export function useMditTab(): void {
if (inBrowser) {
window.addEventListener('click', (event) => {
const el = event.target as Element;
if (el.matches('button.tabs-tab-button')) {
if (el.classList.contains('active')) return;
const index = el.getAttribute('data-tab');
if (!index) return;
const wrapper = el.parentElement?.parentElement;
if (!wrapper) return;
const wrapperId = wrapper.getAttribute('data-id');
const tabId = el.getAttribute('data-id');
if (wrapperId && tabId) {
document.querySelectorAll(`.tabs-tabs-wrapper[data-id="${wrapperId}"]`).forEach(w => {
activateTab(w, tabId, true);
});
}
else {
activateTab(wrapper, index);
}
}
});
}
}
function activateTab(wrapper: Element, key: string, useId: boolean = false): void {
const header = wrapper.querySelector(':scope > .tabs-tabs-header');
if (!header) return;
const container = wrapper.querySelector(':scope > .tabs-tabs-container');
if (!container) return;
Array.from(header.children).forEach(child => {
child.classList.remove('active');
});
Array.from(container.children).forEach(child => {
child.classList.remove('active');
});
const targetButton = header.querySelector(`:scope > .tabs-tab-button[${useId ? "data-id" : "data-tab"}="${key}"]`);
if (targetButton) {
targetButton.classList.add('active');
}
const targetTab = container.querySelector(`:scope > .tabs-tab-content[${useId ? "data-id" : "data-index"}="${key}"]`);
if (targetTab) {
activateElement(targetTab);
}
}
function activateElement(el: Element): void {
el.classList.add('active')
window.dispatchEvent(
new CustomEvent('mdit:TabsTabActivate', { detail: el })
)
}引入ts文件:
import { useMditTab } from './composables/mditTab'
export default {
setup() {
useMditTab();
}
} satisfies Theme这份代码实现了绑定点击事件,并可以按照id同步切换多个选项卡。
我没有去特意支持开发时动态热更新,不过似乎也没有什么问题。
值得注意的是,如果使用我的这份代码的话,每一组选项卡都必须指定一个初始就为active的选项卡,因为这份代码并不会自动激活任何一个未被点击的选项卡。
然而即使所有选项卡都被折叠也不算太丑。
效果展示
同步激活以及嵌套效果:
一个苹果
两根香蕉
三串葡萄
甲乙丙丁
一个苹果
两根香蕉
三串葡萄
1234
Bing
Baidu
ABCD
是
条
奇
路
Bing
Baidu
初始不指定active的效果:
(可以当默认折叠的容器使用)
(虽然展开后收不回去)
(看不见我)