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>
写完之后可以发现内容变化了。
TIP
还可以使用 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/index.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/index.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;
}