Last active
December 9, 2025 07:40
-
-
Save patricknelson/337d121eb7f523df62e79f0c9f1ad0cf to your computer and use it in GitHub Desktop.
HTML and style extractor: Inline styles to HTML for portability and printability
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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