伏鸣 發表於 2025-4-17 19:34:00

一个神奇的JS代码,让浏览器在新的空白标签页运行我们 HTML 代码(createObjectURL 的妙用)

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>前言</li><li>问题</li><li>一个偶然</li><li>预览 HTML 代码</li></ul></div><p></p>
<h2 id="前言">前言</h2>
<p>目前,网上很多在线运行 HTML 的页面,大都是这样的逻辑:</p>
<p>上面一个代码框 <code>&lt;textera&gt;</code> ,下面一个 <code>&lt;iframe&gt;</code>,然后通过 js,将我们的代码框中的 HTML 给输入到 <code>&lt;iframe&gt;</code> 里面,这便是一个简单的在线运行 html 的逻辑。</p>
<p>甚至我们可以在一行里写一个在线运行 html 的页面。比如下面这个,一个高度精简的简短的 HTML JS CSS代码:</p>
<pre><code class="language-html">&lt;body oninput="i.srcdoc=h.value+'&lt;style&gt;'+c.value+'&lt;/style&gt;&lt;script&gt;'+j.value+'&lt;/script&gt;'"&gt;&lt;style&gt;textarea,iframe{width:100%;height:50%}body{margin:0}textarea{width:33.33%;font-size:18}&lt;/style&gt;&lt;textarea placeholder=HTML id=h&gt;&lt;/textarea&gt;&lt;textarea placeholder=CSS id=c&gt;&lt;/textarea&gt;&lt;textarea placeholder=JS id=j&gt;&lt;/textarea&gt;&lt;iframe id=i&gt;
</code></pre>
<p>如果想运行,很简单,将下面这个代码直接粘贴到浏览器框里,并回车即可:</p>
<pre><code class="language-html">data:text/html,&lt;body oninput="i.srcdoc=h.value+'&lt;style&gt;'+c.value+'&lt;/style&gt;&lt;script&gt;'+j.value+'&lt;/script&gt;'"&gt;&lt;style&gt;textarea,iframe{width:100%;height:50%}body{margin:0}textarea{width:33.33%;font-size:18}&lt;/style&gt;&lt;textarea placeholder=HTML id=h&gt;&lt;/textarea&gt;&lt;textarea placeholder=CSS id=c&gt;&lt;/textarea&gt;&lt;textarea placeholder=JS id=j&gt;&lt;/textarea&gt;&lt;iframe id=i&gt;
</code></pre>
<p>效果如下</p>
<p><img src="https://img2024.cnblogs.com/blog/1669501/202504/1669501-20250416184037010-1589119229.png" alt="image" loading="lazy"></p>
<p>上面三个框分别是 HTML CSS JS 代码,直接通过 <code>i.srcdoc=h.value+'&lt;style&gt;'+c.value+'&lt;/style&gt;&lt;script&gt;'+j.value+'&lt;/script&gt;</code> 这样一行 js 便赋予给了 <code>&lt;iframe&gt;</code>,然后就能在线运行我们的 前端代码 了。</p>
<p>原理就是这样,无论是菜鸟教程里的代码运行框,还是大名鼎鼎的 codepen.io 这种代码分享库,其原理都差不多。</p>
<p><img src="https://img2024.cnblogs.com/blog/1669501/202504/1669501-20250416191021640-1160234717.png" alt="image" loading="lazy"></p>
<p>看,长的一模一样。</p>
<h2 id="问题">问题</h2>
<p>但这个时候,就出现了一个问题。</p>
<p>我们有可能,不喜欢在一个页面里夹杂着另一个页面,感觉太闷了,如果 HTML 代码是在一个新的页面运行里就好了。其实实现不难,就是有点费资源。</p>
<p>渐渐我,我就忘了这个问题。</p>
<h2 id="一个偶然">一个偶然</h2>
<p>去年,在改进我制作的一个在某 CMS 平台运行的图片压缩插件的时候,我忘了是从哪里复制过来的一个代码,让我这个插件有了这样一个好功能 : 单击某个按钮可以在新的标签页预览一个图片。至于原程序这里放不下,但我们可以这样体验一下,在 F12 浏览器 JS 控制台输入下面的代码:</p>
<pre><code class="language-js">const imgurl = 'https://assets.cnblogs.com/logo.svg';// 博客园的 logo 地址
const imgTempBlob = new Blob(['&lt;img style="max-width:100%" src="'+ imgurl +'"&gt;'], {type: 'text/html'});
const imgBlobObjurl = window.URL.createObjectURL(imgTempBlob);
window.open(imgBlobObjurl, '_blank');
URL.revokeObjectURL(imgBlobObjurl);// 如果不添加这一行,那么那个地址会一直有效,直到浏览器自己清除
</code></pre>
<p>然后浏览器会马上打开一个新页面,然后将 博客园 的 logo 给展示了出来!如图所示:</p>
<p><img src="https://img2024.cnblogs.com/blog/1669501/202504/1669501-20250417174726900-1531829082.png" alt="image" loading="lazy"></p>
<p>我当时被震惊了。</p>
<p>在过去,我对 js 的二进制的理解只有 ArrayBuffer base64 Blob 三者,且这三者是可以互相转化的。直到今天,我才知道在 JS 二进制 世界里竟然还有一个 createObjectURL 这样一个方法。</p>
<p>createObjectURL 可以把内存里的一个东西,比如一个字符串、一个图像二进制,等,转换成一个 URL,这样你就可以使用 DOM 渲染出来。比如你加载了一张图片,然后修改了一些内容,接下来要渲染到网页上,就会用到这个函数方法。</p>
<p>当然,我们也要注意,每次调用 createObjectURL() 时,都会创建一个新的对象 URL,即使已经为同一个对象创建了一个 URL。当不再需要这些对象时,<mark>必须通过调用 URL.revokeObjectURL() 来释放它们</mark>,浏览器会在卸载文档时自动释放对象 URL;然而,为了优化性能和内存使用,如果在安全时间内可以明确卸载,就应该卸载。createObjectURL() 创建的 URL 会占用内存,如果不手动释放,可能会导致内存泄漏。</p>
<p>这个真是好用,而且用在图片的预览上真的太恰当了!为什么很少见到有人使用它呢?或者说我几乎没在别的地方见过 <code>xxx.com/c533df96-d49e-49af-9a8c-bdbab35b7baf</code> 类似的地址呢?可能是会造成性能上的不妥吧。</p>
<p>我后来发觉到里面的 HTML 代码,我感觉它可以再复杂一点。</p>
<p>它可以不预览图片,它可以作为 HTML 在线编辑器的最彻底的预览!</p>
<h2 id="预览-html-代码">预览 HTML 代码</h2>
<p>我们可以写一个简单的文本框,然后写一个按钮,让按钮在单击后,在新的页面预览我们的代码运行效果。代码如下:</p>
<pre><code class="language-html">&lt;textarea id="htmlcode" placeholder="在此输入 HTML 代码"&gt;&lt;/textarea&gt;&lt;br&gt;
&lt;button id="runcode"&gt;在新窗口预览&lt;/button&gt;

