VitePress 系列教程:05-内容展示优化
首页组件
如果我们觉得首页内容比较单调,想自己设计点可交互的内容,我们可以通过以下方案
追加样式内容
我们在首页 index.md
中添加内容
---
layout: home
hero:
name: VitePress
text: Vite & Vue powered static site generator.
tagline: Lorem ipsum...
image:
src: /logo.svg
alt: VitePress
actions:
- theme: brand
text: Get Started
link: /guide/what-is-vitepress
- theme: alt
text: View on GitHub
link: https://github.com/vuejs/vitepress
features:
- icon: ⚡️
title: Vite, The DX that can't be beat
details: Lorem ipsum...
- icon: 🖖
title: Power of Vue meets Markdown
details: Lorem ipsum...
- icon: 🛠️
title: Simple and minimal, always
details: Lorem ipsum...
---
添加的内容是能直接渲染到首页的 Features 下,Footer 上的空白部分的,由于 vitepress 使用的是 markdown-it
解析的 md,所以也能直接写 html 标签
<div style="color: red; font-size: 24px;">这是个有 style 的随便写点</div>
尝试着加点 class 属性,让内容更丰富
<div class="demo">这是个有 style 的随便写点</div>
然后在文档后补添加内存
<style>
.demo {
font-weight: 700;
font-size: 64px;
}
</style>
写完之后可以发现内容变化了。
提示
还可以使用 tailwind css 之类预设样式类,需要安装拓展插件才可以。
Vue SFC 组件
我们可以自己写一个 SFC,注册到工程中,然后在 md 中使用
1、在 theme
目录中创建 components
目录,然后创建 Counter.vue
<script setup>
import { ref } from "vue";
const num = ref(0);
const add = () => {
num.value++;
};
</script>
<template>
<div class="counter">我就是 counter 组件</div>
<button class="btn" @click="add">{{ num }}</button>
</template>
<style>
.counter {
color: blue;
font-size: 24px;
font-weight: 600;
}
.btn {
width: 50px;
height: 50px;
border: 1px solid green;
}
</style>
2、在 .vitepress/theme/nav.js
中注册 Counter.vue
组件
import Theme from "vitepress/theme";
import "./style/var.css";
import Counter from "./components/Counter.vue";
export default {
...Theme,
enhanceApp({ app }) {
app.component("Counter", Counter);
},
};
3、在首页 index.md
使用组件
<Counter></Counter>
可以看到 Counter
组件成功被渲染出来,并且点击按钮能响应式变化数字
所以,对于复杂的交互组件,我们可以通过自定义 SFC,然后在 theme/nav.js
中注册组件,并在 md 中使用组件,达到想要的复杂交互效果
使用首页预留插槽
现在我们已经能做到在 Features 下 Footer 上添加自定义内容了,但是我有办法将自定义内容加到 Header 下 Hero 上吗
答案是可以的,vitepress 首页给我们预留了很多插槽,通过插槽我们可以将自定义组件渲染到想要的位置
使用插槽案例
尝试将一个组件放到 Hero 上方
1、在 components
目录下新建 HeroBefore.vue
<script setup></script>
<template>
<div class="before">HeroBefore</div>
</template>
<style>
.before {
color: green;
font-size: 24px;
font-weight: 700;
text-align: center;
}
</style>
2、安装 vue,因为需要使用 vue 提供的 h 方法
npm add vue -D
3、在 theme/index.ts
中使用插槽
import Theme from "vitepress/theme";
import "./style/var.css";
import FreeStyle from "./components/FreeStyle.vue";
import { h } from "vue";
import HeroBefore from "./components/HeroBefore.vue";
export default {
...Theme,
Layout() {
return h(Theme.Layout, null, {
"home-hero-before": () => h(HeroBefore),
});
},
enhanceApp({ app }) {
app.component("FreeStyle", FreeStyle);
},
};
组件已经渲染到 Header 下 Hero 上方了
查看插槽位置
vitepress 文档并没有详细说明,我们可以通过查阅 vitepress 源码来知道预留的插槽位置,文件在 src/client/theme-default/components/Layout.vue
<template>
<div class="Layout">
<slot name="layout-top" />
<VPSkipLink />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
<VPNav>
<template #nav-bar-title-before>
<slot name="nav-bar-title-before" />
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after" />
</template>
<template #nav-bar-content-before>
<slot name="nav-bar-content-before" />
</template>
<template #nav-bar-content-after>
<slot name="nav-bar-content-after" />
</template>
<template #nav-screen-content-before>
<slot name="nav-screen-content-before" />
</template>
<template #nav-screen-content-after>
<slot name="nav-screen-content-after" />
</template>
</VPNav>
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
<VPSidebar :open="isSidebarOpen" />
<VPContent>
<template #home-hero-before>
<slot name="home-hero-before" />
</template>
<template #home-hero-after>
<slot name="home-hero-after" />
</template>
<template #home-features-before>
<slot name="home-features-before" />
</template>
<template #home-features-after>
<slot name="home-features-after" />
</template>
<template #doc-footer-before>
<slot name="doc-footer-before" />
</template>
<template #doc-before>
<slot name="doc-before" />
</template>
<template #doc-after>
<slot name="doc-after" />
</template>
<template #aside-top>
<slot name="aside-top" />
</template>
<template #aside-bottom>
<slot name="aside-bottom" />
</template>
<template #aside-outline-before>
<slot name="aside-outline-before" />
</template>
<template #aside-outline-after>
<slot name="aside-outline-after" />
</template>
<template #aside-ads-before>
<slot name="aside-ads-before" />
</template>
<template #aside-ads-after>
<slot name="aside-ads-after" />
</template>
</VPContent>
<VPFooter />
<slot name="layout-bottom" />
</div>
</template>
通过插槽名能大概猜到位置在哪,当然也能一个个试知道具体位置,结合这些插槽就能自定义出更个性化的 vitepress 首页了
更改首页标题色调
默认首页展示的标题颜色是绿色,图标背景是白色,通过以下操作,可以获得跟官方官网一样的炫彩配色了
1、在 .vitepress
目录下创建 theme
目录,theme
目录下创建 index.ts
,输入以下内容
import Theme from "vitepress/theme";
export default {
...Theme,
};
2、在 theme
目录下创建 style
目录,style
目录下创建 var.css
:root {
--vp-home-hero-name-color: red;
}
3、在 theme/index.ts
下引入 style/var.css
import Theme from "vitepress/theme";
import "./style/var.css";
export default {
...Theme,
};
可以看到标题颜色已经变成设定的红色了
可以加点渐变色来让整体效果好看点,渐变色可以从 这个网站 获取
:root {
/* 标题 */
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(
135deg,
#f6ceec 10%,
#d939cd 100%
);
/* 图标背景 */
--vp-home-hero-image-background-image: linear-gradient(
135deg,
#f6ceec 10%,
#d939cd 100%
);
--vp-home-hero-image-filter: blur(150px);
}
首页颜色源码解读
我们通过使用 var.css
文件,给根节点 root 添加了 css 变量,来改变首页的标题和图片的背景色,那我们要怎么确认用什么属性就能修改我希望修改的元素呢
方法一:开发者工具
通过控制台我们就能直观的看出希望修改的样式有没有使用 css 变量,以图片背景做例子
在开发者工具中,可以看到
.image-bg {
background-image: var(--vp-home-hero-image-background-image);
filter: var(--vp-home-hero-image-filter);
}
这两个变量就是我们通过 root 下注入的 css 变量,因为我们显式的修改了两个变量,所以系统优先使用我们设定的样式
方案二:看源码
拉取 vitepress 的源码,看到 src/client/theme-default/Layout.vue
,这个 sfc 就是文档的布局组件,三种 layout 模式都是使用的这个组件
<script setup lang="ts">
import { computed, provide, useSlots, watch } from "vue";
import { useRoute } from "vitepress";
import { useData } from "./composables/data.js";
import { useSidebar, useCloseSidebarOnEscape } from "./composables/sidebar.js";
import VPSkipLink from "./components/VPSkipLink.vue";
import VPBackdrop from "./components/VPBackdrop.vue";
import VPNav from "./components/VPNav.vue";
import VPLocalNav from "./components/VPLocalNav.vue";
import VPSidebar from "./components/VPSidebar.vue";
import VPContent from "./components/VPContent.vue";
import VPFooter from "./components/VPFooter.vue";
const {
isOpen: isSidebarOpen,
open: openSidebar,
close: closeSidebar,
} = useSidebar();
const route = useRoute();
watch(() => route.path, closeSidebar);
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar);
provide("close-sidebar", closeSidebar);
provide("is-sidebar-open", isSidebarOpen);
const { frontmatter } = useData();
const slots = useSlots();
const heroImageSlotExists = computed(() => !!slots["home-hero-image"]);
provide("hero-image-slot-exists", heroImageSlotExists);
</script>
<template>
<div v-if="frontmatter.layout !== false" class="Layout">
<slot name="layout-top" />
<VPSkipLink />
<VPBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
<VPNav>
<template #nav-bar-title-before>
<slot name="nav-bar-title-before" />
</template>
<template #nav-bar-title-after>
<slot name="nav-bar-title-after" />
</template>
<template #nav-bar-content-before>
<slot name="nav-bar-content-before" />
</template>
<template #nav-bar-content-after>
<slot name="nav-bar-content-after" />
</template>
<template #nav-screen-content-before>
<slot name="nav-screen-content-before" />
</template>
<template #nav-screen-content-after>
<slot name="nav-screen-content-after" />
</template>
</VPNav>
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
<VPSidebar :open="isSidebarOpen">
<template #sidebar-nav-before>
<slot name="sidebar-nav-before" />
</template>
<template #sidebar-nav-after>
<slot name="sidebar-nav-after" />
</template>
</VPSidebar>
<VPContent>
<template #home-hero-before>
<slot name="home-hero-before" />
</template>
<template #home-hero-image>
<slot name="home-hero-image" />
</template>
<template #home-hero-after>
<slot name="home-hero-after" />
</template>
<template #home-features-before>
<slot name="home-features-before" />
</template>
<template #home-features-after>
<slot name="home-features-after" />
</template>
<template #doc-footer-before>
<slot name="doc-footer-before" />
</template>
<template #doc-before>
<slot name="doc-before" />
</template>
<template #doc-after>
<slot name="doc-after" />
</template>
<template #aside-top>
<slot name="aside-top" />
</template>
<template #aside-bottom>
<slot name="aside-bottom" />
</template>
<template #aside-outline-before>
<slot name="aside-outline-before" />
</template>
<template #aside-outline-after>
<slot name="aside-outline-after" />
</template>
<template #aside-ads-before>
<slot name="aside-ads-before" />
</template>
<template #aside-ads-after>
<slot name="aside-ads-after" />
</template>
</VPContent>
<VPFooter />
<slot name="layout-bottom" />
</div>
<Content v-else />
</template>
<style scoped>
.Layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
</style>
看到 VPContent
组件,这里会通过 frontmatter.layout
来切换使用的布局模式,所以通过这个文件我们可以看到,三种模式对应的组件名为
- doc:VPDoc
- page:VPPage
- home:VPHome
我们主要看 VPHome
组件
拓展,vitepress 使用的读取 md 头部信息所使用的插件是
gray-matter
,感兴趣的可以查阅下使用方法
<script setup lang="ts">
import VPHomeHero from "./VPHomeHero.vue";
import VPHomeFeatures from "./VPHomeFeatures.vue";
</script>
<template>
<div class="VPHome">
<slot name="home-hero-before" />
<VPHomeHero>
<template #home-hero-image>
<slot name="home-hero-image" />
</template>
</VPHomeHero>
<slot name="home-hero-after" />
<slot name="home-features-before" />
<VPHomeFeatures />
<slot name="home-features-after" />
<Content />
</div>
</template>
<style scoped>
.VPHome {
padding-bottom: 96px;
}
.VPHome :deep(.VPHomeSponsors) {
margin-top: 112px;
margin-bottom: -128px;
}
@media (min-width: 768px) {
.VPHome {
padding-bottom: 128px;
}
}
</style>
这里就是首页模式下的布局情况,可以看到组件名就是对应的我们在 index.md
中设置的 hero
和 features
关于首页标题和图标的样式在 VPHomeHero
组件中
做个预告,可以看到下面有个 Content 组件,可以自定义首页下半部分的内容,将会是下篇文章讲的内容,敬请期待~
<script setup lang="ts">
import { useData } from "../composables/data.js";
import VPHero from "./VPHero.vue";
const { frontmatter: fm } = useData();
</script>
<template>
<VPHero
v-if="fm.hero"
class="VPHomeHero"
:name="fm.hero.name"
:text="fm.hero.text"
:tagline="fm.hero.tagline"
:image="fm.hero.image"
:actions="fm.hero.actions"
>
<template #home-hero-image>
<slot name="home-hero-image" />
</template>
</VPHero>
</template>
然后在 VPHero
中的源码中可以看到首页布局的真面目了!我们先看到标题的 css 属性
.name {
color: var(--vp-home-hero-name-color);
}
.clip {
background: var(--vp-home-hero-name-background);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: var(--vp-home-hero-name-color);
}
可以明显看到这里使用的 css 变量,就是我们在var.css
中设定的 css 变量
同理,我们看图片的 css 属性
.image-bg {
position: absolute;
top: 50%;
left: 50%;
border-radius: 50%;
width: 192px;
height: 192px;
background-image: var(--vp-home-hero-image-background-image);
filter: var(--vp-home-hero-image-filter);
transform: translate(-50%, -50%);
}
也是我们在 var.css
中设定的 css 变量
所以,我们可以直接在 VitePress 源码中,找到我们希望更改样式的组件,观察他们的 css 样式是否使用 css 变量,然后我们在 var.css
中进行更改即可
我们根据这个方法,改一下首页的按钮样式
实践,更改首页按钮样式
首页的按钮通过 hero
下的 actions
属性控制,通过 actions.theme
控制样式,默认是 brand
,也就是绿色按钮,总共有三种模式:brand
、alt
、sponsor
VPButton 源码
<script setup lang="ts">
import { computed } from "vue";
import { normalizeLink } from "../support/utils.js";
import { EXTERNAL_URL_RE } from "../../shared.js";
const props = defineProps<{
tag?: string;
size?: "medium" | "big";
theme?: "brand" | "alt" | "sponsor";
text: string;
href?: string;
}>();
const classes = computed(() => [
props.size ?? "medium",
props.theme ?? "brand",
]);
const isExternal = computed(
() => props.href && EXTERNAL_URL_RE.test(props.href),
);
const component = computed(() => {
if (props.tag) {
return props.tag;
}
return props.href ? "a" : "button";
});
</script>
<template>
<component
:is="component"
class="VPButton"
:class="classes"
:href="href ? normalizeLink(href) : undefined"
:target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noreferrer' : undefined"
>
{{ text }}
</component>
</template>
通过分析源码,可以看到 button 的样式控制,通过传入的 theme,计算动态 classes,然后传给组件
.VPButton.brand {
border-color: var(--vp-button-brand-border);
color: var(--vp-button-brand-text);
background-color: var(--vp-button-brand-bg);
}
.VPButton.brand:hover {
border-color: var(--vp-button-brand-hover-border);
color: var(--vp-button-brand-hover-text);
background-color: var(--vp-button-brand-hover-bg);
}
.VPButton.brand:active {
border-color: var(--vp-button-brand-active-border);
color: var(--vp-button-brand-active-text);
background-color: var(--vp-button-brand-active-bg);
}
这里就是 brand 模式下的 button 样式,可以看到使用了三个 css 变量,我们在 var.css
中对着三个样式进行改动
/* var.css */
:root {
/* 标题 */
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: linear-gradient(
135deg,
#f6ceec 10%,
#d939cd 100%
);
/* 图标背景 */
--vp-home-hero-image-background-image: linear-gradient(
135deg,
#f6ceec 10%,
#d939cd 100%
);
--vp-home-hero-image-filter: blur(150px);
/* brand 按钮 */
--vp-button-brand-border: #f6ceec;
--vp-button-brand-text: #f6ceec;
--vp-button-brand-bg: #d939cd;
--vp-button-brand-hover-border: #f6ceec;
--vp-button-brand-hover-text: #fff;
--vp-button-brand-hover-bg: #d939cd;
--vp-button-brand-active-border: #f6ceec;
}