要使contenteditable中表情输入生效,需手动触发input事件;避免innerHTML插入img表情以防光标错乱;移动端需focus+scrollIntoView防键盘遮挡;emoji-picker-element在Shadow DOM中需显式绑定focus事件;优先用insertAdjacentText+getSelection插入表情;滚动卡顿主因是图片解码,应加loading="lazy"和decoding="async"并启用will-change: transform。

怎么用 contenteditable 让表情面板真正可输入
直接在 <div contenteditable="true"> 里点选表情,不触发输入框更新,是因为浏览器默认不把 emoji 当作“文本变更事件”。必须手动触发 input 事件,否则绑定的 oninput 或 addEventListener('input', ...) 完全收不到信号。
- 选中表情后,用
el.innerText += emoji拼接,再调用el.dispatchEvent(new Event('input', { bubbles: true })) - 别用
innerHTML插入带<img>的表情(如自定义图),会破坏光标位置;纯 Unicode 表情最稳 - 移动端 Safari 对
contenteditable的光标控制极弱,插入后建议用el.focus()+setTimeout(() => el.scrollIntoView({ block: 'nearest' }), 0)防止键盘遮挡
为什么 emoji-picker-element 在 Vue/React 里常不响应
这个 Web Component 默认只监听全局 document 的点击,一旦被封装进 Shadow DOM(比如 Vue 的 shadow: true 或 React 的自定义渲染器),事件就漏掉了。不是组件坏了,是事件没冒泡上来。
- 显式设置
data-show-on-focus="true"并绑定到目标输入框的focus事件,而不是依赖自动发现 - 在 Vue 中使用时,加
ref后手动调用pickerRef.value.showPicker(inputEl),绕过自动绑定逻辑 - React 中若用
createPortal渲染面板,需确保picker实例挂载在document.body下,而非 Portal 内部节点
insertAdjacentText 和 execCommand 哪个更适合插入表情
execCommand 已废弃,Chrome 98+ 完全禁用,Safari 从不支持;但很多人还在用它处理光标位置——其实 insertAdjacentText 配合 getSelection 更可靠,只是需要多几行定位逻辑。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 先用
const sel = window.getSelection()获取当前选区 - 检查
sel.rangeCount > 0,再取sel.getRangeAt(0),调用range.insertNode(document.createTextNode(emoji)) - 插入后执行
range.collapse(false)把光标移到表情后面,避免连续点击重复插入 - 注意:IE11 不支持
insertNode对TextNode,如需兼容,降级用document.execCommand('insertText', false, emoji)并捕获异常
表情面板滚动卡顿?检查 will-change: transform 和图片解码
几百个表情图标一次性渲染,尤其是带 <img src="..."> 的,滚动掉帧主因不是 DOM 数量,而是图片加载解码阻塞主线程。Chrome DevTools 的 Performance 面板里常看到 Decode Image 占满一长条。
立即学习“前端免费学习笔记(深入)”;
- 所有表情图用
loading="lazy",并加decoding="async"属性 - 面板容器加
style="will-change: transform",强制 GPU 加速滚动(仅对transform/opacity有效) - 避免用
background-image加载表情图——CSS 背景图无法设置decoding,且不会触发load事件做懒加载控制


















