Created
March 11, 2019 16:49
-
-
Save captainbrosset/4e6a05637acb0d15cda29bab01173c4e 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
function findRulesMatching(el) { | |
const rules = []; | |
for (const { cssRules } of document.styleSheets) { | |
for (const rule of cssRules) { | |
if (el.matches(rule.selectorText)) { | |
rules.push(rule); | |
} | |
} | |
} | |
return rules; | |
} | |
function isNodeInOverflowArea(root, size, candidate, dir) { | |
// Bail if the candidate isn't a node or is not rendered. | |
const quads = !candidate.getBoxQuads ? [] : candidate.getBoxQuads({ relativeTo: root }); | |
if (!quads.length) { | |
return false; | |
} | |
// Bail out if the candidate isn't displayed (at least partly) inside | |
// the root's scrollable overflow area. | |
const candidatePoints = quads[0]; | |
const isOutOfHArea = candidatePoints.p1.x < 0 || candidatePoints.p2.x > size.width; | |
const isOutOfVArea = candidatePoints.p1.y < 0 || candidatePoints.p4.y > size.height; | |
if (dir === "H" && !isOutOfHArea) { | |
return false; | |
} | |
if (dir === "V" && !isOutOfVArea) { | |
return false; | |
} | |
if (!dir && !isOutOfHArea && !isOutOfVArea) { | |
return false; | |
} | |
return true; | |
} | |
function findNodesAndRulesThatCauseOverflow(root, scrollDelta, rulesAndNodes, dir) { | |
const overflowCausingRules = new Map(); | |
for (const [rule, nodes] of rulesAndNodes) { | |
// Set display:contents in the rule so it doesn't have any effect on the | |
// layout anymore, while everything else still does. | |
const oldDisplayType = rule.style.display; | |
rule.style.display = "contents"; | |
// Measure how much the root element scrolls now. | |
// This will cause a sync reflow. | |
const newScrollDelta = { | |
H: root.scrollWidth - root.clientWidth, | |
V: root.scrollHeight - root.clientHeight | |
}; | |
// Put the rule like it was before. | |
rule.style.display = oldDisplayType; | |
if (!!dir && newScrollDelta[dir] !== scrollDelta[dir]) { | |
overflowCausingRules.set(rule, nodes); | |
} | |
if (!dir && (newScrollDelta.H !== scrollDelta.H || newScrollDelta.V !== scrollDelta.V)) { | |
overflowCausingRules.set(rule, nodes); | |
} | |
} | |
return overflowCausingRules; | |
} | |
function findRulesAndNodesThatCauseOverflow(root, dir) { | |
// Get all elements in root | |
const descendants = root.querySelectorAll("body *"); | |
// Remember how much the root element scrolls now, and its size. | |
// This causes a sync reflow, and we don't need to do it each time, just once now. | |
const quads = !root.getBoxQuads ? [] : root.getBoxQuads(); | |
if (!quads.length) { | |
return []; | |
} | |
const size = { | |
width: quads[0].getBounds().width, | |
height: quads[0].getBounds().height | |
}; | |
const scrollDelta = { | |
H: root.scrollWidth - root.clientWidth, | |
V: root.scrollHeight - root.clientHeight | |
}; | |
// Find all nodes that potentially cause the overflow. | |
const candidates = [...descendants].filter(candidate => { | |
return isNodeInOverflowArea(root, size, candidate, dir); | |
}); | |
// Find all rules for those nodes. | |
const rules = new Map(); | |
for (const candidate of candidates) { | |
for (const rule of findRulesMatching(candidate)) { | |
if (!rules.has(rule)) { | |
rules.set(rule, [candidate]); | |
} else { | |
rules.set(rule, [...rules.get(rule), candidate]); | |
} | |
} | |
} | |
// Find the rules in there that do have an effect on the overflow. | |
return findNodesAndRulesThatCauseOverflow(root, scrollDelta, rules, dir); | |
} | |
var scrollableRoot = document.querySelector("#bugzilla-body"); | |
var dir = "H"; | |
console.log(findRulesAndNodesThatCauseOverflow(scrollableRoot, dir)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment