Iframe文档写入不更新JavaScript

w80xi6nr  于 2023-08-02  发布在  Java
关注(0)|答案(6)|浏览(108)

下面是一个基本的HTML编辑器:

var textarea = document.querySelector('textarea');

function preview() {
  var iframeDoc = document.querySelector('iframe').contentDocument;
  iframeDoc.open();
  iframeDoc.write(textarea.value);
  iframeDoc.close();
}

textarea.addEventListener('input', preview);
textarea,
iframe {
  width: 400px;
  height: 300px;
}
<textarea></textarea>
<iframe></iframe>

DEMO
它会更新你放在textarea中的HTML和CSS,但你不能使用JavaScript constlet变量,因为一旦你编辑插入的代码,它就会抛出以下语法错误:
Identifier * has already been declared
要理解我的意思,请在textarea中插入以下示例代码:

<!doctype html>
<html lang="en">
<head>
  <title>Sample Code</title>
</head>
<body>
  <p>Hello!</p>
  <script>
    const p = document.querySelector('p');
    p.style.color = 'blue';
  </script>
</body>
</html>


现在将Hello!更改为Hello, world!,或将blue更改为red
有什么解决方案可以让用户继续编辑代码而不会出现错误?

更新

svarlitskiy's answer的启发,我最终决定用一个新创建的iframe来替换它:

function preview() {
  var iframe = document.createElement('iframe');
  document.querySelector('iframe').replaceWith(iframe);
  var iframeDoc = iframe.contentDocument;
  iframeDoc.write(textarea.value);
  iframeDoc.close();
}

d6kp6zgx

d6kp6zgx1#

在我看来,主要有两种方法可以做到这一点:使用srcdoc属性(@Kaiido的答案)或使用blob URL。在这个答案中,我将解释这些选项是什么以及它们的区别。我还将给予你一个代码示例,说明如何使用blob URL选项。

使用srcdoc

srcdoc属性允许您将iframe的HTML内容写成字符串。这很简单,但也有一些问题:
·一些旧的浏览器不支持srcdoc属性,因此您可能需要使用src属性提供后备。
HTML内容可能包含恶意脚本或链接,可能会危及主文档的安全性。您可能需要在将HTML内容分配给srcdoc属性之前对其进行清理或转义。
因此,如果您不介意这样的问题,您可能不需要这个答案,因为使用blob URL比使用srcdoc更复杂和间接。

使用blob URL

blob URL方法包括从HTML内容创建一个新的blob对象,然后将其URL分配给iframe的src属性。这种方法可以避免一些兼容性和安全性问题:
大多数支持src属性的浏览器都支持blob URL,因此您无需担心回退。
Blob URL与主文档是分开的,并且有自己的来源和权限,因此它不能访问或修改主文档中的任何内容。

示例:

下面是如何使用blob URL方法的代码示例。我使用了一个debounce函数来避免过于频繁地更新iframe,这可能会导致 Flink 。您可以找到有关debounce函数here的更多信息。

function preview() {
  var blob = new Blob([textarea.value], { type: "text/html" });
  var url = URL.createObjectURL(blob);
  iframe.onload = function () {
    URL.revokeObjectURL(url);
  };
  iframe.src = url;
}

var debounceTimer;

function debounce(func, delay) {
  return function () {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(func, delay);
  };
}

var debouncedPreview = debounce(preview, 300);

textarea.addEventListener("input", debouncedPreview);

字符串

var textarea = document.querySelector("textarea");
var iframe = document.querySelector("iframe");
var debounceTimer;

function debounce(func, delay) {
  return function () {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(func, delay);
  };
}

function preview() {
  var blob = new Blob([textarea.value], { type: "text/html" });
  var url = URL.createObjectURL(blob);
  iframe.onload = function () {
    URL.revokeObjectURL(url);
  };
  iframe.src = url;
}

var debouncedPreview = debounce(preview, 300);

textarea.addEventListener("input", debouncedPreview);
textarea,
iframe {
  width: 400px;
  height: 300px;
}
<textarea></textarea>
<iframe src=""></iframe>

