Retrieve CSS percentage value with jQuery - javascript

Currently the width() method and all of it's variations in jQuery return pixel values. The same happens when calling the css('width') method.
I have elements, which are styled in .css files, and I have no way of knowing if they're styled in percentages or pixels, but in case it's in percentage or no width is explicitly set on the element, I need to get a percent value.
For example if I have the following:
.seventy { width: 70%; }
.pixels { width: 350px; }
<div class="seventy"></div>
<div class="pixels"></div>
<div class="regular"></div>
I would need these results.
$('.seventy').method() //=> '70%'
$('.pixels').method() //=> '350px'
$('.regular').method() //=> '100%' since that's how block elements behave
Is there anything in jQuery I can use to achieve this effect? Or a custom approach to it?

You can parse the document.stylesheets to find a match. Note, this will only return the actual style after the browser has parsed it so is of no use for getting raw unadulterated CSS as written in file. For that you'd need to parse the file itself rather than the document.stylesheets.
This code is old and untested so your mileage may vary. I have no idea how well it performs with inherited values or more complicated selectors.
//TEST PARSE CSS
var CSS = function () {
var _sheet;
var _rules;
function CSS() {
_sheet = document.styleSheets[0];
if (_sheet.rules) {
_rules = _sheet.rules; // IE
} else {
_rules = _sheet.cssRules; // Standards
}
this.find = function (selector) {
var i = _rules.length;
while(i--){
if (_rules[i].selectorText == selector) {
break;
}
if(i==0){break;}
}
//return _rules[i].cssText;
return _rules[i].style;
}
this.set = function (foo) {
//to do
}
};
return new CSS();
};
//init
var css = new CSS();
//view the console.
console.log(css.find(".regular"));//Note how the width property is blank
//update elements with the results
document.querySelector(".seventy").innerHTML = css.find(".seventy").width;
document.querySelector(".pixels").innerHTML = css.find(".pixels").width;
document.querySelector(".regular").innerHTML = css.find(".regular").width;
//other tests
document.getElementById("a").innerHTML = css.find("body").color;
document.getElementById("b").innerHTML = css.find("h1").color;
document.getElementById("c").innerHTML = css.find("h1").width;
document.getElementById("d").innerHTML = css.find(".notInDom").color;
body {
font-family:sans-serif;
color:black;
background-color:#cccccc;
}
h1 {
color:blue;
font-size:1.5em;
font-weight:400;
width:70%;
}
.seventy, .pixels, .regular {display:block; border:1px solid red;}
.seventy {display:block; border:1px solid red; width: 70%; }
.pixels { width: 350px; }
.regular {}
.notInDom {
color:red;
}
<h1>Find and Read Style Attributes Directly from the Stylesheet.</h1>
<div class="seventy"></div>
<div class="pixels"></div>
<div class="regular"></div>
<ul>
<li>css.find("body").color = <span id='a'></span></li>
<li>css.find("h1").color = <span id='b'></span></li>
<li>css.find("h1").width = <span id='c'></span></li>
<li>css.find(".notInDom").color = <span id='d'></span></li>
</ul>
<p>This is a work in progress and hasn't been tested in any meaningful way. Its messy and very limited.</p>

function getStyle(className) {
var classes = document.styleSheets[0].rules || document.styleSheets[0].cssRules;
for (var x = 0; x < classes.length; x++) {
if (classes[x].selectorText == className) {
(classes[x].cssText) ? alert(classes[x].cssText) : alert(classes[x].style.cssText);
}
}
}
getStyle('.test');

Related

How do you get/set non root CSS variables from JavaScript

