Skip to content

Instantly share code, notes, and snippets.

@patricknelson
Last active December 9, 2025 07:40
Show Gist options
  • Select an option

  • Save patricknelson/337d121eb7f523df62e79f0c9f1ad0cf to your computer and use it in GitHub Desktop.

Select an option

Save patricknelson/337d121eb7f523df62e79f0c9f1ad0cf to your computer and use it in GitHub Desktop.
HTML and style extractor: Inline styles to HTML for portability and printability
/**
* Copies the HTML at the selector and injects inline styles to preserve appearance.
* Really great for making things more portable or printable when they otherwise wouldn't be.
*
* WARNING: This is very inefficient and should be used for reference purposes only.
*
* TODO: Have it always open in a new tab and the in that tab offer the option for the user to copy the element HTML or save it (via Blob API).
*/
function htmlExtractor(target, openInNewTab = false) {
// Resolve selector string OR DOM element
const original =
typeof target === "string"
? document.querySelector(target)
: target instanceof Element
? target
: null;
if (!original) {
throw new Error("No valid element or selector provided.");
}
const clone = original.cloneNode(true);
function inlineStyles(src, dst) {
const computed = window.getComputedStyle(src);
let styleText = "";
for (let i = 0; i < computed.length; i++) {
const prop = computed[i];
styleText += prop + ":" + computed.getPropertyValue(prop) + ";";
}
dst.setAttribute("style", styleText);
const srcChildren = src.children;
const dstChildren = dst.children;
for (let i = 0; i < srcChildren.length; i++) {
inlineStyles(srcChildren[i], dstChildren[i]);
}
}
inlineStyles(original, clone);
const elementHtml = clone.outerHTML;
// If opening in a new tab, construct a minimal document to wrap this element's HTML.
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${document.title}</title>
</head>
<body>
${elementHtml}
</body>
</html>`.trim();
// By default, we'll copy the element's HTML, but if opening in a new tab, copy the wrapping document HTML as well,
// since that makes it much easier to save it separately.
let copyHtml = openInNewTab ? fullHtml : elementHtml;
// --- Copy result to clipboard ---
const tryFallbackCopy = () => {
const ta = document.createElement("textarea");
ta.value = copyHtml;
ta.style.position = "fixed";
ta.style.top = "-9999px";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); } catch (_) {}
document.body.removeChild(ta);
};
if (navigator.clipboard?.writeText) {
navigator.clipboard.writeText(copyHtml).catch(tryFallbackCopy);
} else {
tryFallbackCopy();
}
// --- Optional: Open in new tab ---
if (openInNewTab) {
const w = window.open("", "_blank");
if (w) {
w.document.open();
w.document.write(fullHtml);
w.document.close();
} else {
console.warn("Pop-up blocked; failed to open new tab.");
}
}
return fullHtml;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment