一個神奇的JS代碼,讓瀏覽器在新的空白標簽頁運行我們 HTML 代碼(createObjectURL 的妙用)
前言
目前,網上很多在線運行 HTML 的頁面,大都是這樣的邏輯:
上面一個代碼框 <textera> ,下面一個 <iframe>,然后通過 js,將我們的代碼框中的 HTML 給輸入到 <iframe> 里面,這便是一個簡單的在線運行 html 的邏輯。
甚至我們可以在一行里寫一個在線運行 html 的頁面。比如下面這個,一個高度精簡的簡短的 HTML JS CSS代碼:
<body oninput="i.srcdoc=h.value+'<style>'+c.value+'</style><script>'+j.value+'</script>'"><style>textarea,iframe{width:100%;height:50%}body{margin:0}textarea{width:33.33%;font-size:18}</style><textarea placeholder=HTML id=h></textarea><textarea placeholder=CSS id=c></textarea><textarea placeholder=JS id=j></textarea><iframe id=i>
如果想運行,很簡單,將下面這個代碼直接粘貼到瀏覽器框里,并回車即可:
data:text/html,<body oninput="i.srcdoc=h.value+'<style>'+c.value+'</style><script>'+j.value+'</script>'"><style>textarea,iframe{width:100%;height:50%}body{margin:0}textarea{width:33.33%;font-size:18}</style><textarea placeholder=HTML id=h></textarea><textarea placeholder=CSS id=c></textarea><textarea placeholder=JS id=j></textarea><iframe id=i>
效果如下

上面三個框分別是 HTML CSS JS 代碼,直接通過 i.srcdoc=h.value+'<style>'+c.value+'</style><script>'+j.value+'</script> 這樣一行 js 便賦予給了 <iframe>,然后就能在線運行我們的 前端代碼 了。
原理就是這樣,無論是菜鳥教程里的代碼運行框,還是大名鼎鼎的 codepen.io 這種代碼分享庫,其原理都差不多。

看,長的一模一樣。
問題
但這個時候,就出現了一個問題。
我們有可能,不喜歡在一個頁面里夾雜著另一個頁面,感覺太悶了,如果 HTML 代碼是在一個新的頁面運行里就好了。其實實現不難,就是有點費資源。
漸漸我,我就忘了這個問題。
一個偶然
去年,在改進我制作的一個在某 CMS 平臺運行的圖片壓縮插件的時候,我忘了是從哪里復制過來的一個代碼,讓我這個插件有了這樣一個好功能 : 單擊某個按鈕可以在新的標簽頁預覽一個圖片。至于原程序這里放不下,但我們可以這樣體驗一下,在 F12 瀏覽器 JS 控制臺輸入下面的代碼:
const imgurl = 'https://assets.cnblogs.com/logo.svg'; // 博客園的 logo 地址
const imgTempBlob = new Blob(['<img style="max-width:100%" src="'+ imgurl +'">'], {type: 'text/html'});
const imgBlobObjurl = window.URL.createObjectURL(imgTempBlob);
window.open(imgBlobObjurl, '_blank');
URL.revokeObjectURL(imgBlobObjurl); // 如果不添加這一行,那么那個地址會一直有效,直到瀏覽器自己清除
然后瀏覽器會馬上打開一個新頁面,然后將 博客園 的 logo 給展示了出來!如圖所示:

