为何document.execCommand()与contenteditable div会拆分格式化文本为节点?
问题:LWC富文本组件格式化后无法获取带格式标签的完整内容
我有一个LWC组件,在另一个组件中通过document.execCommand(action)对其中选中的文本执行格式化操作。
组件HTML结构
<template> <div contenteditable="true"> </div> <div class="clone"> </div> </template>
组件JS代码(原实现)
import { api, track, LightningElement } from 'lwc'; export default class appRichText extends LightningElement { @api textvalue; renderedCallback(){ this.divRich = this.template.querySelector("div"); this.divRich.innerHTML = this.textvalue; this.clone = this.template.querySelector(".clone"); } @api getHTML(){ let clonedHtml = this.divRich.cloneNode(true); clonedHtml.removeAttribute("contenteditable"); let completeHTML = this.getDivContentAsString(clonedHtml); return completeHTML; } getDivContentAsString(divElement) { let contentString = ""; function traverseNodes(node) { if (node.nodeType === Node.TEXT_NODE) { contentString += node.textContent; } else if (node.nodeType === Node.ELEMENT_NODE) { contentString += node.textContent; for (let childNode of node.childNodes) { traverseNodes(childNode); } } } traverseNodes(divElement); return contentString; } }
遇到的问题
- 执行
execCommand后,可编辑div中的选中文本会被拆分为多个节点(格式化前为连续文本节点,格式化后生成带格式的子节点)。 - 直接获取div的
innerHTML/outerHTML,仅能得到第一段未格式化的文本,无法获取带格式标签的完整内容。 - 自定义的节点遍历方法只能提取纯文本,无法保留格式标签;控制台显示格式化后的节点为HTMLCollection,但通过
divElement.children[0]获取失败,遍历childNodes时会跳过这些格式节点。 - 尝试过克隆节点到其他容器、创建范围、多种子节点获取方式等,均无效。
解决方案
问题根源
LWC的Shadow DOM隔离机制会影响对格式化后动态生成节点的直接访问,加上execCommand操作后的节点结构变化,常规的innerHTML获取可能因组件渲染时机或Shadow DOM限制失效。
优化后的实现
方案1:直接获取innerHTML(推荐)
修改getHTML方法,同时调整渲染逻辑避免覆盖格式化后的内容:
import { api, LightningElement } from 'lwc'; export default class AppRichText extends LightningElement { @api textvalue; divRich; renderedCallback() { // 初始化元素引用 if (!this.divRich) { this.divRich = this.template.querySelector('[contenteditable="true"]'); } // 仅在初始渲染或textvalue更新时同步内容,避免覆盖用户操作 if (this.divRich && this.divRich.innerHTML !== this.textvalue) { this.divRich.innerHTML = this.textvalue; } } @api getHTML() { // 直接获取可编辑div的完整innerHTML return this.divRich?.innerHTML || ''; } }
方案2:正确遍历节点并保留格式标签
如果需要手动遍历节点生成HTML字符串,修改遍历逻辑以保留标签结构:
getDivContentAsString(divElement) { let contentString = ""; function traverseNodes(node) { if (node.nodeType === Node.TEXT_NODE) { contentString += node.textContent; } else if (node.nodeType === Node.ELEMENT_NODE) { // 拼接元素开始标签(包含属性) const tagName = node.tagName.toLowerCase(); contentString += `<${tagName}`; // 遍历并拼接所有属性 Array.from(node.attributes).forEach(attr => { contentString += ` ${attr.name}="${attr.value}"`; }); contentString += '>'; // 递归遍历子节点 Array.from(node.childNodes).forEach(childNode => { traverseNodes(childNode); }); // 拼接元素结束标签 contentString += `</${tagName}>`; } } traverseNodes(divElement); return contentString; }
关键说明
- 渲染时机控制:在
renderedCallback中增加内容一致性判断,防止每次渲染时用初始值覆盖用户已格式化的内容。 - Shadow DOM访问:组件内部可直接通过
querySelector访问Shadow DOM内的元素,无需克隆节点即可获取完整innerHTML。 - 节点遍历优化:手动遍历时需拼接元素的标签和属性,而不是仅提取
textContent,才能保留格式结构。
内容的提问来源于stack exchange,提问作者Mauricio Vilar
相关产品推荐
相关产品推荐