总之,srcdoc和blob URL方法都可以用于在iframe中创建HTML内容的实时预览。srcdoc方法更简单,但兼容性和安全性较差,而blob URL方法更复杂,但兼容性和安全性更高。我更喜欢blob URL方法,因为它避免了旧浏览器和恶意内容的一些潜在问题,但您可以选择适合您的需求和偏好的方法。

yzxexxkh

yzxexxkh2#

设置iframe的srcdoc,而不是重新打开它的Document

var textarea = document.querySelector('textarea'),
  iframe = document.querySelector('iframe');

function preview() {
  iframe.srcdoc = textarea.value;
}

textarea.addEventListener('input', preview);
textarea,
iframe {
  width: 400px;
  height: 300px;
}
<textarea></textarea>
<iframe></iframe>
dxxyhpgq

dxxyhpgq3#

这将允许您输入部分HTML,并且仍然在iframe中呈现页面而不会 Flink 。

HTML

<textarea id="input"></textarea>
<iframe id="result"></iframe>

字符串

CSS

textarea,
iframe {
    width: 400px;
    height: 300px;
}

JS

const textarea = document.getElementById('input');
let iframe = document.getElementById('result');

function preview() {
    const elem = document.createElement("iframe");
    const dom = (new DOMParser()).parseFromString( textarea.value, 'text/html' );
    
    elem.id = iframe.id
    iframe.replaceWith( elem );
    iframe = elem;
    
    const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
    if ( !iframeDoc ) return null;
    iframeDoc.open();
    iframeDoc.write( dom.documentElement.outerHTML );
    iframeDoc.close();
}

textarea.addEventListener('input', preview);

xdnvmnnf

xdnvmnnf4#

另一种方法:

function preview() {
  document.querySelector('iframe').outerHTML = '<iframe></iframe>';
  var iframeDoc = document.querySelector('iframe').contentDocument;
  iframeDoc.write(textarea.value);
  iframeDoc.close();
}

字符串

at0kjp5o

at0kjp5o5#

据我所知,没有办法在JS中取消声明变量或将它们从文档的范围中删除。我建议在每个input事件上动态地重新创建一个iframe元素。大概是这样的:

// index.js
const textarea = document.querySelector("textarea");

function preview() {
  const container = document.getElementById("iframe-container");
  const existing = document.querySelector("iframe");

  if (existing) existing.remove();

  const iframe = document.createElement("iframe");
  container?.appendChild(iframe);
  const iframeDoc = iframe?.contentDocument;

  iframeDoc?.open();
  iframeDoc?.write(textarea?.value ?? "");
  iframeDoc?.close();
}

textarea?.addEventListener("input", preview);

个字符

ecr0jaav

ecr0jaav6#

您遇到的问题是由于每次调用iframeDoc.write(textarea.value)时,它都会完全重新创建iframe的内容,包括其JavaScript上下文。这意味着当您修改文本区域中的内容时,新的JavaScript代码会与之前声明的变量发生冲突,从而导致“Identifier has already been declared”错误。
为了解决这个问题,并允许用户继续编辑代码而不会出现错误,您可以使用
document.createElement
方法创建新的脚本元素,并将它们添加到iframe文档的头部。这样,您就不会覆盖整个iframe内容,并且JavaScript上下文将被保留。

var textarea = document.querySelector('textarea'),
  iframe = document.querySelector('iframe');

function preview() {
  var iframeDoc = iframe.contentDocument;
  iframeDoc.open();
  iframeDoc.write(textarea.value);
  iframeDoc.close();

  // Extract and execute script tags in the iframe separately
  var scriptElements = iframeDoc.getElementsByTagName('script');
  for (var i = 0; i < scriptElements.length; i++) {
    eval(scriptElements[i].text);
  }
}

textarea.addEventListener('input', preview);
textarea,
iframe {
  width: 400px;
  height: 300px;
}

字符串
使用这种方法,当您更新文本区域中的内容时,它只会使用iframeDoc.write()更新iframe中的HTML和CSS部分。iframe内部的JavaScript代码将被提取并单独执行,使用脚本标记上的eval(),允许用户继续编辑代码,而不会遇到“标识符已被声明”错误。

相关问题