纯 JavaScript

你是在没有使用 React 或 Vue 之类前端框架的情况下开发吗?没问题,你可以直接在原生 JavaScript 中使用 Tiptap。

提示

这里的“原生 JavaScript”指的是不使用任何前端框架,但你仍然在使用带有 ES module 导入现代 JavaScript(例如通过 Vite、Rollup 或 Webpack)。

如果你想完全跳过构建工具,可以查看下面的 CDN 部分,了解一个可直接在浏览器中运行的配置。

提示

如果你没有使用像 Webpack 或 Rollup 这样的打包工具,请改为按照CDN指南操作。由于 Tiptap 采用模块化构建,你需要在 HTML 中使用 <script type="module"> 来使我们的 CDN 导入生效。

使用构建工具(Vite、Webpack、Rollup)

如果你已经在使用打包工具,或者只是想使用标准的 npm 工作流,推荐采用这种方式。

安装依赖

开始使用时,你需要以下 Tiptap 核心包:

  • @tiptap/core – 主要的编辑器 API
  • @tiptap/pm – ProseMirror,Tiptap 背后的引擎
  • @tiptap/starter-kit – 常用扩展的便捷集合包
npm install @tiptap/core @tiptap/pm @tiptap/starter-kit

添加标记

在你希望挂载编辑器的位置添加以下 HTML:

<div class="element"></div>

初始化编辑器

一切准备就绪,现在开始设置编辑器。将以下代码添加到你的 JavaScript 文件中:

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

new Editor({
  element: document.querySelector('.element'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

在浏览器中打开你的项目,看看 Tiptap 的效果吧。干得漂亮!


不使用构建工具(CDN)

如果你想完全跳过 npm 和构建工具,可以使用 ES module 导入,直接从 CDN 加载 Tiptap。这在所有现代浏览器中都可用。

注意

你的 <script> 标签必须包含 type="module",导入语法才能在浏览器中生效。

最小示例

<script type="module">
  import { Editor } from 'https://esm.sh/@tiptap/core'
  import StarterKit from 'https://esm.sh/@tiptap/starter-kit'

  new Editor({
    element: document.querySelector('.element'),
    extensions: [StarterKit],
    content: '<p>Hello from CDN!</p>',
  })
</script>

<div class="element"></div>

带工具栏的完整 HTML 文件

下面是一个可直接复制粘贴的示例,其中包含带格式化按钮的工具栏:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tiptap 编辑器</title>
    <style>
      body {
        margin: 2rem;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      }
      .editor-container {
        max-width: 48rem;
        margin: 0 auto;
      }
      .toolbar {
        display: flex;
        gap: 0.25rem;
        padding: 0.5rem;
        border: 1px solid #d1d5db;
        border-bottom: none;
        border-radius: 0.375rem 0.375rem 0 0;
        background: #f9fafb;
        flex-wrap: wrap;
      }
      .toolbar button {
        padding: 0.25rem 0.5rem;
        font-size: 0.875rem;
        border: 1px solid transparent;
        border-radius: 0.25rem;
        background: transparent;
        cursor: pointer;
      }
      .toolbar button:hover {
        background: #e5e7eb;
      }
      .toolbar button.is-active {
        background: #111827;
        color: #fff;
      }
      .tiptap {
        padding: 0.75rem 1rem;
        border: 1px solid #d1d5db;
        border-radius: 0 0 0.375rem 0.375rem;
        min-height: 10rem;
        outline: none;
      }
      .tiptap p { margin: 0.5rem 0; }
      .tiptap h1, .tiptap h2, .tiptap h3 { margin: 1rem 0 0.5rem; }
      .tiptap ul, .tiptap ol { padding-left: 1.5rem; }
      .tiptap pre {
        background: #111827;
        color: #f3f4f6;
        padding: 0.75rem;
        border-radius: 0.25rem;
        font-size: 0.875rem;
      }
      .tiptap code {
        background: #f3f4f6;
        padding: 0.125rem 0.25rem;
        border-radius: 0.25rem;
        font-size: 0.875rem;
      }
      .tiptap pre code {
        background: transparent;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <div class="editor-container">
      <div class="toolbar" id="toolbar">
        <button data-tiptap-button="bold">粗体</button>
        <button data-tiptap-button="italic">斜体</button>
        <button data-tiptap-button="strike">删除线</button>
        <button data-tiptap-button="code">代码</button>
        <button data-tiptap-button="h1">H1</button>
        <button data-tiptap-button="h2">H2</button>
        <button data-tiptap-button="bulletList">无序列表</button>
        <button data-tiptap-button="orderedList">有序列表</button>
        <button data-tiptap-button="blockquote">引用块</button>
        <button data-tiptap-button="codeBlock">代码块</button>
      </div>
      <div id="editor"></div>
    </div>

    <script type="module">
      import { Editor } from 'https://esm.sh/@tiptap/core'
      import StarterKit from 'https://esm.sh/@tiptap/starter-kit'

      const editor = new Editor({
        element: document.querySelector('#editor'),
        extensions: [StarterKit],
        content: `
          <h1>Welcome to Tiptap</h1>
          <p>This is a text editor built with <strong>Vanilla JavaScript</strong> and loaded from a CDN.</p>
          <p>Try the toolbar buttons above to format your text.</p>
        `,
      })

      // 绑定工具栏按钮
      const buttons = document.querySelectorAll('[data-tiptap-button]')
      buttons.forEach((button) => {
        button.addEventListener('click', () => {
          const command = button.dataset.tiptapButton

          switch (command) {
            case 'bold':
              editor.chain().focus().toggleBold().run()
              break
            case 'italic':
              editor.chain().focus().toggleItalic().run()
              break
            case 'strike':
              editor.chain().focus().toggleStrike().run()
              break
            case 'code':
              editor.chain().focus().toggleCode().run()
              break
            case 'h1':
              editor.chain().focus().toggleHeading({ level: 1 }).run()
              break
            case 'h2':
              editor.chain().focus().toggleHeading({ level: 2 }).run()
              break
            case 'bulletList':
              editor.chain().focus().toggleBulletList().run()
              break
            case 'orderedList':
              editor.chain().focus().toggleOrderedList().run()
              break
            case 'blockquote':
              editor.chain().focus().toggleBlockquote().run()
              break
            case 'codeBlock':
              editor.chain().focus().toggleCodeBlock().run()
              break
          }

          updateActiveButtons()
        })
      })

      function updateActiveButtons() {
        const map = {
          bold: () => editor.isActive('bold'),
          italic: () => editor.isActive('italic'),
          strike: () => editor.isActive('strike'),
          code: () => editor.isActive('code'),
          h1: () => editor.isActive('heading', { level: 1 }),
          h2: () => editor.isActive('heading', { level: 2 }),
          bulletList: () => editor.isActive('bulletList'),
          orderedList: () => editor.isActive('orderedList'),
          blockquote: () => editor.isActive('blockquote'),
          codeBlock: () => editor.isActive('codeBlock'),
        }

        buttons.forEach((button) => {
          const command = button.dataset.tiptapButton
          const check = map[command]
          if (check && check()) {
            button.classList.add('is-active')
          } else {
            button.classList.remove('is-active')
          }
        })
      }

      editor.on('selectionUpdate', updateActiveButtons)
      editor.on('update', updateActiveButtons)
    </script>
  </body>
</html>

将此文件保存为 index.html,然后在任何现代浏览器中打开它即可——无需服务器。

在线演示

你可以在 CodeSandbox 上查看一个类似的可运行示例:

https://codesandbox.io/s/throbbing-smoke-now37q


连接工具栏(原生 JS)

Tiptap 是无头的,因此它不自带内置工具栏。你需要自己连接 UI 按钮。下面是原生 JavaScript 中的实现方式:

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

const editor = new Editor({
  element: document.querySelector('#editor'),
  extensions: [StarterKit],
  content: '<p>Hello World!</p>',
})

// 监听点击并调用 editor 命令
document.querySelector('#bold-button').addEventListener('click', () => {
  editor.chain().focus().toggleBold().run()
})

// 检查某个 mark 或节点是否处于激活状态,以便为按钮设置样式
const isBold = editor.isActive('bold')
const isHeading1 = editor.isActive('heading', { level: 1 })
const isBulletList = editor.isActive('bulletList')

// 在选择变化时更新按钮的激活状态
editor.on('selectionUpdate', () => {
  document.querySelector('#bold-button').classList.toggle('is-active', editor.isActive('bold'))
})

常用编辑器命令

命令代码
粗体editor.chain().focus().toggleBold().run()
斜体editor.chain().focus().toggleItalic().run()
删除线editor.chain().focus().toggleStrike().run()
代码(行内)editor.chain().focus().toggleCode().run()
标题 1editor.chain().focus().toggleHeading({ level: 1 }).run()
标题 2editor.chain().focus().toggleHeading({ level: 2 }).run()
无序列表editor.chain().focus().toggleBulletList().run()
有序列表editor.chain().focus().toggleOrderedList().run()
引用块editor.chain().focus().toggleBlockquote().run()
代码块editor.chain().focus().toggleCodeBlock().run()
撤销editor.chain().focus().undo().run()
重做editor.chain().focus().redo().run()

可用命令请参阅各个扩展的文档。


样式

Tiptap 默认不包含视觉样式——它只输出语义化的 HTML。你可以使用自己的 CSS,或者使用 Tailwind 或 Bootstrap 之类的框架来按你喜欢的方式设置样式。

StarterKit 包含最小化的默认样式,可使文本呈现为一个基础文档的样子。

样式指南 中了解更多。

接下来的步骤