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

如何实现Quill Emoji Blot?解决删除表情需两次退格的问题

解决Quill Editor表情Blot的光标与删除问题

我之前在实现Quill表情嵌入时也碰到过一模一样的问题——为了修复光标异常加了空格,结果删除表情得按两次退格。咱们来拆解问题,然后给出完美的解决方案:

问题根源分析

你现在的代码插入了两个独立的Blot:一个是表情的Embed Blot(默认长度为1),另一个是空格的Text Blot(长度为1)。第一次按退格删除的是空格Blot,第二次才会触发表情Blot的删除,这就是需要两次退格的核心原因。而最初的光标异常,其实是Quill对自定义内联嵌入元素的光标定位需要额外配置,根本不需要手动插入空格来规避。

修复后的完整代码

我们只需要调整EmojiBlot的实现,去掉多余的空格插入逻辑,同时让Quill能正确识别并处理表情元素的光标定位:

const Embed = Quill.import("blots/embed");
class EmojiBlot extends Embed {
  static create(classes) {
    let node = super.create();
    classes.split(" ").forEach(iconClass => {
      node.classList.add(iconClass);
    });
    // 添加该属性,避免光标跑到表情元素内部
    node.setAttribute('contenteditable', 'false');
    return node;
  }
  static formats(node) {
    let format = {};
    if (node.hasAttribute("class")) {
      format.class = node.getAttribute("class");
    }
    return format;
  }
  static value(node) {
    return node.getAttribute("class");
  }
  format(name, value) {
    if (name === "class") {
      if (value) {
        this.domNode.setAttribute(name, value);
      } else {
        this.domNode.removeAttribute(name, value);
      }
    } else {
      super.format(name, value);
    }
  }
}
EmojiBlot.blotName = "emoji";
EmojiBlot.tagName = "span";
// 关键:明确标记为内联元素,让Quill按内联规则处理光标与排版
EmojiBlot.scope = Quill.Scope.INLINE;
Quill.register({ "formats/emoji": EmojiBlot });

var myEditor = new Quill("#editor-container", {
  modules: {
    toolbar: document.getElementById("toolbar")
  },
  placeholder: "Compose an epic...",
  theme: "snow"
});

const insertEmoji = function() {
  let editorSelection = myEditor.getSelection();
  const cursorPosition = editorSelection?.index || 0;
  // 仅插入表情Embed Blot,无需额外空格
  myEditor.insertEmbed(cursorPosition, "emoji", 'icon icon-smiley');
  // 直接将光标定位到表情后方,一步到位
  myEditor.setSelection(cursorPosition + 1);
};
document.querySelector(".emojiButton").addEventListener("click", insertEmoji);
#editor-container {
  height: 200px;
}
.icon {
  display: inline-block;
  height: 1em;
  width: 1em;
  margin: 0 .05em 0 .1em;
  vertical-align: -0.1em;
  background-repeat: no-repeat;
  background-position: center center;
  background-size: 1em 1em;
  font-size: 20px;
}
.icon-smiley {
  background-image: url("https://twemoji.maxcdn.com/2/svg/1f603.svg");
}
<link href="//cdn.quilljs.com/1.3.5/quill.snow.css" rel="stylesheet"/>
<script src="//cdn.quilljs.com/1.3.5/quill.js"></script>
<div id="editor-container"></div>
<div id="toolbar">
  <button class="ql-bold"></button>
  <button class="ql-italic"></button>
  <button class="emojiButton">:D</button>
</div>

关键修改点说明

  1. 添加contenteditable="false":告诉浏览器这个表情元素不可编辑,从根源上避免光标跑到表情内部的异常情况。
  2. 指定EmojiBlot.scope = Quill.Scope.INLINE:明确告知Quill这是一个内联元素,让它按照内联内容的规则处理光标移动和排版逻辑。
  3. 移除空格插入逻辑:不再额外插入空格,直接将光标定位到表情后方(cursorPosition + 1),因为Embed Blot的默认长度为1,这个定位是准确的。

这样修改后,插入表情后光标会自动停在表情后面,删除表情只需要按一次退格键,两个问题都能完美解决!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:37:56