Skip to content

Instantly share code, notes, and snippets.

@danrichman
Last active February 4, 2025 16:11
Show Gist options
  • Select an option

  • Save danrichman/4ecd34247cca12bad1da7ed7a9c77bdf to your computer and use it in GitHub Desktop.

Select an option

Save danrichman/4ecd34247cca12bad1da7ed7a9c77bdf to your computer and use it in GitHub Desktop.
Copy Selected HTML/CSS Bookmarklet
javascript:(function()%7Bconst highlight=document.createElement('div');highlight.style.cssText=%60%0A position: absolute;%0A background: rgba(78, 146, 249, 0.3);%0A border: 2px solid %234e92f9;%0A pointer-events: none;%0A z-index: 999999;%0A transition: all 50ms;%0A box-sizing: border-box;%0A %60;let selectedElement=null;function processCSSRule(rule,elements)%7Btry%7Bif(rule instanceof CSSStyleRule)%7Breturn processStyleRule(rule,elements)%7Dif(rule instanceof CSSMediaRule%7C%7Crule instanceof CSSSupportsRule)%7Breturn processConditionalRule(rule,elements)%7Dif(rule instanceof CSSFontFaceRule)%7Breturn processFontRule(rule)%7Dreturn ''%7Dcatch(error)%7Breturn ''%7D%7Dfunction processStyleRule(rule,elements)%7Bconst selector=rule.selectorText?.replace(/%5Cs*%5C%7B.*/,%27%27)%7C%7C%27%27;if(!selector%7C%7C!elements.some(el=>el.matches(selector)%7C%7Cel.closest(selector)))%7Breturn %27%27%7Dconst styles=Array.from(rule.style).filter(prop=>%7Bconst value=rule.style.getPropertyValue(prop).trim();return value&&!%5B%27none%27,%27normal%27,%27auto%27%5D.includes(value)%7D).map(prop=>%7Bconst value=rule.style.getPropertyValue(prop);const priority=rule.style.getPropertyPriority(prop);return %60$%7B prop %7D: $%7B value %7D$%7Bpriority?%27 !important%27:%27%27%7D%60%7D).join(%27; %27);return styles?%60$%7B selector %7D %7B $%7B styles %7D %7D%60:%27%27%7Dfunction processConditionalRule(rule,elements)%7Bconst condition=rule instanceof CSSMediaRule?%60@media $%7Brule.conditionText %7D%60:%60@supports $%7Brule.conditionText %7D%60;const nestedRules=Array.from(rule.cssRules).map(nestedRule=>processCSSRule(nestedRule,elements)).filter(Boolean).join(%27%5Cn%27);return nestedRules?%60$%7B condition %7D %7B%5Cn$%7B nestedRules %7D%5Cn%7D%60:%27%27%7Dfunction processFontRule(rule)%7Btry%7Bconst fontFamily=rule.style.getPropertyValue(%27font-family%27);return document.fonts.check(%6012px $%7B fontFamily %7D%60)?rule.cssText:%27%27%7Dcatch%7Breturn %27%27%7D%7Dfunction startSelection()%7Bdocument.body.style.cursor=%27crosshair%27;document.body.appendChild(highlight);const mouseMoveHandler=e=>%7Bif(e.target===highlight)%7Breturn%7DselectedElement=e.target;const rect=selectedElement.getBoundingClientRect();Object.assign(highlight.style,%7Bwidth:%60$%7Brect.width %7Dpx%60,height:%60$%7Brect.height %7Dpx%60,top:%60$%7Brect.top+window.scrollY %7Dpx%60,left:%60$%7Brect.left+window.scrollX %7Dpx%60%7D)%7D;const clickHandler=e=>%7Be.preventDefault();e.stopImmediatePropagation();document.body.style.cursor=%27%27;highlight.remove();const elements=%5BselectedElement,...selectedElement.querySelectorAll(%27*%27)%5D;const cssRules=Array.from(document.styleSheets).flatMap(sheet=>%7Btry%7Breturn Array.from(sheet.cssRules%7C%7C%5B%5D)%7Dcatch%7Breturn%5B%5D%7D%7D).map(rule=>processCSSRule(rule,elements)).filter(Boolean);elements.forEach(el=>%7Bif(el.style.cssText)%7Bconst inlineStyles=Array.from(el.style).filter(prop=>el.style.getPropertyValue(prop).trim()).map(prop=>%60$%7B prop %7D: $%7Bel.style.getPropertyValue(prop)%7D%60).join(%27; %27);if(inlineStyles)%7BcssRules.push(%60%5Bdata-inline-$%7Bel.tagName %7D%5D %7B $%7B inlineStyles %7D %7D%60)%7D%7D%7D);const clonedElement=selectedElement.cloneNode(true);clonedElement.querySelectorAll(%27*%27).forEach(el=>%7Bif(el.style.cssText)%7Bel.setAttribute(%60data-inline-$%7Bel.tagName %7D%60,%27%27)%7D%7D);const output=%60$%7BclonedElement.outerHTML %7D%5Cn<style>%5Cn$%7B%5B...new Set(cssRules)%5D.join(%27%5Cn%27)%7D%5Cn</style>%60;navigator.clipboard.writeText(output).then(()=>alert(%27Copied! ✨%27)).catch(()=>alert(%27Copy failed - check console%27));document.removeEventListener(%27mousemove%27,mouseMoveHandler);document.removeEventListener(%27click%27,clickHandler,true)%7D;document.addEventListener(%27mousemove%27,mouseMoveHandler);document.addEventListener(%27click%27,clickHandler,%7Bcapture:true,once:true%7D)%7DstartSelection()%7D)();
javascript:(function() {
// Visual highlight element configuration
const highlight = document.createElement('div');
highlight.style.cssText = `
position: absolute;
background: rgba(78, 146, 249, 0.3);
border: 2px solid #4e92f9;
pointer-events: none;
z-index: 999999;
transition: all 50ms;
box-sizing: border-box;
`;
let selectedElement = null;
// ========================
// CSS PROCESSING FUNCTIONS
// ========================
/**
* Processes CSS rules and returns applicable styles
* @param {CSSRule} rule - CSS rule to process
* @param {Array<Element>} elements - Elements to match against
* @returns {string} Valid CSS text
*/
function processCSSRule(rule, elements) {
try {
// Handle standard style rules
if (rule instanceof CSSStyleRule) {
return processStyleRule(rule, elements);
}
// Handle nested conditional rules
if (rule instanceof CSSMediaRule || rule instanceof CSSSupportsRule) {
return processConditionalRule(rule, elements);
}
// Handle font-face rules
if (rule instanceof CSSFontFaceRule) {
return processFontRule(rule);
}
return '';
} catch (error) {
return '';
}
}
function processStyleRule(rule, elements) {
const selector = rule.selectorText?.replace(/\s*\{.*/, '') || '';
if (!selector || !elements.some(el => el.matches(selector) || el.closest(selector))) return '';
// Filter valid style declarations
const styles = Array.from(rule.style)
.filter(prop => {
const value = rule.style.getPropertyValue(prop).trim();
return value && !['none', 'normal', 'auto'].includes(value);
})
.map(prop => {
const value = rule.style.getPropertyValue(prop);
const priority = rule.style.getPropertyPriority(prop);
return `${prop}: ${value}${priority ? ' !important' : ''}`;
})
.join('; ');
return styles ? `${selector} { ${styles} }` : '';
}
function processConditionalRule(rule, elements) {
const condition = rule instanceof CSSMediaRule
? `@media ${rule.conditionText}`
: `@supports ${rule.conditionText}`;
const nestedRules = Array.from(rule.cssRules)
.map(nestedRule => processCSSRule(nestedRule, elements))
.filter(Boolean)
.join('\n');
return nestedRules ? `${condition} {\n${nestedRules}\n}` : '';
}
function processFontRule(rule) {
try {
const fontFamily = rule.style.getPropertyValue('font-family');
return document.fonts.check(`12px ${fontFamily}`) ? rule.cssText : '';
} catch {
return '';
}
}
// =====================
// CORE FUNCTIONALITY
// =====================
function startSelection() {
document.body.style.cursor = 'crosshair';
document.body.appendChild(highlight);
// Highlight positioning handler
const mouseMoveHandler = e => {
if (e.target === highlight) return;
selectedElement = e.target;
const rect = selectedElement.getBoundingClientRect();
Object.assign(highlight.style, {
width: `${rect.width}px`,
height: `${rect.height}px`,
top: `${rect.top + window.scrollY}px`,
left: `${rect.left + window.scrollX}px`
});
};
// Click handler (main logic)
const clickHandler = e => {
e.preventDefault();
e.stopImmediatePropagation();
// Cleanup UI
document.body.style.cursor = '';
highlight.remove();
// Get target element and its children
const elements = [selectedElement, ...selectedElement.querySelectorAll('*')];
// Process all CSS rules
const cssRules = Array.from(document.styleSheets)
.flatMap(sheet => {
try {
return Array.from(sheet.cssRules || []);
} catch {
return [];
}
})
.map(rule => processCSSRule(rule, elements))
.filter(Boolean);
// Add inline styles
elements.forEach(el => {
if (el.style.cssText) {
const inlineStyles = Array.from(el.style)
.filter(prop => el.style.getPropertyValue(prop).trim())
.map(prop => `${prop}: ${el.style.getPropertyValue(prop)}`)
.join('; ');
if (inlineStyles) {
cssRules.push(`[data-inline-${el.tagName}] { ${inlineStyles} }`);
}
}
});
// Clone HTML structure
const clonedElement = selectedElement.cloneNode(true);
clonedElement.querySelectorAll('*').forEach(el => {
if (el.style.cssText) {
el.setAttribute(`data-inline-${el.tagName}`, '');
}
});
// Prepare final output
const output = `${clonedElement.outerHTML}\n<style>\n${[...new Set(cssRules)].join('\n')}\n</style>`;
// Copy to clipboard
navigator.clipboard.writeText(output)
.then(() => alert('Copied! ✨'))
.catch(() => alert('Copy failed - check console'));
// Cleanup event listeners
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('click', clickHandler, true);
};
// Event listeners
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('click', clickHandler, { capture: true, once: true });
}
// Start the selection process
startSelection();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment