自定义主题
Rspress 提供强大的主题定制功能,允许您创建符合品牌形象的独特文档站点。本章探讨如何自定义主题样式、创建自定义主题以及调整布局。
主题系统概述
默认主题
Rspress 附带一个功能完整的默认主题:
- 响应式布局
- 深色/浅色模式
- 可自定义的配色方案
- 灵活的组件系统
- 可访问性功能
自定义选项
主题可以通过以下方式自定义:
- CSS 变量: 修改颜色、字体和间距
- 主题配置: 在配置文件中设置选项
- 自定义组件: 覆盖默认组件
- 完全自定义主题: 从头创建新主题
使用 CSS 变量
基本自定义
在自定义 CSS 文件中覆盖 CSS 变量:
css
/* styles/custom.css */
:root {
/* 主色 */
--rp-c-brand: #667eea;
--rp-c-brand-dark: #5568d3;
--rp-c-brand-light: #8b9dff;
/* 背景色 */
--rp-c-bg: #ffffff;
--rp-c-bg-soft: #f9f9f9;
--rp-c-bg-mute: #f1f1f1;
/* 文本色 */
--rp-c-text-1: #213547;
--rp-c-text-2: #476582;
--rp-c-text-3: #7d8590;
/* 边框色 */
--rp-c-divider: #e2e8f0;
--rp-c-border: #c9d3df;
}在 rspress.config.ts 中导入:
typescript
import { defineConfig } from 'rspress/config';
export default defineConfig({
// ... 其他配置
builderConfig: {
html: {
tags: [
{
tag: 'link',
attrs: {
rel: 'stylesheet',
href: '/styles/custom.css'
}
}
]
}
}
});深色模式定制
自定义深色模式颜色:
css
/* 深色模式变量 */
.dark {
/* 主色(深色模式) */
--rp-c-brand: #8b9dff;
--rp-c-brand-dark: #667eea;
--rp-c-brand-light: #a0aeff;
/* 背景色(深色模式) */
--rp-c-bg: #1a1a1a;
--rp-c-bg-soft: #252525;
--rp-c-bg-mute: #2f2f2f;
/* 文本色(深色模式) */
--rp-c-text-1: #f0f0f0;
--rp-c-text-2: #c9d1d9;
--rp-c-text-3: #8b949e;
/* 边框色(深色模式) */
--rp-c-divider: #30363d;
--rp-c-border: #3d444d;
}排版定制
自定义字体和间距:
css
:root {
/* 字体 */
--rp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--rp-font-family-mono: 'Fira Code', 'Courier New', monospace;
/* 字号 */
--rp-font-size-root: 16px;
--rp-font-size-xs: 12px;
--rp-font-size-sm: 14px;
--rp-font-size-md: 16px;
--rp-font-size-lg: 18px;
--rp-font-size-xl: 20px;
/* 行高 */
--rp-line-height-tight: 1.25;
--rp-line-height-base: 1.5;
--rp-line-height-relaxed: 1.75;
/* 间距 */
--rp-space-1: 4px;
--rp-space-2: 8px;
--rp-space-3: 12px;
--rp-space-4: 16px;
--rp-space-5: 20px;
--rp-space-6: 24px;
--rp-space-8: 32px;
}主题配置
配置导航栏
自定义导航栏外观:
typescript
import { defineConfig } from 'rspress/config';
export default defineConfig({
title: '我的文档',
themeConfig: {
// 导航栏配置
nav: [
{ text: '首页', link: '/' },
{ text: '指南', link: '/guide/' },
{ text: '关于', link: '/about' }
],
// Logo
logo: '/logo.png',
logoText: '我的项目',
// 社交链接
socialLinks: [
{
icon: 'github',
link: 'https://github.com/username/project'
},
{
icon: 'twitter',
link: 'https://twitter.com/username'
},
{
icon: 'discord',
link: 'https://discord.gg/invite'
}
],
// 导航栏外观
appearance: true, // 启用深色模式切换
}
});配置侧边栏
自定义侧边栏样式:
typescript
export default defineConfig({
themeConfig: {
sidebar: {
'/guide/': [
{
text: '入门',
items: [
{ text: '介绍', link: '/guide/introduction' },
{ text: '快速开始', link: '/guide/quick-start' }
]
}
]
},
// 侧边栏设置
outlineTitle: '本页目录',
outline: [2, 3], // 显示 h2 和 h3 标题
}
});配置页脚
添加自定义页脚:
typescript
export default defineConfig({
themeConfig: {
footer: {
message: '使用 MIT 许可证发布',
copyright: 'Copyright © 2024 我的项目'
},
// 上一页/下一页链接
docFooter: {
prev: '上一页',
next: '下一页'
},
// 编辑链接
editLink: {
pattern: 'https://github.com/username/project/edit/main/docs/:path',
text: '在 GitHub 上编辑此页'
},
// 最后更新时间
lastUpdatedText: '最后更新'
}
});组件自定义
覆盖默认组件
创建自定义组件以覆盖默认组件:
tsx
// theme/components/CustomNavbar.tsx
import React from 'react';
export function CustomNavbar() {
return (
<nav className="custom-navbar">
<div className="navbar-brand">
<img src="/logo.png" alt="Logo" />
<span>我的项目</span>
</div>
<div className="navbar-links">
<a href="/guide">指南</a>
<a href="/api">API</a>
<a href="/blog">博客</a>
</div>
<div className="navbar-actions">
<button className="search-button">搜索</button>
<button className="theme-toggle">🌙</button>
</div>
</nav>
);
}css
/* theme/components/CustomNavbar.css */
.custom-navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: var(--rp-c-bg);
border-bottom: 1px solid var(--rp-c-divider);
}
.navbar-brand {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.25rem;
font-weight: bold;
}
.navbar-brand img {
width: 32px;
height: 32px;
}
.navbar-links {
display: flex;
gap: 2rem;
}
.navbar-links a {
color: var(--rp-c-text-1);
text-decoration: none;
font-weight: 500;
transition: color 0.3s;
}
.navbar-links a:hover {
color: var(--rp-c-brand);
}
.navbar-actions {
display: flex;
gap: 1rem;
}
.search-button,
.theme-toggle {
padding: 0.5rem 1rem;
border: 1px solid var(--rp-c-border);
background: var(--rp-c-bg);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
}
.search-button:hover,
.theme-toggle:hover {
background: var(--rp-c-bg-soft);
border-color: var(--rp-c-brand);
}自定义首页
创建自定义首页组件:
tsx
// theme/components/CustomHome.tsx
import React from 'react';
import './CustomHome.css';
export function CustomHome() {
return (
<div className="custom-home">
{/* 主要部分 */}
<section className="hero">
<h1 className="hero-title">
<span className="gradient-text">下一代</span>
<br />
文档框架
</h1>
<p className="hero-subtitle">
快速、现代且强大的文档解决方案
</p>
<div className="hero-actions">
<a href="/guide/quick-start" className="btn btn-primary">
开始使用
</a>
<a href="/guide/introduction" className="btn btn-secondary">
了解更多
</a>
</div>
</section>
{/* 功能部分 */}
<section className="features">
<div className="container">
<div className="feature-grid">
<div className="feature-card">
<div className="feature-icon">⚡</div>
<h3>闪电般快速</h3>
<p>使用 Vite 构建,实现极快的开发和构建时间</p>
</div>
<div className="feature-card">
<div className="feature-icon">🎨</div>
<h3>完全可定制</h3>
<p>完全控制主题和样式</p>
</div>
<div className="feature-card">
<div className="feature-icon">📱</div>
<h3>响应式</h3>
<p>在所有设备上完美运行</p>
</div>
<div className="feature-card">
<div className="feature-icon">🔍</div>
<h3>强大搜索</h3>
<p>内置全文搜索功能</p>
</div>
<div className="feature-card">
<div className="feature-icon">🌍</div>
<h3>国际化</h3>
<p>内置多语言支持</p>
</div>
<div className="feature-card">
<div className="feature-icon">⚙️</div>
<h3>可扩展</h3>
<p>通过插件和主题扩展功能</p>
</div>
</div>
</div>
</section>
</div>
);
}css
/* theme/components/CustomHome.css */
.custom-home {
width: 100%;
}
.hero {
text-align: center;
padding: 120px 20px;
background: linear-gradient(135deg,
rgba(102, 126, 234, 0.1) 0%,
rgba(118, 75, 162, 0.1) 100%);
}
.hero-title {
font-size: 4rem;
font-weight: 800;
margin-bottom: 1rem;
line-height: 1.2;
}
.gradient-text {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 1.5rem;
color: var(--rp-c-text-2);
margin-bottom: 2rem;
}
.hero-actions {
display: flex;
gap: 1rem;
justify-content: center;
}
.btn {
padding: 12px 32px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
font-size: 1rem;
transition: all 0.3s;
display: inline-block;
}
.btn-primary {
background: var(--rp-c-brand);
color: white;
border: 2px solid var(--rp-c-brand);
}
.btn-primary:hover {
background: var(--rp-c-brand-dark);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: transparent;
color: var(--rp-c-brand);
border: 2px solid var(--rp-c-brand);
}
.btn-secondary:hover {
background: var(--rp-c-brand);
color: white;
}
.features {
padding: 80px 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.feature-card {
padding: 30px;
border: 1px solid var(--rp-c-divider);
border-radius: 12px;
transition: all 0.3s;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
border-color: var(--rp-c-brand);
}
.feature-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: 0.5rem;
color: var(--rp-c-text-1);
}
.feature-card p {
color: var(--rp-c-text-2);
line-height: 1.6;
}
@media (max-width: 768px) {
.hero-title {
font-size: 2.5rem;
}
.hero-subtitle {
font-size: 1.25rem;
}
.hero-actions {
flex-direction: column;
align-items: center;
}
.btn {
width: 100%;
max-width: 300px;
}
.feature-grid {
grid-template-columns: 1fr;
}
}创建自定义主题
主题结构
创建完整的自定义主题:
theme/
├── index.ts # 主题入口
├── components/
│ ├── Layout.tsx # 主布局
│ ├── Navbar.tsx # 导航栏
│ ├── Sidebar.tsx # 侧边栏
│ ├── Content.tsx # 内容区域
│ └── Footer.tsx # 页脚
├── styles/
│ ├── vars.css # CSS 变量
│ ├── base.css # 基础样式
│ └── components.css # 组件样式
└── hooks/
├── useTheme.ts # 主题钩子
└── useNav.ts # 导航钩子主题入口
typescript
// theme/index.ts
import { Theme } from 'rspress/theme';
import { Layout } from './components/Layout';
import './styles/vars.css';
import './styles/base.css';
import './styles/components.css';
const theme: Theme = {
Layout,
// 注册其他组件...
};
export default theme;
export * from './components';
export * from './hooks';自定义布局组件
tsx
// theme/components/Layout.tsx
import React from 'react';
import { usePageData } from 'rspress/runtime';
import { Navbar } from './Navbar';
import { Sidebar } from './Sidebar';
import { Content } from './Content';
import { Footer } from './Footer';
export function Layout() {
const pageData = usePageData();
const { frontmatter } = pageData;
// 检查是否为自定义页面
if (frontmatter.pageType === 'custom') {
return <>{pageData.content}</>;
}
return (
<div className="theme-layout">
<Navbar />
<div className="layout-container">
{frontmatter.sidebar !== false && <Sidebar />}
<Content />
</div>
<Footer />
</div>
);
}主题钩子
typescript
// theme/hooks/useTheme.ts
import { useState, useEffect } from 'react';
export function useTheme() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
useEffect(() => {
// 从 localStorage 读取主题
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark';
if (savedTheme) {
setTheme(savedTheme);
document.documentElement.classList.toggle('dark', savedTheme === 'dark');
}
}, []);
const toggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
document.documentElement.classList.toggle('dark', newTheme === 'dark');
};
return { theme, toggleTheme };
}布局自定义
响应式布局
创建响应式布局系统:
css
/* theme/styles/layout.css */
.theme-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.layout-container {
display: flex;
flex: 1;
max-width: 1400px;
margin: 0 auto;
width: 100%;
padding: 0 24px;
}
/* 侧边栏 */
.sidebar {
width: 280px;
flex-shrink: 0;
padding: 24px 0;
position: sticky;
top: 64px;
height: calc(100vh - 64px);
overflow-y: auto;
}
/* 内容 */
.content {
flex: 1;
min-width: 0;
padding: 24px 48px;
}
/* 目录 */
.outline {
width: 240px;
flex-shrink: 0;
padding: 24px 0;
position: sticky;
top: 64px;
height: calc(100vh - 64px);
overflow-y: auto;
}
/* 响应式 */
@media (max-width: 1200px) {
.outline {
display: none;
}
}
@media (max-width: 960px) {
.sidebar {
position: fixed;
left: 0;
top: 64px;
transform: translateX(-100%);
transition: transform 0.3s;
background: var(--rp-c-bg);
z-index: 100;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}
.sidebar.open {
transform: translateX(0);
}
.layout-container {
padding: 0 16px;
}
.content {
padding: 24px 0;
}
}网格系统
实现灵活的网格系统:
css
/* theme/styles/grid.css */
.grid {
display: grid;
gap: var(--grid-gap, 24px);
}
.grid-cols-1 { grid-template-columns: repeat(1, 1fr); }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr); }
/* 自动填充 */
.grid-auto-fit {
grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 300px), 1fr));
}
.grid-auto-fill {
grid-template-columns: repeat(auto-fill, minmax(var(--grid-min, 300px), 1fr));
}
/* 响应式 */
@media (max-width: 768px) {
.grid-cols-2,
.grid-cols-3,
.grid-cols-4 {
grid-template-columns: 1fr;
}
}高级主题功能
动画和过渡
添加流畅的动画:
css
/* theme/styles/animations.css */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
transform: translateX(-20px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.fade-in {
animation: fadeIn 0.3s ease-out;
}
.slide-in {
animation: slideIn 0.3s ease-out;
}
/* 页面过渡 */
.page-transition-enter {
opacity: 0;
transform: translateY(20px);
}
.page-transition-enter-active {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s, transform 0.3s;
}
.page-transition-exit {
opacity: 1;
}
.page-transition-exit-active {
opacity: 0;
transition: opacity 0.2s;
}自定义滚动条
样式化滚动条:
css
/* theme/styles/scrollbar.css */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--rp-c-bg);
}
::-webkit-scrollbar-thumb {
background: var(--rp-c-border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--rp-c-brand);
}
/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: var(--rp-c-border) var(--rp-c-bg);
}打印样式
优化打印体验:
css
/* theme/styles/print.css */
@media print {
/* 隐藏导航元素 */
.navbar,
.sidebar,
.outline,
.footer,
.edit-link {
display: none !important;
}
/* 全宽内容 */
.content {
max-width: 100%;
padding: 0;
}
/* 优化排版 */
body {
font-size: 12pt;
line-height: 1.5;
color: #000;
background: #fff;
}
/* 保持链接可见 */
a[href]:after {
content: " (" attr(href) ")";
font-size: 10pt;
color: #666;
}
/* 分页控制 */
h1, h2, h3 {
page-break-after: avoid;
}
pre, blockquote {
page-break-inside: avoid;
}
}最佳实践
性能优化
- CSS 优化
css
/* 使用 CSS 自定义属性 */
:root {
--primary-color: #667eea;
}
/* 避免复杂选择器 */
.simple-class { } /* 好 */
div > ul > li > a { } /* 避免 */
/* 使用 transform 而不是 position */
.animated {
transform: translateX(10px); /* 好 */
/* left: 10px; */ /* 避免 */
}- 代码分割
typescript
// theme/index.ts
import { lazy } from 'react';
const HeavyComponent = lazy(() => import('./components/HeavyComponent'));可访问性
确保主题可访问:
css
/* 焦点样式 */
:focus-visible {
outline: 2px solid var(--rp-c-brand);
outline-offset: 2px;
}
/* 跳过链接 */
.skip-to-content {
position: absolute;
top: -40px;
left: 0;
background: var(--rp-c-brand);
color: white;
padding: 8px 16px;
text-decoration: none;
z-index: 100;
}
.skip-to-content:focus {
top: 0;
}主题测试
测试主题组件:
typescript
// theme/__tests__/Layout.test.tsx
import { render, screen } from '@testing-library/react';
import { Layout } from '../components/Layout';
describe('Layout', () => {
it('renders navbar', () => {
render(<Layout />);
expect(screen.getByRole('navigation')).toBeInTheDocument();
});
it('renders content area', () => {
render(<Layout />);
expect(screen.getByRole('main')).toBeInTheDocument();
});
});故障排除
常见问题
- 样式未应用
typescript
// 确保在配置中导入 CSS
export default defineConfig({
builderConfig: {
html: {
tags: [
{
tag: 'link',
attrs: {
rel: 'stylesheet',
href: '/styles/custom.css'
}
}
]
}
}
});- 深色模式不工作
css
/* 确保使用正确的类名 */
.dark {
/* 深色模式样式 */
}
/* 或使用数据属性 */
[data-theme="dark"] {
/* 深色模式样式 */
}- 组件未覆盖
typescript
// 确保导出组件
export { CustomNavbar as Navbar } from './components/CustomNavbar';下一步
最佳实践
- 使用 CSS 变量以提高可维护性
- 确保深色模式和浅色模式都经过测试
- 优化移动设备体验
- 遵循可访问性指南
- 记录自定义主题组件
性能
- 避免过度的 CSS 选择器
- 优化图片和资源
- 使用代码分割处理大型组件
- 最小化重绘和重排