Lots of answers on how to get/set "root" CSS variables but none that I've found on how to get/set NON root CSS variables.
NOTE: This is NOT answer: https://stackoverflow.com/a/36088890/104380 That answer only handles root variables and or variables on elements, not on classes
Also: These are not answers (from: https://www.google.com/search?q=get%2Fset+css+variables). Checked the first page of links. All of them only handle root variables. I'm trying to deal with non-root variables.
.foo {
--fg-color: red;
}
.bar {
--fg-color: blue;
}
div {
color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="bar">foo</div>
How do you get someFunctionToGetCSSVariable('.foo', '--fg-color') (yes, I made that up). The point is I want to get/set the --fg-color variable on the .foo CSS rule. Whatever the solution is, getting the value it should return 'red' or '#FF0000') and how do you set it to something else?
I think what OP tries to do is not to change the all elements of that certain class, but to change the class itself, there is a difference. What you need (if I understood well) is actually cssRules change on the certain class. I've created a quick (and dirty) snippet of how to search the cssRules and change the CSS properties in it (including custom one):
<html>
<head>
<style>
.foo {
--fg-color: red;
}
</style>
</head>
<body>
<div class="foo">lorem ipsum</div>
<script>
window.onload = function() {
rules = document.styleSheets[0].cssRules
for (var r in rules) {
if (rules[r].selectorText == ".foo") {
rules[r].style.setProperty('--fg-color', 'blue');
alert('hi');
break;
}
}
};
</script>
</body>
</html>
You could make use of the fact that we have cascading style sheets.
It depends on exactly what your structure is of course, (e.g. are there style elements buried in body?).
For the simple case you could add another stylesheet onto the bottom of the head element.
<!doctype html>
<html>
<head>
<style>
.foo {
--fg-color: red;
}
.bar {
--fg-color: blue;
}
div {
color: var(--fg-color);
}
</style>
</head>
<body>
<div class="foo">foo</div>
<div class="bar">foo</div>
<button>Click me</button>
<script>
const button = document.querySelector('button');
button.addEventListener('click', function () {
const head = document.querySelector('head');
const style = document.createElement('style');
style.innerHTML = '.foo { --fg-color: lime; }';
head.appendChild(style);
button.style.display = 'none';
});
</script>
</body>
</html>
Of course, it could get messy if this happens more than once. You'd want to remember that you'd added a style sheet and get rid of it before adding another one, or alter it in situ, depending on exactly what the situation is. And if there is styling scattered in the body you'll have to add the stylesheet to the bottom of that.
Looks like I have to iterate through all the stylesheets and find the rule. Here's one try.
function getCSSRuleBySelector(selector) {
for (let s = 0; s < document.styleSheets.length; ++s) {
const rules = document.styleSheets[s].cssRules;
for (let i = 0; i < rules.length; ++i) {
const r = rules[i];
if (r.selectorText === selector) {
return r;
}
}
}
}
function setCSSVariableBySelector(selector, varName, value) {
const rule = getCSSRuleBySelector(selector);
rule.style.setProperty(varName, value);
}
function getCSSVariableBySelector(selector, varName) {
const rule = getCSSRuleBySelector(selector);
return rule.style.getPropertyValue(varName);
}
console.log('was:', getCSSVariableBySelector('.foo', '--fg-color'));
setCSSVariableBySelector('.foo', '--fg-color', 'green');
.foo {
--fg-color: red;
}
.bar {
--fg-color: blue;
}
div {
color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="foo">foo</div>
<div class="foo">foo</div>
<div class="bar">bar</div>
<div class="bar">bar</div>
You can use document's styleSheets property to access the CSS rules of the linked .css file, and perform some operations on it to get the value of the custom css variable.
To set a value of custom CSS variable (of the class itself), you can create a new style element, and add a rule for it.
function getStyle(selector, prop) {
let rules = getRule(selector).cssText;
return rules.split(prop + ":")[1].replace(";", "").replace("}", "").trim();
}
function setStyle(selector, prop, val) {
let style = document.querySelector("style.customStyle");
if(style === null){
style = document.createElement("style");
style.className = "customStyle";
style.innerHTML = `${selector}{${prop}:${val}}`;
document.head.appendChild(style);
} else{
style.innerHTML += `${selector}{${prop}:${val}}`;
}
}
function getRule(selector) {
let rulesObj = document.styleSheets[0];
let classes = rulesObj.rules || rulesObj.cssRules;
classes = Object.values(classes)
let rules = classes.filter(c => c.selectorText === selector)[0];
return rules;
}
console.log(getStyle(".foo", "--fg-color"));
console.log(getStyle(".bar", "--fg-color"));
document.querySelector("button").onclick = ()=>{
setStyle(".bar", "--fg-color", "green");
}
.foo {
--fg-color: red;
}
.bar {
--fg-color: blue;
}
div {
color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="bar">foo</div>
<div><button>Click</button></div>
Get
Iterate all styles
Styles can be injected via external CSS file, a <style> tag (on the page) or style attribute (irrelevant to you).
const results = [];
const SELECTOR = '.foo';
const PROP = '--bar';
[...document.styleSheets].forEach(sheet => {
[...sheet?.cssRules].forEach(rule => {
// you should probably use regex to find PROP (so it won't match `--bar-x` for example)
if( rule.cssText.includes(SELECTOR) && rule.cssText.includes(PROP) ){
results.push(rule.cssText)
}
})
});
console.log(results)
.foo { --bar: red }
Now you need to parse the result and extract the property value.
You did not specify in your question if CSS selectors' specificity should be regarded, which makes things a lot more complex.
There might also be selectors which implicitly applies style properties to elements. Examples of different specificities:
/* assuming '.foo' is a div child of <body> */
.foo { --x:1 }
body .foo { --x:2 }
body > div { --x:3 }
body * { --x:4 }
div { --x:5 }
Set
Regarding setting, you need to dynamically add a rule or a style tag:
document.head.insertAdjacentHTML("beforeend", `<style id="whatever">--fg-color: red;</style>`)
And when you want to update it, just overwrite the #whatever <style> with innerHTML.
To set the value you can use document.querySelectorAll and the setProperty method on the style of each object:
function setCssVariable(selector, variable, value) {
document.querySelectorAll(selector).forEach(function (element) {
element.style.setProperty(variable, value);
});
}
setCssVariable(".foo", "--fg-color", "black");
And to get the value, assuming all elements of the class have the same value, you can use a similar approach using document.querySelector and the getProperty method on style of the element:
function getCssVariable(selector, variable) {
const element = document.querySelector(selector);
if (element !== null) {
return element.style.getPropertyValue(variable);
}
}
getCssVariable(".foo", "--fg-color");
You can read the variable value from the getComputedStyle method of JS and set the
variable value with style attribute.
let foo = document.querySelector('.foo');
/*You can get the value of variable property from the element where you used the variable*/
//console.log(getComputedStyle(foo).getPropertyValue('--fg-color'));
/*You can set the variable value like this for every element where used the variable and want to choose at runtime with js */
//foo.style.cssText = '--fg-color:black;';
function setCSSProperty(cls, prop, val){
let els = document.querySelectorAll(cls);
if(els){
els.forEach(el => {
el.style.cssText += `${prop}:${val};`
})
}
}
function getCSSPropertyData(cls, prop){
let els = document.querySelectorAll(cls);
let data = [];
if(els){
els.forEach((el,ind) => {
let cs = getComputedStyle(el);
data[ind] = cs.getPropertyValue(prop).trim();
});
}
return data;
}
console.log(getCSSPropertyData('.foo', '--fg-color'));
setCSSProperty('.foo', '--fg-color', 'green');
.foo {
--fg-color: red;
}
.bar {
--fg-color: blue;
}
div {
color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="bar">foo</div>

JavaScript: Select multiple elements by z-index number (in dynamic DOM)

I would like to select ALL elements in DOM that have the z-index = 2147483647 using 100% JavaScript (NO jQuery)
The DOM is constantly dynamically changing; adding and removing
elements. The code to remove elements by z-index ### MUST have a DOM event listener
I've tried so many iterations of similar codes without success. This is my last iteration attempt and for some reason it is not working
window.addEventListener('change', function() {
var varElements = document.querySelectorAll("[style='z-index: 2147483647']");
if(varElements) { for(let varElement of varElements) { varElement.remove(); } }
} //function
}) //window.
check below code
const check = () => {
var varElements = document.querySelectorAll("*");
for(let varElement of varElements) {
if(varElement.style['z-index'] == 10) {
var node = document.createElement("LI");
var textnode = document.createTextNode(varElement.className);
node.appendChild(textnode);
document.getElementById('list').appendChild(node)
}
}
}
window.addEventListener('change', check )
window.addEventListener('load', check);
<div class="top" style="z-index:10">
<div class="inner1" style="display:'block';z-index:10">
<div class="inner2" style="z-index:10">
</div>
</div>
<div class="inner3" style="z-index:12">
</div>
</div>
<ul id="list">
</ul>
There are some things that come into play here i.e. it has to be positioned to get a z-index. Here I show some examples and how to find stuff that has a z-index not "auto";
You can then loop the list to find a z-index you desire. Here, I just pushed all elements with a z-index not "auto" but you could use your selected index value to filter those out in the conditional for example if (!isNaN(zIndex) && zIndex != "auto" && zIndex == 4042) for those with 4042 value;
Once you have your elements, you can do what you desire which is to set an event handler on each of them.
This specifically answers the question of finding the elements by z-index, not the ultimate desire which is another question of how to manage the changes to the DOM and adding/removing on mutation.
var getZIndex = function(checkelement) {
let compStyles = window.getComputedStyle(checkelement);
let z = compStyles.getPropertyValue('z-index');
if (typeof z == "object" || (isNaN(z) && checkelement.parentNode != document.body)) {
return getZIndex(checkelement.parentNode);
} else {
return z;
}
};
let evallist = document.querySelectorAll("div");
let zthings = [];
for (let item of evallist) {
let zIndex = getZIndex(item);
if (!isNaN(zIndex) && zIndex != "auto") {
zthings.push(item);
}
}
console.log(zthings);
.sureThing {
z-index: 4242;
position: absolute;
background: gold;
top: 4em;
}
<div class="mything">Howddy</div>
<div class="sureThing">Here I am</div>
<div class="onelink">https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle</div>
<div class="otherthing" style="z-index:4040;">other thing set internal woops Ihave no position so I am not in list</div>
<div class="otherthing" style="z-index:4040;position: absolute;top:5em;">other thing set internal OK</div>
You cannot select an element based on css in css. So you cannot use the querySelectorAll. This code works if the css is set by the inline style attribute. Here is the code explained:
Get all element using *.
Turn the NodeList into an array.
Filter out the elements that do not have a specific css property.
get the css properties using: window.getComputedStyle()
window.addEventListener( 'load', () => {
let all = document.querySelectorAll('*');
all = Array.from(all);
const filtered = all.filter( zindex_filter )
console.log( filtered )
})
function zindex_filter (element) {
const style = window.getComputedStyle(element);
console.log( style.getPropertyValue('z-index') )
if( style.getPropertyValue('z-index') == 100 ) return true;
else return false;
}
.div {
width: 100px;
height: 100px;
margin: 10px;
}
.zindex {
position: relative;
z-index: 100;
}
<div class='zindex div'></div>
<div class='div'></div>
<div class='div' style='position: relative; z-index: 100; width: 100px;'></div>
Notes:
window.getComputedStyle() docs
note that the z-indexed must be positioned correctly to return a value other than auto.

How get all computed css properties of element and its children in Javascript

I'm trying to set up a service in python using pdfKit to create a pdf file from html files.
So basically I will send my element as string and expect the server to return a pdf version of it, but to be an accurate representation I also need to send a css file of the element.
How can I do this? Generate JSON / object with only the relevant style properties and selectors of an element and all its children. Respecting hierarchy and no duplicates. There are similar questions but they are outdated and tend to not consider children elements.
I was thinking maybe there is a way to create a new DOM from this element and then get the root css?
Here is something I came up with, basically pass the element you want to extract the styles of and ones of its children, and it will return you the stylesheet as a string. Open your console before running the snippet and you will see the output from the console.log.
Because I wanted to support the extraction of every element even those without a selector, I had to replace each element id by a unique uuid specifically generated for them in order to facilitate the styling of your output. The problem with this approach is in case you are using ids for styling or for user interaction, you are going to loose such functionality on concerned elements after calling extractCSS.
However, it is pretty trivial to use the oldId I'm passing to change back once your pdfKit process finished the generation. Simply call swapBackIds passing the elements returned by the function. You can see the difference of behavior if you uncomment the call in my snippet: the #root pink background would disappear because the styling targets an element id.
All in all, you need to:
Call extractCSS with the element you want to extract
Generate your pdf using res.stylesheet
Call swapBackIds with res.elements
// Generate an unique id for your element
// From https://stackoverflow.com/a/2117523/2054072
function uuidv4 () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Flatten an array
// https://stackoverflow.com/a/15030117/2054072
function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
function recursiveExtract (element) {
var id = uuidv4()
var oldId = element.id
var computed = window.getComputedStyle(element)
var style = computed.cssText
// Now that we get the style, we can swap the id
element.setAttribute('id', id)
// The children are not a real array but a NodeList, we need to convert them
// so we can map over them easily
var children = Array.prototype.slice.call(element.children)
return [{ id: id, style: style, oldId: oldId }].concat(children.map(recursiveExtract))
}
function extractCSS (element) {
if (!element) { return { elements: [], stylesheet: '' } }
var raw = recursiveExtract(element)
var flat = flatten(raw)
return {
elements: flat,
stylesheet: flat.reduce(function (acc, cur) {
var style = '#' + cur.id + ' {\n' + cur.style + '\n}\n\n'
return acc + style
}, '')
}
}
var pdfElement = document.querySelector('#root')
var res = extractCSS(pdfElement)
console.log(res.stylesheet)
function swapBackIds (elements) {
elements.forEach(function (e) {
var element = document.getElementById(e.id)
element.setAttribute('id', e.oldId)
})
}
swapBackIds(res.elements)
#root {
background-color: pink;
}
.style-from-class {
background-color: red;
width: 200px;
height: 200px;
}
.style-from-id {
background-color: green;
width: 100px;
height: 100px;
}
<div id="root">
<span>normal</span>
<span style="background: blue">inline</span>
<div class="style-from-class">
style-class
</div>
<div class="style-from-id">
style-id
<div style="font-size: 10px">a very nested</div>
<div style="font-size: 12px; color: white">and another</div>
</div>
</div>
<div id="ignored-sibling">
</div>
let para = document.querySelector('p');
let compStyles = window.getComputedStyle(para);
para.textContent = 'My computed font-size is ' + compStyles.getPropertyValue('font-size') + ',\nand my computed background is ' + compStyles.getPropertyValue('background') + '.';
p {
width: 400px;
margin: 0 auto;
padding: 20px;
font: 2rem/2 sans-serif;
text-align: center;
background: purple;
color: white;
}
<p>Hello</p>
you can use getComputedStyle method to get computed value of style property

Know when flex-box puts item to new row [duplicate]

I have flex container with items inside. How to detect flex wrap event? I want to apply some new css to elements that have been wrapped. I suppose that it is impossible to detect wrap event by pure css. But it would be very powerful feature! I can try to "catch" this break point event by media query when element wraps into new line/row. But this is a terrible approach. I can try to detect it by script, but it's also not very good.
I am very surprised, but simple $("#element").resize() doesn't work to detect height or width changes of flex container to apply appropriate css to child elements. LOL.
I have found that only this example of jquery code works
jquery event listen on position changed
But still terribly.
Here's one potential solution. There might be other gotchas and edge cases you need to check for.
The basic idea is to loop through the flex items and test their top position against the previous sibling. If the top value is greater (hence further down the page) then the item has wrapped.
The function detectWrap returns an array of DOM elements that have wrapped, and could be used to style as desired.
The function could ideally be used with a ResizeObserver (while using window's resize event as a fallback) as a trigger to check for wrapping as the window is resized or as elements in the page change due to scripts and other user-interaction. Because the StackOverflow code window doesn't resize it won't work here.
Here's a CodePen that works with a screen resize.
var detectWrap = function(className) {
var wrappedItems = [];
var prevItem = {};
var currItem = {};
var items = document.getElementsByClassName(className);
for (var i = 0; i < items.length; i++) {
currItem = items[i].getBoundingClientRect();
if (prevItem && prevItem.top < currItem.top) {
wrappedItems.push(items[i]);
}
prevItem = currItem;
};
return wrappedItems;
}
window.onload = function(event){
var wrappedItems = detectWrap('item');
for (var k = 0; k < wrappedItems.length; k++) {
wrappedItems[k].className = "wrapped";
}
};
div {
display: flex;
flex-wrap: wrap;
}
div > div {
flex-grow: 1;
flex-shrink: 1;
justify-content: center;
background-color: #222222;
padding: 20px 0px;
color: #FFFFFF;
font-family: Arial;
min-width: 300px;
}
div.wrapped {
background-color: red;
}
<div>
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
</div>
Little bit improved snippet on jQuery for this purpose.
wrapped();
$(window).resize(function() {
wrapped();
});
function wrapped() {
var offset_top_prev;
$('.flex-item').each(function() {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped');
} else if (offset_top == offset_top_prev) {
$(this).removeClass('wrapped');
}
offset_top_prev = offset_top;
});
}
I've modified sansSpoon's code to work even if the element isn't at the absolute top of the page. Codepen: https://codepen.io/tropix126/pen/poEwpVd
function detectWrap(node) {
for (const container of node) {
for (const child of container.children) {
if (child.offsetTop > container.offsetTop) {
child.classList.add("wrapped");
} else {
child.classList.remove("wrapped");
}
}
}
}
Note that margin-top shouldn't be applied to items since it's factored into getBoundingClientRect and will trigger the wrapped class to apply on all items.
I'm using a similar approach in determining if a <li> has been wrapped in an <ul> that has it's display set to flex.
ul = document.querySelectorAll('.list');
function wrapped(ul) {
// loops over all found lists on the page - HTML Collection
for (var i=0; i<ul.length; i++) {
//Children gets all the list items as another HTML Collection
li = ul[i].children;
for (var j=0; j<li.length; j++) {
// offsetTop will get the vertical distance of the li from the ul.
// if > 0 it has been wrapped.
loc = li[j].offsetTop;
if (loc > 0) {
li[j].className = 'wrapped';
} else {
li[j].className = 'unwrapped';
}
}
}
}
I noticed elements will typically wrap in relation to the first element. Comparing offset top of each element to the first element is a simpler approach. This works for wrap and wrap-reverse. (Probably won't work if elements use flex order)
var wrappers = $('.flex[class*="flex-wrap"]'); //select flex wrap and wrap-reverse elements
if (wrappers.length) { //don't add listener if no flex elements
$(window)
.on('resize', function() {
wrappers.each(function() {
var prnt = $(this),
chldrn = prnt.children(':not(:first-child)'), //select flex items
frst = prnt.children().first();
chldrn.each(function(i, e) { $(e).toggleClass('flex-wrapped', $(e).offset().top != frst.offset().top); }); //element has wrapped
prnt.toggleClass('flex-wrapping', !!prnt.find('.flex-wrapped').length); //wrapping has started
frst.toggleClass('flex-wrapped', !!!chldrn.filter(':not(.flex-wrapped)').length); //all are wrapped
});
})
.trigger('resize'); //lazy way to initially call the above
}
.flex {
display: flex;
}
.flex.flex-wrap {
flex-wrap: wrap;
}
.flex.flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.flex.flex-1 > * { /*make items equal width*/
flex: 1;
}
.flex > * {
flex-grow: 1;
}
.cc-min-width-200 > * { /*child combinator*/
min-width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="flex flex-1 flex-wrap-reverse cc-min-width-200">
<div>Hello</div>
<div>There</div>
<div>World</div>
</div>
If someone wants to find the last element of the row from where wrapped elements started can use the below logic. It's applicable for multiple lines as well
window.onresize = function (event) {
const elements = document.querySelectorAll('.borrower-detail');
let previousElement = {};
let rowTop = elements[0].getBoundingClientRect().top;
elements.forEach(el => el.classList.remove('last-el-of-row'))
elements.forEach(el => {
const elementTop = el.getBoundingClientRect().top;
if (rowTop < elementTop) {
previousElement.classList.add('last-el-of-row');
rowTop = elementTop;
}
previousElement = el;
})
};

How to detect CSS flex wrap event

I have flex container with items inside. How to detect flex wrap event? I want to apply some new css to elements that have been wrapped. I suppose that it is impossible to detect wrap event by pure css. But it would be very powerful feature! I can try to "catch" this break point event by media query when element wraps into new line/row. But this is a terrible approach. I can try to detect it by script, but it's also not very good.
I am very surprised, but simple $("#element").resize() doesn't work to detect height or width changes of flex container to apply appropriate css to child elements. LOL.
I have found that only this example of jquery code works
jquery event listen on position changed
But still terribly.
Here's one potential solution. There might be other gotchas and edge cases you need to check for.
The basic idea is to loop through the flex items and test their top position against the previous sibling. If the top value is greater (hence further down the page) then the item has wrapped.
The function detectWrap returns an array of DOM elements that have wrapped, and could be used to style as desired.
The function could ideally be used with a ResizeObserver (while using window's resize event as a fallback) as a trigger to check for wrapping as the window is resized or as elements in the page change due to scripts and other user-interaction. Because the StackOverflow code window doesn't resize it won't work here.
Here's a CodePen that works with a screen resize.
var detectWrap = function(className) {
var wrappedItems = [];
var prevItem = {};
var currItem = {};
var items = document.getElementsByClassName(className);
for (var i = 0; i < items.length; i++) {
currItem = items[i].getBoundingClientRect();
if (prevItem && prevItem.top < currItem.top) {
wrappedItems.push(items[i]);
}
prevItem = currItem;
};
return wrappedItems;
}
window.onload = function(event){
var wrappedItems = detectWrap('item');
for (var k = 0; k < wrappedItems.length; k++) {
wrappedItems[k].className = "wrapped";
}
};
div {
display: flex;
flex-wrap: wrap;
}
div > div {
flex-grow: 1;
flex-shrink: 1;
justify-content: center;
background-color: #222222;
padding: 20px 0px;
color: #FFFFFF;
font-family: Arial;
min-width: 300px;
}
div.wrapped {
background-color: red;
}
<div>
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
</div>
Little bit improved snippet on jQuery for this purpose.
wrapped();
$(window).resize(function() {
wrapped();
});
function wrapped() {
var offset_top_prev;
$('.flex-item').each(function() {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped');
} else if (offset_top == offset_top_prev) {
$(this).removeClass('wrapped');
}
offset_top_prev = offset_top;
});
}
I've modified sansSpoon's code to work even if the element isn't at the absolute top of the page. Codepen: https://codepen.io/tropix126/pen/poEwpVd
function detectWrap(node) {
for (const container of node) {
for (const child of container.children) {
if (child.offsetTop > container.offsetTop) {
child.classList.add("wrapped");
} else {
child.classList.remove("wrapped");
}
}
}
}
Note that margin-top shouldn't be applied to items since it's factored into getBoundingClientRect and will trigger the wrapped class to apply on all items.
I'm using a similar approach in determining if a <li> has been wrapped in an <ul> that has it's display set to flex.
ul = document.querySelectorAll('.list');
function wrapped(ul) {
// loops over all found lists on the page - HTML Collection
for (var i=0; i<ul.length; i++) {
//Children gets all the list items as another HTML Collection
li = ul[i].children;
for (var j=0; j<li.length; j++) {
// offsetTop will get the vertical distance of the li from the ul.
// if > 0 it has been wrapped.
loc = li[j].offsetTop;
if (loc > 0) {
li[j].className = 'wrapped';
} else {
li[j].className = 'unwrapped';
}
}
}
}
I noticed elements will typically wrap in relation to the first element. Comparing offset top of each element to the first element is a simpler approach. This works for wrap and wrap-reverse. (Probably won't work if elements use flex order)
var wrappers = $('.flex[class*="flex-wrap"]'); //select flex wrap and wrap-reverse elements
if (wrappers.length) { //don't add listener if no flex elements
$(window)
.on('resize', function() {
wrappers.each(function() {
var prnt = $(this),
chldrn = prnt.children(':not(:first-child)'), //select flex items
frst = prnt.children().first();
chldrn.each(function(i, e) { $(e).toggleClass('flex-wrapped', $(e).offset().top != frst.offset().top); }); //element has wrapped
prnt.toggleClass('flex-wrapping', !!prnt.find('.flex-wrapped').length); //wrapping has started
frst.toggleClass('flex-wrapped', !!!chldrn.filter(':not(.flex-wrapped)').length); //all are wrapped
});
})
.trigger('resize'); //lazy way to initially call the above
}
.flex {
display: flex;
}
.flex.flex-wrap {
flex-wrap: wrap;
}
.flex.flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.flex.flex-1 > * { /*make items equal width*/
flex: 1;
}
.flex > * {
flex-grow: 1;
}
.cc-min-width-200 > * { /*child combinator*/
min-width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="flex flex-1 flex-wrap-reverse cc-min-width-200">
<div>Hello</div>
<div>There</div>
<div>World</div>
</div>
If someone wants to find the last element of the row from where wrapped elements started can use the below logic. It's applicable for multiple lines as well
window.onresize = function (event) {
const elements = document.querySelectorAll('.borrower-detail');
let previousElement = {};
let rowTop = elements[0].getBoundingClientRect().top;
elements.forEach(el => el.classList.remove('last-el-of-row'))
elements.forEach(el => {
const elementTop = el.getBoundingClientRect().top;
if (rowTop < elementTop) {
previousElement.classList.add('last-el-of-row');
rowTop = elementTop;
}
previousElement = el;
})
};

Categories

Resources