Skip to content

Instantly share code, notes, and snippets.

@ggorlen
Last active April 17, 2026 21:50
Show Gist options
  • Select an option

  • Save ggorlen/54d729f6022430183512a268efe8d2c0 to your computer and use it in GitHub Desktop.

Select an option

Save ggorlen/54d729f6022430183512a268efe8d2c0 to your computer and use it in GitHub Desktop.
No JS HTML previewer
<!DOCTYPE html>
<html lang="en" class="light dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HTML Script Stripper</title>
<style>
:root { color-scheme: light dark; }
body {
margin: 0;
font-family: system-ui, sans-serif;
}
iframe {
width: 100vw;
height: 100vh;
border: none;
}
#overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
#dropZone {
border: 2px dashed currentColor;
border-radius: 12px;
padding: 32px 48px;
backdrop-filter: blur(6px);
background: rgba(128,128,128,0.1);
pointer-events: all;
transition: 0.2s ease;
}
#dropZone.hidden {
opacity: 0;
transform: scale(0.95);
pointer-events: none;
}
#dropZone.dragover {
transform: scale(1.05);
}
</style>
</head>
<body>
<iframe id="preview"></iframe>
<div id="overlay">
<div id="dropZone">
Drop HTML file or paste to show without scripts
</div>
</div>
<script>
const dropZone = document.getElementById('dropZone');
const preview = document.getElementById('preview');
const showOverlay = () => dropZone.classList.remove('hidden');
const hideOverlay = () => dropZone.classList.add('hidden');
function sanitizeHTML(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
// remove dangerous nodes
doc.querySelectorAll(
"script, iframe, object, embed, base, meta[http-equiv]"
).forEach(el => el.remove());
// strip dangerous attributes
const walker = document.createTreeWalker(
doc,
NodeFilter.SHOW_ELEMENT,
null
);
while (walker.nextNode()) {
const el = walker.currentNode;
[...el.attributes].forEach(attr => {
const name = attr.name.toLowerCase();
const value = attr.value.trim().toLowerCase();
if (name.startsWith("on")) {
el.removeAttribute(attr.name);
return;
}
if (
(name === "href" || name === "src" || name === "xlink:href") &&
value.startsWith("javascript:")
) {
el.removeAttribute(attr.name);
return;
}
if (name === "srcdoc") {
el.removeAttribute(attr.name);
}
});
}
return "<!DOCTYPE html>\n" + doc.documentElement.outerHTML;
}
function loadHTML(html) {
const clean = sanitizeHTML(html);
const blob = new Blob([clean], { type: "text/html" });
preview.src = URL.createObjectURL(blob);
hideOverlay();
}
// drag drop
window.addEventListener("dragover", e => {
e.preventDefault();
showOverlay();
dropZone.classList.add("dragover");
});
window.addEventListener("dragleave", () => {
dropZone.classList.remove("dragover");
});
window.addEventListener("drop", e => {
e.preventDefault();
dropZone.classList.remove("dragover");
const file = e.dataTransfer.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => loadHTML(e.target.result);
reader.readAsText(file);
});
// paste anywhere
window.addEventListener("paste", e => {
const html =
e.clipboardData.getData("text/html") ||
e.clipboardData.getData("text/plain");
if (html && html.trim().startsWith("<")) {
e.preventDefault();
loadHTML(html);
}
});
window.addEventListener("dragenter", showOverlay);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment