TinyMCE:如何通过PastePostProcess的e.node动态填充创建的iframe
你遇到的这个问题其实很常见,核心原因是:你通过e.node创建的iframe此时还只是内存中的DOM节点,还没被挂载到编辑器的实际DOM树里,所以它的contentDocument还没完成初始化,自然是null。
而你用editor.dom.select("[id='aaa']")[0]能拿到可用的iframe,是因为当$.post的回调执行时,TinyMCE已经完成了粘贴的后续流程,把e.node里的内容插入到了编辑器的DOM中,此时通过DOM查询找到的iframe已经是挂载到页面上的有效节点,contentDocument也就正常可用了。
具体解决步骤
要通过e.node动态定位并操作iframe,你需要等待它被插入到编辑器的DOM树之后再访问contentDocument,这里有两种可靠的实现方式:
方式1:利用setTimeout让浏览器先完成DOM插入
这是最简单的实现方式,通过0延迟的setTimeout把操作放到浏览器的下一个事件循环,此时iframe已经被挂载到DOM中:
tinymce.init({ selector: "textarea", plugins: 'paste', height: 300, extended_valid_elements: "iframe[* ], div[* ]", init_instance_callback: function (editor) { editor.on('PastePostProcess', function (e) { // 直接创建iframe并保存引用,不用硬编码ID const iframe = editor.dom.create('iframe', { style: "width:450px;", src: "about:blank" }); // 构建包裹的div并插入iframe e.node.innerHTML = '<div></div>'; e.node.firstChild.appendChild(iframe); $.post("..", {}, function (data) { // 等待浏览器完成DOM插入后再操作 setTimeout(() => { console.log(iframe.contentDocument); // 此时已正常返回HTMLDocument const msg = "The iframe has been populated using dynamic reference!<br/>"; msg += "No hard-coded ID needed anymore!"; iframe.contentDocument.write(msg); }, 0); }); }); } });
方式2:用MutationObserver监听DOM变化(更可靠)
如果你想更精确地监听iframe的插入时机,可以用MutationObserver,它能在节点被插入到DOM时触发回调:
tinymce.init({ selector: "textarea", plugins: 'paste', height: 300, extended_valid_elements: "iframe[* ], div[* ]", init_instance_callback: function (editor) { editor.on('PastePostProcess', function (e) { const iframe = editor.dom.create('iframe', { style: "width:450px;", src: "about:blank" }); e.node.innerHTML = '<div></div>'; e.node.firstChild.appendChild(iframe); $.post("..", {}, function (data) { // 创建观察者,监听编辑器内容区的节点变化 const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { // 检查是否是我们的iframe的父节点被插入 if (mutation.addedNodes.length && mutation.addedNodes[0] === iframe.parentElement) { console.log(iframe.contentDocument); const msg = "The iframe has been populated using MutationObserver!<br/>"; msg += "Perfect timing for DOM operations!"; iframe.contentDocument.write(msg); observer.disconnect(); // 完成操作后停止监听 } }); }); // 监听编辑器内容区的子节点变化,包括子树 observer.observe(editor.getBody(), { childList: true, subtree: true }); }); }); } });
为什么之前的e.node.firstChild.firstChild.contentDocument是null?
在PastePostProcess事件触发时,TinyMCE只是把你处理后的e.node暂存起来,还没把它插入到编辑器的DOM树中。此时这个iframe节点只存在于内存里,没有和页面的DOM关联,所以浏览器还没初始化它的contentDocument对象。而当$.post的回调执行时,TinyMCE已经完成了粘贴流程,把e.node的内容插入到了DOM中,所以通过editor.dom.select能找到已经挂载的iframe,contentDocument就正常了。
内容的提问来源于stack exchange,提问作者Fizz