&lt;script&gt;
runcode.onclick = function() {
    const codeBlob = new Blob([`&lt;meta charset="utf-8"&gt;` + htmlcode.value], {type: 'text/html'});
    const codeTempUrl = window.URL.createObjectURL(codeBlob);
    window.open(codeTempUrl, '_blank');
    URL.revokeObjectURL(codeTempUrl);
}
&lt;/script&gt;
</code></pre>
<p>运行效果如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/1669501/202504/1669501-20250417183712367-956295346.png" alt="image" loading="lazy"></p>
<p>单击按钮后,</p>
<p><img src="https://img2024.cnblogs.com/blog/1669501/202504/1669501-20250417183740221-596240187.png" alt="image" loading="lazy"></p>
<p>很不错。然后我们就可以根据我们自身的需要,为其添加这样的功能!</p>
<ul>
<li>在写代码的时候,添加保存快捷键,让其保存到我们的浏览器的 <code>localStorage</code> 里,防止丢失代码</li>
<li>设置一个保存按钮</li>
<li>把 CSS 和 JS 也搞里头,单独设置两个框</li>
<li>一键清除记录</li>
<li>简单的把界面给美化一下</li>
<li>可以将当前的页面转化为 HTML 文件下载</li>
</ul>
<p>然后我就搞成了这个样子!</p>
<p>大家可以单击这个地址,查看运行效果! https://www.ccgxk.com/528.html我的站点上的一个页面将其嵌入其中了。</p>
<p><img src="https://img2024.cnblogs.com/blog/1669501/202504/1669501-20250417184353031-1778630240.png" alt="image" loading="lazy"></p>
<p>我感觉真的很实用。我在使用 Gemini 这种对前端输出比较厉害的 AI 的时候,它会给我输出一大堆的 HTML ,有时候还会分 CSS 和 JS 输出,我使用这个页面来辅助测试的话还不错,比类似于 codepen 那种页面好用很多。</p>
<p>下面是目前的全部代码,可能还有很多地方要改进的,我之后在使用过程中会进行迭代,并实时更新到我的站点上的那个页面上。</p>
<pre><code class="language-html">&lt;style&gt;
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}

button { cursor: pointer; }

.code-textarea {width: 100%;height: 250px;padding: 1em;background-color: azure; border: 0; outline: 0;}

#htmlcode::::placeholder { color: grey; }

.code-action-btn {float: right;background-color: antiquewhite;border: 0;width: 60px;height: 50px;font-size: 22px;padding: 0;}
&lt;/style&gt;

