Created
August 5, 2025 13:22
-
-
Save kurtextrem/038aa36e504485381a78e518dfc6a600 to your computer and use it in GitHub Desktop.
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
/** | |
* 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-like props set | |
* 2. Elements that have any filter/backdrop-filter applied | |
*/ | |
;(() => { | |
const TRANSFORM_PROPS = [ | |
"transform", | |
"transformOrigin", | |
"transformStyle", | |
"perspective", | |
"backfaceVisibility", | |
"translate", | |
"rotate", | |
"scale", | |
"opacity", | |
"willChange", | |
] | |
const FILTER_PROPS = ["filter", "backdropFilter"] | |
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", | |
} | |
const style = (el, pseudo = null) => window.getComputedStyle(el, pseudo) | |
const pickProps = (obj, keys) => { | |
const out = {} | |
keys.forEach(k => { | |
const v = obj[k] | |
if (!v) return | |
const defaultVal = DEFAULTS[k] | |
if (v === defaultVal || v === "none" || v === "normal") return | |
out[k] = v | |
}) | |
return out | |
} | |
const hasAny = o => Object.keys(o).length > 0 | |
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 | |
} | |
}) | |
} | |
function getIdentifier(el, pseudo = null) { | |
return `${el.tagName.toLowerCase()}${el.id ? "#" + el.id : ""}${typeof el.className === "string" && el.className !== "" ? "." + el.className.split(" ").join(".") : ""}${pseudo || ""}` | |
} | |
const bigTransforms = [] | |
const withFilters = [] | |
const scanElement = (el, pseudo = null) => { | |
const identifier = getIdentifier(el, pseudo) | |
if (!pseudo && IGNORE_EL.includes(identifier)) return | |
const r = el.getBoundingClientRect() | |
const s = style(el, pseudo) | |
if (r.width > 1000 && r.height > 1000) { | |
const tProps = pickProps(s, TRANSFORM_PROPS) | |
if (hasAny(tProps)) bigTransforms.push({ el, r, props: tProps, pseudo }) | |
} | |
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, pseudo }) | |
} | |
document.querySelectorAll("*").forEach(el => { | |
try { | |
scanElement(el) | |
scanElement(el, "::before") | |
scanElement(el, "::after") | |
} catch (e) { | |
console.error(e, el) | |
} | |
}) | |
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] | |
}) | |
if (bigTransforms.length || withFilters.length) { | |
console.group( | |
`%c⚠️ Match found! Marked in red are elements that are larger than the viewport width.`, | |
"color:orange;font-weight:bold;" | |
) | |
const viewportWidth = window.innerWidth | |
console.groupCollapsed(`Big-transform elements (${bigTransforms.length})`) | |
bigTransforms | |
.sort((a, b) => b.r.width - a.r.width) | |
.forEach(({ el, r, props, pseudo }) => { | |
console.groupCollapsed( | |
`%c${getIdentifier(el, pseudo)} ${el.dataset.framerName ? "('" + el.dataset.framerName + "') " : ""}| %c${r.width}x${r.height}`, | |
"font-weight:bold", | |
r.width > viewportWidth ? "color:red" : "" | |
) | |
console.log("Node →", el) | |
console.log("Pseudo →", pseudo || "(none)") | |
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, pseudo }) => { | |
console.groupCollapsed( | |
`%c${getIdentifier(el, pseudo)} ${el.dataset.framerName ? "('" + el.dataset.framerName + "') " : ""}| %c${props.filter || props.backdropFilter}`, | |
"font-weight:bold", | |
CRITICAL_FILTERS.some(f => (props.filter || "").includes(f)) ? "color:red" : "" | |
) | |
console.log("Node →", el) | |
console.log("Pseudo →", pseudo || "(none)") | |
console.log("Props →", props) | |
console.groupEnd() | |
}) | |
console.groupEnd() | |
console.groupEnd() | |
highlight([...bigTransforms, ...withFilters]) | |
window.unhighlightStutter = () => { | |
unhighlight([...bigTransforms, ...withFilters]) | |
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