Skip to content

Instantly share code, notes, and snippets.

@kurtextrem
Last active January 19, 2026 12:35
Show Gist options
  • Select an option

  • Save kurtextrem/038aa36e504485381a78e518dfc6a600 to your computer and use it in GitHub Desktop.

Select an option

Save kurtextrem/038aa36e504485381a78e518dfc6a600 to your computer and use it in GitHub Desktop.
/**
* DevTools snippet – find potential stutter sources
* Run in the Chrome DevTools console on any page
* Flags:
* 1. Elements > 1000×1000 px that have any transform-* or opacity set
* 2. Elements that have any filter applied
* If at least one of each category exists → we have a match.
* Matching elements get a red 3 px outline and a console table.
*/
(() => {
const TRANSFORM_PROPS = [
'transform','transformOrigin','transformStyle','perspective','backfaceVisibility',
'translate','rotate','scale','opacity','willChange'
];
const FILTER_PROPS = ['filter'];
const MAYBE_HEAVY_PROPS = ['backdropFilter', 'boxShadow'];
const CRITICAL_FILTERS = ["contrast", "blur"];
const IGNORE_EL = ["html", "body", "div#main"];
const DEFAULTS = {
transform: 'none',
transformOrigin: '50% 50%',
transformStyle: 'flat',
perspective: 'none',
backfaceVisibility: 'visible',
translate: 'none',
rotate: 'none',
scale: 'none',
opacity: '1',
willChange: 'auto',
filter: 'none',
backdropFilter: 'none',
boxShadow: 'none'
};
/* ---------- helpers ---------- */
const style = el => window.getComputedStyle(el);
const pickProps = (obj, keys) => {
const out = {};
keys.forEach(k => {
const v = obj[k];
if (!v) return;
const defaultVal = DEFAULTS[k];
if (v === defaultVal) return;
if (v === 'none' || v === 'normal') return;
out[k] = v;
});
return out;
};
const hasAny = o => Object.keys(o).length > 0;
/* ---------- filter-value checker ---------- */
const filterRegex = /[a-z-]+\([^)]+\)/gi;
const splitRegex = /\(|\)/;
const isMeaningfulFilter = val => {
if (!val || val === 'none') return false;
if (val.includes('var(--')) return true;
const parts = val.match(filterRegex) || [];
return parts.some(p => {
const [fn, rawVal] = p.split(splitRegex).filter(Boolean);
const v = parseFloat(rawVal);
switch (fn.trim()) {
case 'brightness':
case 'contrast':
case 'saturate':
case 'sepia':
case 'grayscale': return v !== 1 && v !== 100;
case 'invert': return v !== 0;
case 'opacity': return v !== 1 && v !== 100;
case 'blur': return v !== 0;
case 'hue-rotate': return v !== 0;
case 'drop-shadow': return true;
default: return true;
}
});
};
/* ---------- scan ---------- */
const bigTransforms = [];
const withFilters = [];
const maybeHeavy = [];
document.querySelectorAll('*').forEach(el => {
if (IGNORE_EL.includes(`${el.tagName.toLowerCase()}${el.id?'#'+el.id:''}`)) return;
const r = el.getBoundingClientRect();
const s = style(el);
if (r.width > 1000 && r.height > 1000) {
const tProps = pickProps(s, TRANSFORM_PROPS);
if (hasAny(tProps)) bigTransforms.push({el, r, props: tProps});
}
const fProps = pickProps(s, FILTER_PROPS);
Object.keys(fProps).forEach(k => {
if (!isMeaningfulFilter(fProps[k])) delete fProps[k];
});
if (hasAny(fProps)) withFilters.push({el, r, props: fProps});
const mProps = pickProps(s, MAYBE_HEAVY_PROPS);
if (hasAny(mProps)) maybeHeavy.push({el, r, props: mProps});
});
/* ---------- outline helpers ---------- */
const OUTLINE = '3px solid red';
const KEY = '__stutterOutline';
const highlight = arr => arr.forEach(({el}) => {
el[KEY] = el.style.outline;
el.style.outline = OUTLINE;
});
const unhighlight = arr => arr.forEach(({el}) => {
el.style.outline = el[KEY] || '';
delete el[KEY];
});
/* ---------- report ---------- */
if (bigTransforms.length && withFilters.length) {
console.group(`%c⚠️ Match found! Marked in red are elements that are larger than the viewport.`, 'color:orange;font-weight:bold;');
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
console.groupCollapsed(`Big-transform elements (${bigTransforms.length})`);
bigTransforms.sort((a,b) => b.r.width - a.r.width).forEach(({el, r, props}) => {
console.groupCollapsed(
`%c${el.tagName.toLowerCase()}${el.id?'#'+el.id:''}${el.className?'.'+el.className.split(' ').join('.'):''} ${el.dataset.framerName ? el.dataset.framerName+") " : ''}| %c${r.width}x${r.height}`,
'font-weight:bold',
r.width > viewportWidth || r.height > viewportHeight ? 'color:red' : ''
);
console.log('Node →', el);
console.log('Rect →', r.width, 'x', r.height, r);
console.log('Props →', props);
console.groupEnd();
});
console.groupEnd();
console.groupCollapsed(`Elements with filter (${withFilters.length})`);
withFilters.sort((a,b) => a.props.filter.localeCompare(b.props.filter)).forEach(({el, props}) => {
console.groupCollapsed(
`%c${el.tagName.toLowerCase()}${el.id?'#'+el.id:''}${el.className?'.'+el.className.split(' ').join('.'):''} | %c${props.filter}`,
'font-weight:bold',
CRITICAL_FILTERS.some(f => props.filter.includes(f)) ? 'color:red' : ''
);
console.log('Node →', el);
console.log('Props →', props);
console.groupEnd();
});
console.groupEnd();
console.groupCollapsed(`Elements with maybe heavy props (${maybeHeavy.length})`);
const groupedByProp = {};
maybeHeavy.forEach(({el, props}) => {
Object.keys(props).forEach(prop => {
if (!groupedByProp[prop]) groupedByProp[prop] = [];
groupedByProp[prop].push({el, props});
});
});
Object.keys(groupedByProp).sort().forEach(prop => {
const elements = groupedByProp[prop];
console.groupCollapsed(`${prop} (${elements.length})`);
elements.forEach(({el, props}) => {
console.groupCollapsed(
`%c${el.tagName.toLowerCase()}${el.id?'#'+el.id:''}${el.className?'.'+el.className.split(' ').join('.'):''} | %c${props.backdropFilter}`,
'font-weight:bold',
CRITICAL_FILTERS.some(f => props.backdropFilter.includes(f)) ? 'color:red' : ''
);
console.log('Node →', el);
console.log('Props →', props);
console.groupEnd();
});
console.groupEnd();
});
console.groupEnd();
highlight([...bigTransforms, ...withFilters, ...maybeHeavy]);
window.unhighlightStutter = () => {
unhighlight([...bigTransforms, ...withFilters, ...maybeHeavy]);
console.log('Outlines removed.');
};
console.log('%cRun `unhighlightStutter()` to remove outlines.','color:steelblue');
} else {
console.log('%c✅ No stutter candidates found.','color:green;font-weight:bold');
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment