You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于原生JavaScript实现Tiptap提及与建议功能求助

原生JavaScript实现Tiptap提及与表情建议功能示例

1. 安装依赖

首先安装所需的Tiptap包:

npm install @tiptap/core @tiptap/extension-mention @tiptap/suggestion

2. 完整示例代码

创建一个HTML文件,直接运行即可看到效果:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Tiptap Vanilla JS Mentions & Emojis</title>
  <style>
    /* 编辑器基础样式 */
    .tiptap {
      border: 1px solid #ddd;
      padding: 10px;
      min-height: 150px;
      border-radius: 4px;
      margin-bottom: 10px;
    }
    /* 建议列表样式 */
    .suggestions {
      position: absolute;
      background: white;
      border: 1px solid #eee;
      border-radius: 4px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
      max-height: 200px;
      overflow-y: auto;
      z-index: 100;
      display: none;
    }
    .suggestions-item {
      padding: 8px 12px;
      cursor: pointer;
      margin: 0;
      font-size: 14px;
    }
    .suggestions-item:hover {
      background: #f5f5f5;
    }
    /* 提及内容样式 */
    .mention {
      background: #e8f4fd;
      padding: 2px 4px;
      border-radius: 3px;
      text-decoration: none;
      color: #1a73e8;
    }
  </style>
</head>
<body>
  <div id="editor"></div>
  <div id="suggestions" class="suggestions"></div>

  <script type="module">
    import { Editor } from '@tiptap/core'
    import Mention from '@tiptap/extension-mention'
    import { suggestion } from '@tiptap/suggestion'

    // 模拟数据源:用户列表和表情列表
    const userList = [
      { id: 1, name: '张三', username: 'zhangsan' },
      { id: 2, name: '李四', username: 'lisi' },
      { id: 3, name: '王五', username: 'wangwu' }
    ]

    const emojiList = [
      { id: 'smile', label: '😊', name: '微笑' },
      { id: 'laugh', label: '😂', name: '大笑' },
      { id: 'love', label: '❤️', name: '爱心' },
      { id: 'cool', label: '😎', name: '酷' }
    ]

    // 生成建议组件配置
    const buildSuggestionConfig = (editor) => {
      return {
        // 触发字符:@ 用于提人,: 用于表情
        char: ['@', ':'],
        // 过滤匹配项
        filter: (items, query) => {
          return items.filter(item => {
            const searchTarget = item.name || item.label
            return searchTarget.toLowerCase().includes(query.toLowerCase())
          })
        },
        // 根据触发字符返回对应数据源
        items: ({ query, char }) => {
          return char === '@' ? userList : emojiList
        },
        // 渲染建议列表
        render: () => {
          const container = document.getElementById('suggestions')

          return {
            onUpdate(props) {
              container.innerHTML = ''
              if (props.items.length === 0) {
                container.style.display = 'none'
                return
              }

              container.style.display = 'block'
              // 定位到光标下方
              const { top, left, height } = editor.view.coordsAtPos(props.range.from)
              container.style.top = `${top + height + 5}px`
              container.style.left = `${left}px`

              // 生成列表项
              props.items.forEach(item => {
                const itemEl = document.createElement('div')
                itemEl.className = 'suggestions-item'
                itemEl.textContent = props.char === '@' ? `@${item.name}` : `${item.label} ${item.name}`
                itemEl.addEventListener('click', () => props.command({ id: item.id, label: props.char === '@' ? item.name : item.label }))
                container.appendChild(itemEl)
              })
            },
            onHide() {
              container.style.display = 'none'
            }
          }
        },
        // 选中建议项后的插入逻辑
        onSelect: ({ editor, range, props }) => {
          editor
            .chain()
            .focus()
            .deleteRange(range)
            .insertContentAt(range.from, {
              type: 'mention',
              attrs: {
                id: props.id,
                label: props.label,
                type: props.char === '@' ? 'user' : 'emoji'
              }
            })
            .insertContent(' ') // 添加空格,方便后续输入
            .run()
        }
      }
    }

    // 初始化编辑器
    const editor = new Editor({
      element: document.getElementById('editor'),
      extensions: [
        Mention.configure({
          HTMLAttributes: { class: 'mention' },
          suggestion: suggestion(buildSuggestionConfig)
        })
      ],
      content: '<p>试试输入 @ 选择用户,输入 : 选择表情</p>'
    })
  </script>
</body>
</html>

核心逻辑说明

  • 利用Mention扩展结合@tiptap/suggestion实现多触发字符的建议功能
  • 通过char配置项指定触发符号,根据符号切换数据源(用户/表情)
  • 直接操作DOM渲染建议列表,绑定点击事件触发编辑器内容插入
  • 选中建议后通过Tiptap的链式API替换光标区域内容为提及节点

内容的提问来源于stack exchange,提问作者jyms

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 07:53:35