&lt;div style="width: 600px;max-width: 100%;"&gt;
        &lt;textarea placeholder="在此处输入 HTML 代码,单击下面的【运行】,浏览器会新建一个空白标签页运行预览..." name="code" id="htmlcode" class="code-textarea"&gt;&lt;/textarea&gt;&lt;br&gt;
        &lt;button class="code-action-btn" id="runhtml"&gt;运行&lt;/button&gt;
        &lt;button class="code-action-btn" id="dwnhtml" style="margin-right: 10px;"&gt;下载&lt;/button&gt;
        &lt;button class="code-action-btn" id="delhtml" style="margin-right: 10px;"&gt;清空&lt;/button&gt;
        &lt;button class="code-action-btn" id="savehtml" style="margin-right: 10px;"&gt;保存&lt;/button&gt;
        &lt;details&gt;
        &lt;summary style="cursor: pointer;background-color: cornsilk;width: fit-content;"&gt;CSS + JS
                &lt;div id="res_info" style=" display: inline; margin-left: 10px; background-color: white; padding-left: 10px; "&gt;&lt;/div&gt;
        &lt;/summary&gt;
        CSS &lt;br&gt;
        &lt;textarea class="code-textarea" name="code" id="csscode"&gt;&lt;/textarea&gt;&lt;br&gt;
        JS &lt;br&gt;
        &lt;textarea class="code-textarea" name="code" id="jscode"&gt;&lt;/textarea&gt;
        &lt;/details&gt;
&lt;/div&gt;

&lt;script&gt;
// 生成随机字符串
function generateRandomString(length = 8) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let result = '';
    for (let i = 0; i &lt; length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
}

// 浏览器运行后检测本地 storage
const codeData = (JSON.parse(localStorage.getItem('codeData'))) || false;
if (codeData) {
    htmlcode.value = codeData.htmlcode;
    csscode.value = codeData.csscode;
    jscode.value = codeData.jscode;
}

// 点击 运行 事件
runhtml.onclick = function() {
    saveHtmlFunc();
    let testBlob;
    if (csscode.value || jscode.value) {
      testBlob = new Blob([`&lt;meta charset="utf-8"&gt;` + `&lt;style&gt;` + csscode.value + `&lt;/style&gt;` + htmlcode.value + `&lt;script&gt;` + jscode.value + `&lt;/sc` + `ript&gt;`], {
            type: 'text/html'
      });
    } else {
      testBlob = new Blob([`&lt;meta charset="utf-8"&gt;` + htmlcode.value], {
            type: 'text/html'
      });
    }
    const codeTempUrl = window.URL.createObjectURL(testBlob);
    window.open(codeTempUrl, '_blank');
    URL.revokeObjectURL(codeTempUrl);
}

// 保存 HTML 函数
function saveHtmlFunc() {
    const codeContent = {
      htmlcode: htmlcode.value,
      csscode: csscode.value,
      jscode: jscode.value
    };
    localStorage.setItem('codeData', JSON.stringify(codeContent));
    res_info.innerHTML = "&lt;span style='animation: fadeOut 3s forwards; opacity: 1;'&gt;已暂存&lt;/span&gt;";
}

// 按下保存快捷键后
document.addEventListener('keydown', function(e) { // 保存快捷键
    if (e.keyCode == 83 &amp;&amp; (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) {
      e.preventDefault();
      saveHtmlFunc();
    }
});

// 点击 下载 事件
dwnhtml.onclick = function() {
    if (!htmlcode.value) {
      return 0;
    }
    const fileName = generateRandomString() + '.html';
    let testBlob;
    if (csscode.value || jscode.value) {
      testBlob = new Blob([`&lt;meta charset="utf-8"&gt;` + `&lt;style&gt;` + csscode.value + `&lt;/style&gt;` + htmlcode.value + `&lt;script&gt;` + jscode.value + `&lt;/sc` + `ript&gt;`], {
            type: 'text/html'
      });
    } else {
      testBlob = new Blob([`&lt;meta charset="utf-8"&gt;` + htmlcode.value], {
            type: 'text/html'
      });
    }
    const downloadLink = document.createElement('a');
    downloadLink.href = URL.createObjectURL(testBlob);
    downloadLink.download = fileName;
    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);
    URL.revokeObjectURL(downloadLink.href);
}

// 点击 清空 事件
delhtml.onclick = function() {
    htmlcode.value = '';
    csscode.value = '';
    jscode.value = '';
    localStorage.removeItem('codeData');
}

// 点击 保存 事件
savehtml.onclick = function() {
    saveHtmlFunc();
}
&lt;/script&gt;
</code></pre>


</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:独元殇 (www.ccgxk.com),转载请注明原文链接:https://www.cnblogs.com/duyuanshang/p/18829312</p><br><br>
来源:https://www.cnblogs.com/duyuanshang/p/18829312
頁: [1]
查看完整版本: 一个神奇的JS代码,让浏览器在新的空白标签页运行我们 HTML 代码(createObjectURL 的妙用)