Package Exports
- ds-markdown
- ds-markdown/katex.css
- ds-markdown/plugins
- ds-markdown/style.css
Readme
ds-markdown
🚀 高性能 React Markdown 打字动画组件,完美复刻 DeepSeek 聊天界面效果
🇨🇳 中文 | 🇺🇸 English | 🇯🇵 日本語 | 🇰🇷 한국어
一个专为现代 AI 应用设计的 React 组件,提供流畅的实时打字动画和完整的 Markdown 渲染能力。
✨ 核心特性
🤖 AI 对话场景
- 1:1 复刻 DeepSeek 官网 聊天响应效果
- 支持思考过程 (
thinking) 和回答内容 (answer) 双模式 - 流式数据完美适配,零延迟响应用户输入
📊 内容展示场景
- 完整 Markdown 语法支持,包括代码高亮、表格、列表等
- 数学公式渲染 (KaTeX),支持
$...$和\[...\]语法 - 支持亮色/暗色主题,适配不同产品风格
- 插件化架构,支持 remark/rehype 插件扩展
🔧 开发体验
- 支持打字中断
stop和继续resume - 支持打字关闭与开启
🎬 流畅动画
- 双模式定时器优化,支持
requestAnimationFrame和setTimeout模式 - 高频打字支持(
requestAnimationFrame模式下打字间隔最低可接近于0ms) - 帧同步渲染,与浏览器刷新完美配合
- 智能字符批量处理,视觉效果更自然
📦 快速安装
# npm
npm install ds-markdown
# yarn
yarn add ds-markdown
# pnpm
pnpm add ds-markdown通过 ESM CDN 使用
无需安装,直接在浏览器中使用:
<!-- 导入样式, 必须 -->
<link rel="stylesheet" href="https://esm.sh/ds-markdown/dist/style.css" />
<!-- 导入katex数学公式样式, 非不要不引入 -->
<link rel="stylesheet" href="https://esm.sh/ds-markdown/dist/katex.css" />
<!-- 导入组件 -->
<script type="module">
import Markdown from 'https://esm.sh/ds-markdown';
</script>🚀 5分钟上手
基础用法
import DsMarkdown from 'ds-markdown';
import 'ds-markdown/style.css';
function App() {
return (
<DsMarkdown interval={20} answerType="answer">
# Hello ds-markdown 这是一个**高性能**的打字动画组件! ## 特性 - ⚡ 零延迟流式处理 - 🎬 流畅打字动画 - 🎯 完美语法支持
</DsMarkdown>
);
}禁用打字动画
import DsMarkdown from 'ds-markdown';
import 'ds-markdown/style.css';
function StaticDemo() {
const [disableTyping, setDisableTyping] = useState(false);
return (
<div>
<button onClick={() => setDisableTyping(!disableTyping)}>{disableTyping ? '开启' : '关闭'}打字机效果</button>
<DsMarkdown interval={20} answerType="answer" disableTyping={disableTyping}>
# 静态展示模式 当 `disableTyping` 为 `true` 时,内容会立即全部显示,无打字动画效果。 这在某些场景下非常有用: - 📄 静态文档展示 - 🔄 切换显示模式 - ⚡ 快速预览内容
</DsMarkdown>
</div>
);
}数学公式支持
import DsMarkdown from 'ds-markdown';
// 如果需要展示公式,则需要引入公式转换插件
import { katexPlugin } from 'ds-markdown/plugins';
import 'ds-markdown/style.css';
// 如果需要展示公式,则需要引入数学公式样式
import 'ds-markdown/katex.css';
function MathDemo() {
return (
<DsMarkdown interval={20} answerType="answer" plugins={[katexPlugin]} math={{ splitSymbol: 'dollar' }}>
# 勾股定理 在直角三角形中,斜边的平方等于两条直角边的平方和: $a^2 + b^2 = c^2$ 其中: - $a$ 和 $b$ 是直角边 - $c$ 是斜边 对于经典的"勾三股四弦五": $c = \sqrt{3 ^ (2 + 4) ^ 2} = \sqrt{25} = 5$
</DsMarkdown>
);
}AI 对话场景
function ChatDemo() {
const [thinking, setThinking] = useState('');
const [answer, setAnswer] = useState('');
const handleAsk = () => {
setThinking('🤔 正在思考您的问题...');
setTimeout(() => {
setAnswer(`# 关于 React 19
React 19 带来了许多激动人心的新特性:
## 🚀 主要更新
1. **React Compiler** - 自动优化性能
2. **Actions** - 简化表单处理
3. **Document Metadata** - 内置 SEO 支持
让我们一起探索这些新功能!`);
}, 2000);
};
return (
<div>
<button onClick={handleAsk}>询问 AI</button>
{thinking && (
<DsMarkdown answerType="thinking" interval={30}>
{thinking}
</DsMarkdown>
)}
{answer && (
<DsMarkdown answerType="answer" interval={15}>
{answer}
</DsMarkdown>
)}
</div>
);
}📚 完整 API 文档
默认导出 DsMarkdown 和 MarkdownCMD 的 props
import DsMarkdown, { MarkdownCMD } from 'ds-markdown';| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
interval |
number |
打字间隔 (毫秒) | 30 |
timerType |
'setTimeout' | 'requestAnimationFrame' |
定时器类型 | 当前默认值是setTimeout,后期会改为requestAnimationFrame |
answerType |
'thinking' | 'answer' |
内容类型 (影响样式主题) | 'answer' |
theme |
'light' | 'dark' |
主题类型 | 'light' |
plugins |
IMarkdownPlugin[] |
插件配置 | [] |
math |
IMarkdownMath | 数学公式配置 | { splitSymbol: 'dollar' } |
onEnd |
(data: EndData) => void |
打字结束回调 | - |
onStart |
(data: StartData) => void |
打字开始回调 | - |
onTypedChar |
(data: ITypedChar) => void |
每字符打字回调 | - |
disableTyping |
boolean |
禁用打字动画效果 | false |
注意: 如果当在打字中
disableTyping从true变为false,则在下一个打字触发时,会把剩下的所有字一次性显示
ITypedChar
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
percent |
number |
打字进度百分比 | 0 |
currentChar |
string |
当前打字的字符 | - |
currentIndex |
number |
当前打字在整个字符串中的索引 | 0 |
IMarkdownMath
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
splitSymbol |
'dollar' | 'bracket' |
数学公式分隔符类型 | 'dollar' |
分隔符说明:
'dollar':使用$...$和$$...$$语法'bracket':使用\(...\)和\[...\]语法
IMarkdownPlugin
| 属性 | 类型 | 说明 | 默认值 |
|---|---|---|---|
remarkPlugin |
unknown |
remark 插件 | - |
rehypePlugin |
unknown |
rehype 插件 | - |
type |
'buildIn' | 'custom' |
插件类型 | - |
id |
any |
插件唯一标识 | - |
组件暴露的方法
默认导出 DsMarkdown
| 方法 | 参数 | 说明 |
|---|---|---|
stop |
- | 暂停打字动画 |
resume |
- | 恢复打字动画 |
MarkdownCMD 暴露的方法
| 方法 | 参数 | 说明 |
|---|---|---|
push |
(content: string, answerType: AnswerType) |
添加内容并开始打字 |
clear |
- | 清空所有内容和状态 |
triggerWholeEnd |
- | 手动触发完成回调 |
stop |
- | 暂停打字动画 |
resume |
- | 恢复打字动画 |
用法示例:
markdownRef.current?.stop(); // 暂停动画
markdownRef.current?.resume(); // 恢复动画🧮 数学公式使用指南
基本语法
import { katexPlugin } from 'ds-markdown/plugins';
// 1. 启用数学公式支持
<DsMarkdown plugins={[katexPlugin]}>
# 数学公式示例
// 行内公式
这是一个行内公式:$E = mc^2$
// 块级公式
$$\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}$$
</DsMarkdown>分隔符选择
// 使用美元符号分隔符(默认)
<DsMarkdown
plugins={[katexPlugin]}
math={{ splitSymbol: 'dollar' }}
>
行内:$a + b = c$
块级:$$\sum_{i=1}^{n} x_i = x_1 + x_2 + \cdots + x_n$$
</DsMarkdown>
// 使用括号分隔符
<DsMarkdown
plugins={[katexPlugin]}
math={{ splitSymbol: 'bracket' }}
>
行内:\(a + b = c\)
块级:\[\sum_{i=1}^{n} x_i = x_1 + x_2 + \cdots + x_n\]
</DsMarkdown>流式数学公式
// 完美支持流式输出中的数学公式
const mathContent = [
'勾股定理:',
'$a^2 + b^2 = c^2$',
'\n\n',
'其中:',
'- $a$ 和 $b$ 是直角边\n',
'- $c$ 是斜边\n\n',
'对于经典的"勾三股四弦五":\n',
'$c = \\sqrt{3^2 + 4^2} = \\sqrt{25} = 5$\n\n',
'这个定理在几何学中有着广泛的应用!',
];
mathContent.forEach((chunk) => {
markdownRef.current?.push(chunk, 'answer');
});样式定制
/* 数学公式样式定制 */
.katex {
font-size: 1.1em;
}
.katex-display {
margin: 1em 0;
text-align: center;
}
/* 暗色主题适配 */
[data-theme='dark'] .katex {
color: #e1e1e1;
}🔌 插件系统
内置插件
KaTeX 数学公式插件
import { katexPlugin } from 'ds-markdown/plugins';
// 启用数学公式支持
<DsMarkdown plugins={[katexPlugin]}>数学公式:$E = mc^2$</DsMarkdown>;自定义插件
import { createBuildInPlugin } from 'ds-markdown/plugins';
// 创建自定义插件
const customPlugin = createBuildInPlugin({
remarkPlugin: yourRemarkPlugin,
rehypePlugin: yourRehypePlugin,
id: Symbol('custom-plugin'),
});
// 使用自定义插件
<DsMarkdown plugins={[katexPlugin, customPlugin]}>内容</DsMarkdown>;🎛️ 定时器模式详解
requestAnimationFrame 模式 🌟 (推荐)
// 🎯 特性
- 时间驱动:基于真实经过时间计算字符数量
- 批量处理:单帧内可处理多个字符
- 帧同步:与浏览器 60fps 刷新率同步
- 高频优化:完美支持 interval < 16ms 的高速打字
// 🎯 适用场景
- 现代 Web 应用的默认选择
- 追求流畅动画效果
- 高频打字 (interval > 0 即可)
- AI 实时对话场景setTimeout 模式 📟 (兼容)
// 🎯 特性
- 单字符:每次精确处理一个字符
- 固定间隔:严格按设定时间执行
- 节拍感:经典打字机的节奏感
- 精确控制:适合特定时序要求
// 🎯 适用场景
- 需要精确时间控制
- 营造复古打字机效果
- 兼容性要求较高的场景📊 性能对比
| 特性 | requestAnimationFrame | setTimeout |
|---|---|---|
| 字符处理 | 每帧可处理多个字符 | 每次处理一个字符 |
| 高频间隔 | ✅ 优秀 (5ms → 每帧3字符) | ❌ 可能卡顿 |
| 低频间隔 | ✅ 正常 (100ms → 6帧后1字符) | ✅ 精确 |
| 视觉效果 | 🎬 流畅动画感 | ⚡ 精确节拍感 |
| 性能开销 | 🟢 低 (帧同步) | 🟡 中等 (定时器) |
高频推荐requestAnimationFrame,低频推荐 setTimeout
💡 实战示例
📝 AI 流式对话
import { useRef } from 'react';
import { MarkdownCMD, MarkdownCMDRef } from 'ds-markdown';
function StreamingChat() {
const markdownRef = useRef<MarkdownCMDRef>(null);
// 模拟 AI 流式响应
const simulateAIResponse = async () => {
markdownRef.current?.clear();
// 思考阶段
markdownRef.current?.push('🤔 正在分析您的问题...', 'thinking');
await delay(1000);
markdownRef.current?.push('\n\n✅ 分析完成,开始回答', 'thinking');
// 流式回答
const chunks = [
'# React 19 新特性解析\n\n',
'## 🚀 React Compiler\n',
'React 19 最大的亮点是引入了 **React Compiler**:\n\n',
'- 🎯 **自动优化**:无需手动 memo 和 useMemo\n',
'- ⚡ **性能提升**:编译时优化,运行时零开销\n',
'- 🔧 **向后兼容**:现有代码无需修改\n\n',
'## 📝 Actions 简化表单\n',
'新的 Actions API 让表单处理变得更简单:\n\n',
'```tsx\n',
'function ContactForm({ action }) {\n',
' const [state, formAction] = useActionState(action, null);\n',
' return (\n',
' <form action={formAction}>\n',
' <input name="email" type="email" />\n',
' <button>提交</button>\n',
' </form>\n',
' );\n',
'}\n',
'```\n\n',
'希望这个解答对您有帮助!🎉',
];
for (const chunk of chunks) {
await delay(100);
markdownRef.current?.push(chunk, 'answer');
}
};
return (
<div className="chat-container">
<button onClick={simulateAIResponse}>🤖 询问 React 19 新特性</button>
<MarkdownCMD ref={markdownRef} interval={10} timerType="requestAnimationFrame" onEnd={(data) => console.log('段落完成:', data)} />
</div>
);
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));🧮 数学公式流式渲染
import { katexPlugin } from 'ds-markdown/plugins';
function MathStreamingDemo() {
const markdownRef = useRef<MarkdownCMDRef>(null);
const simulateMathResponse = async () => {
markdownRef.current?.clear();
const mathChunks = [
'# 勾股定理详解\n\n',
'在直角三角形中,斜边的平方等于两条直角边的平方和:\n\n',
'$a^2 + b^2 = c^2$\n\n',
'其中:\n',
'- $a$ 和 $b$ 是直角边\n',
'- $c$ 是斜边\n\n',
'对于经典的"勾三股四弦五":\n',
'$c = \\sqrt{3^2 + 4^2} = \\sqrt{25} = 5$\n\n',
'这个定理在几何学中有着广泛的应用!',
];
for (const chunk of mathChunks) {
await delay(150);
markdownRef.current?.push(chunk, 'answer');
}
};
return (
<div>
<button onClick={simulateMathResponse}>📐 讲解勾股定理</button>
<MarkdownCMD ref={markdownRef} interval={20} timerType="requestAnimationFrame" plugins={[katexPlugin]} math={{ splitSymbol: 'dollar' }} />
</div>
);
}🔧 最佳实践
1. 性能优化
// ✅ 推荐配置
<DsMarkdown
timerType="requestAnimationFrame"
interval={15} // 15-30ms 为最佳体验
/>
// ❌ 避免过小间隔
<DsMarkdown interval={1} /> // 可能导致性能问题2. 流式数据处理
// ✅ 推荐:命令式 API
const ref = useRef<MarkdownCMDRef>(null);
useEffect(() => {
ref.current?.push(newChunk, 'answer');
}, [newChunk]);
// ❌ 避免:频繁更新 children
const [content, setContent] = useState('');
// 每次更新都会重新解析整个内容3. 数学公式优化
// ✅ 推荐:按需加载数学公式样式
import 'ds-markdown/style.css';
import 'ds-markdown/katex.css'; // 仅在需要时引入
// ✅ 推荐:合理使用分隔符
// 对于简单公式,使用 $...$ 更简洁
// 对于复杂公式,使用 $$...$$ 更清晰
// ✅ 推荐:插件化配置
import { katexPlugin } from 'ds-markdown/plugins';
<DsMarkdown plugins={[katexPlugin]}>数学公式内容</DsMarkdown>;4. 类型安全
import { MarkdownCMDRef } from 'ds-markdown';
const ref = useRef<MarkdownCMDRef>(null);
// 完整的 TypeScript 类型提示