实现contenteditable中<rt>/<rb>标签文本的可编辑权限控制
实现Ruby标签的双模式可编辑功能
原生简洁实现方案
核心思路
通过动态控制<rb>和<rt>的contenteditable状态,结合事件监听拦截对不可编辑区域的非法操作(退格删除、跨区域选中),同时保证光标在可编辑字符间正常移动。
完整代码实现
HTML结构
<button id="switchMode">切换编辑模式(当前:编辑主文本)</button> <div contenteditable="true" id="rubyEditor"> <ruby> <rb>T</rb><rt>u</rt> <rb>E</rb><rt>f</rt> <rb>S</rb><rt>t</rt> <rb>T</rb><rt>u</rt> </ruby> </div>
CSS样式(优化显示)
#rubyEditor { padding: 8px; border: 1px solid #ccc; min-height: 40px; font-size: 20px; } ruby { margin-right: 4px; }
JavaScript逻辑
const editor = document.getElementById('rubyEditor'); const switchBtn = document.getElementById('switchMode'); let editRB = true; // 默认编辑<rb>区域 // 切换模式函数 function toggleEditMode() { editRB = !editRB; const rbs = editor.querySelectorAll('rb'); const rts = editor.querySelectorAll('rt'); rbs.forEach(rb => rb.contentEditable = editRB ? 'true' : 'false'); rts.forEach(rt => rt.contentEditable = editRB ? 'false' : 'true'); switchBtn.textContent = `切换编辑模式(当前:${editRB ? '编辑主文本' : '编辑假名注音'})`; // 切换后自动定位光标到第一个可编辑节点 const firstEditable = editRB ? rbs[0] : rts[0]; const range = document.createRange(); const sel = window.getSelection(); range.setStart(firstEditable, 0); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); } // 拦截非法删除操作 editor.addEventListener('keydown', (e) => { const key = e.key; if (key !== 'Backspace' && key !== 'Delete') return; const sel = window.getSelection(); if (sel.isCollapsed) { // 光标处于折叠状态(无选中内容) const currentNode = sel.anchorNode; const isEditableNode = (editRB && currentNode.parentNode.tagName === 'RB') || (!editRB && currentNode.parentNode.tagName === 'RT'); if (!isEditableNode) { e.preventDefault(); return; } // 退格时:光标在可编辑节点开头,禁止删除前方不可编辑内容 if (key === 'Backspace' && sel.anchorOffset === 0) { const prevSibling = currentNode.parentNode.previousElementSibling; if (prevSibling && prevSibling.tagName !== (editRB ? 'RB' : 'RT')) { e.preventDefault(); } } // Delete时:光标在可编辑节点末尾,禁止删除后方不可编辑内容 if (key === 'Delete' && sel.anchorOffset === currentNode.textContent.length) { const nextSibling = currentNode.parentNode.nextElementSibling; if (nextSibling && nextSibling.tagName !== (editRB ? 'RB' : 'RT')) { e.preventDefault(); } } } else { // 有选中内容时,若包含不可编辑区域则阻止删除 const range = sel.getRangeAt(0); const forbiddenNodes = Array.from(editor.querySelectorAll(editRB ? 'rt' : 'rb')); const hasForbidden = forbiddenNodes.some(node => range.intersectsNode(node)); if (hasForbidden) e.preventDefault(); } }); // 修正选中范围,禁止选中不可编辑区域 editor.addEventListener('selectionchange', () => { const sel = window.getSelection(); if (sel.rangeCount === 0) return; const range = sel.getRangeAt(0); const forbiddenNodes = Array.from(editor.querySelectorAll(editRB ? 'rt' : 'rb')); forbiddenNodes.forEach(node => { if (range.intersectsNode(node)) { // 自动切换到对应可编辑节点的选中状态 const editableSibling = editRB ? node.parentNode.querySelector('rb') : node.parentNode.querySelector('rt'); if (editableSibling) { sel.removeAllRanges(); const newRange = document.createRange(); newRange.selectNodeContents(editableSibling); sel.addRange(newRange); } } }); }); // 绑定切换按钮事件 switchBtn.addEventListener('click', toggleEditMode); // 初始化状态 toggleEditMode();
关键细节说明
- 动态切换
contenteditable属性,确保仅目标区域可编辑 - 键盘事件拦截:针对退格/删除键,判断光标位置与选中内容,阻止对不可编辑区域的操作
- 选中事件监听:自动修正选中范围,避免选中不可编辑标签内容
- 模式切换后自动定位光标到第一个可编辑节点,提升操作流畅度
成熟库备选方案
如果不想手动处理复杂的光标与事件逻辑,可选用以下富文本编辑器实现:
- Quill:自定义Blots格式模块,为
<rb>和<rt>分别配置可编辑状态,通过工具栏按钮切换编辑权限 - CKEditor:利用内容过滤与自定义命令功能,限制特定标签的编辑权限,实现双模式切换
内容的提问来源于stack exchange,提问作者Dewi Morgan
相关产品推荐
相关产品推荐

