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

实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:37:46