JSPM

  • Created
  • Published
  • Downloads 152
  • Score
    100M100P100Q83065F
  • License MIT

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 渲染能力。

npm version npm downloads bundle size React TypeScript

📖 在线演示

DEMO:🔧 StackBlitz 体验


✨ 核心特性

🤖 AI 对话场景

  • 1:1 复刻 DeepSeek 官网 聊天响应效果
  • 支持思考过程 (thinking) 和回答内容 (answer) 双模式
  • 流式数据完美适配,零延迟响应用户输入

📊 内容展示场景

  • 完整 Markdown 语法支持,包括代码高亮、表格、列表等
  • 数学公式渲染 (KaTeX),支持 $...$\[...\] 语法
  • 支持亮色/暗色主题,适配不同产品风格
  • 插件化架构,支持 remark/rehype 插件扩展

🔧 开发体验

  • 支持打字中断 stop 和继续 resume
  • 支持打字关闭与开启

🎬 流畅动画

  • 双模式定时器优化,支持requestAnimationFramesetTimeout模式
  • 高频打字支持(requestAnimationFrame模式下打字间隔最低可接近于0ms
  • 帧同步渲染,与浏览器刷新完美配合
  • 智能字符批量处理,视觉效果更自然

📦 快速安装

# npm
npm install ds-markdown

# yarn
yarn add ds-markdown

# pnpm
pnpm add ds-markdown

通过 ESM CDN 使用

无需安装,直接在浏览器中使用:

DEMO

<!-- 导入样式, 必须 -->
<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分钟上手

基础用法

DEMO

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

注意: 如果当在打字中 disableTypingtrue 变为 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(); // 恢复动画

🧮 数学公式使用指南

DEMO1:勾股定理

DEMO2:题目解答

基本语法

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 流式对话

DEMO: 🔧 StackBlitz 体验

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 类型提示