我當時被震驚了。
在過去,我對 js 的二進制的理解只有 ArrayBuffer base64 Blob 三者,且這三者是可以互相轉化的。直到今天,我才知道在 JS 二進制 世界里竟然還有一個 createObjectURL 這樣一個方法。
createObjectURL 可以把內存里的一個東西,比如一個字符串、一個圖像二進制,等,轉換成一個 URL,這樣你就可以使用 DOM 渲染出來。比如你加載了一張圖片,然后修改了一些內容,接下來要渲染到網頁上,就會用到這個函數方法。
當然,我們也要注意,每次調用 createObjectURL() 時,都會創建一個新的對象 URL,即使已經為同一個對象創建了一個 URL。當不再需要這些對象時,必須通過調用 URL.revokeObjectURL() 來釋放它們,瀏覽器會在卸載文檔時自動釋放對象 URL;然而,為了優化性能和內存使用,如果在安全時間內可以明確卸載,就應該卸載。createObjectURL() 創建的 URL 會占用內存,如果不手動釋放,可能會導致內存泄漏。
這個真是好用,而且用在圖片的預覽上真的太恰當了!為什么很少見到有人使用它呢?或者說我幾乎沒在別的地方見過 xxx.com/c533df96-d49e-49af-9a8c-bdbab35b7baf 類似的地址呢?可能是會造成性能上的不妥吧。
我后來發覺到里面的 HTML 代碼,我感覺它可以再復雜一點。
它可以不預覽圖片,它可以作為 HTML 在線編輯器的最徹底的預覽!
預覽 HTML 代碼
我們可以寫一個簡單的文本框,然后寫一個按鈕,讓按鈕在單擊后,在新的頁面預覽我們的代碼運行效果。代碼如下:
<textarea id="htmlcode" placeholder="在此輸入 HTML 代碼"></textarea><br>
<button id="runcode">在新窗口預覽</button>
<script>
runcode.onclick = function() {
const codeBlob = new Blob([`<meta charset="utf-8">` + htmlcode.value], {type: 'text/html'});
const codeTempUrl = window.URL.createObjectURL(codeBlob);
window.open(codeTempUrl, '_blank');
URL.revokeObjectURL(codeTempUrl);
}
</script>
運行效果如下:

單擊按鈕后,

很不錯。然后我們就可以根據我們自身的需要,為其添加這樣的功能!
- 在寫代碼的時候,添加保存快捷鍵,讓其保存到我們的瀏覽器的
localStorage里,防止丟失代碼 - 設置一個保存按鈕
- 把 CSS 和 JS 也搞里頭,單獨設置兩個框
- 一鍵清除記錄
- 簡單的把界面給美化一下
- 可以將當前的頁面轉化為 HTML 文件下載
然后我就搞成了這個樣子!
大家可以單擊這個地址,查看運行效果! https://www.ccgxk.com/528.html 我的站點上的一個頁面將其嵌入其中了。

我感覺真的很實用。我在使用 Gemini 這種對前端輸出比較厲害的 AI 的時候,它會給我輸出一大堆的 HTML ,有時候還會分 CSS 和 JS 輸出,我使用這個頁面來輔助測試的話還不錯,比類似于 codepen 那種頁面好用很多。
下面是目前的全部代碼,可能還有很多地方要改進的,我之后在使用過程中會進行迭代,并實時更新到我的站點上的那個頁面上。
<style>
@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;}
</style>
<div style="width: 600px;max-width: 100%;">
<textarea placeholder="在此處輸入 HTML 代碼,單擊下面的【運行】,瀏覽器會新建一個空白標簽頁運行預覽..." name="code" id="htmlcode" class="code-textarea"></textarea><br>
<button class="code-action-btn" id="runhtml">運行</button>
<button class="code-action-btn" id="dwnhtml" style="margin-right: 10px;">下載</button>
<button class="code-action-btn" id="delhtml" style="margin-right: 10px;">清空</button>
<button class="code-action-btn" id="savehtml" style="margin-right: 10px;">保存</button>
<details>
<summary style="cursor: pointer;background-color: cornsilk;width: fit-content;">CSS + JS
<div id="res_info" style=" display: inline; margin-left: 10px; background-color: white; padding-left: 10px; "></div>
</summary>
CSS <br>
<textarea class="code-textarea" name="code" id="csscode"></textarea><br>
JS <br>
<textarea class="code-textarea" name="code" id="jscode"></textarea>
</details>
</div>
<script>
// 生成隨機字符串
function generateRandomString(length = 8) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < 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([`<meta charset="utf-8">` + `<style>` + csscode.value + `</style>` + htmlcode.value + `<script>` + jscode.value + `</sc` + `ript>`], {
type: 'text/html'
});
} else {
testBlob = new Blob([`<meta charset="utf-8">` + 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 = "<span style='animation: fadeOut 3s forwards; opacity: 1;'>已暫存</span>";
}
// 按下保存快捷鍵后
document.addEventListener('keydown', function(e) { // 保存快捷鍵
if (e.keyCode == 83 && (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([`<meta charset="utf-8">` + `<style>` + csscode.value + `</style>` + htmlcode.value + `<script>` + jscode.value + `</sc` + `ript>`], {
type: 'text/html'
});
} else {
testBlob = new Blob([`<meta charset="utf-8">` + 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();
}
</script>
本文來自博客園,作者:獨元殤,轉載請注明原文鏈接:http://www.rzrgm.cn/duyuanshang/p/18829312

浙公網安備 33010602011771號