Skip to content

Instantly share code, notes, and snippets.

@captainbrosset
Created March 11, 2019 16:49
Show Gist options
  • Save captainbrosset/4e6a05637acb0d15cda29bab01173c4e to your computer and use it in GitHub Desktop.
Save captainbrosset/4e6a05637acb0d15cda29bab01173c4e to your computer and use it in GitHub Desktop.
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