How to detect CSS flex wrap event - javascript

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;
})
};

Related

Re: How to target child nodes in HTML collection

I am new to programming and this is my first question. The problem I am having is I am trying to use DOM manipulation on all the child nodes of an html collection. I am expecting the nodes to change background color when they are hovered. Here is what I have tried so far:
let x = 0;
do{
const square = document.createElement("div");
square.className = "squares";
square.setAttribute("id","block");
document.getElementById("container").appendChild(square);
x++;
}
while(x < 16);
var container = document.getElementById("container");
var cells = container.childNodes;
cells.forEach(function(){
cells.onmouseover = function(){
cells.style.backgroundColor = "black";
}
});
console.log(`${cells.length}`);
This doesn't work even though console.log shows 16 child nodes being targeted.
var container = document.getElementById("container");
var cells = container.children[0];
cells.onmouseover = function(){
cells.style.backgroundColor = "black";
}
I have tried this and can use index but of course only that cell will change bg color. I want any cell that is hovered to change also.
I am at a loss for what I am doing wrong here. If anyone can point me in the right direction I would greatly appreciate it.
Welcome to Stack Overflow.
There is an issue in your forEach cycle. Consider the following:
cells.forEach(cell => {
cell.onmouseover = () => {
cell.style.backgroundColor = "black"
}
})
Note that you need to refer to cycle variable instead of the cells array.
Instead of attaching listeners to all the squares you can use event delegation and just have one listener on the container that captures the events from its children as they "bubble up" the DOM.
// Cache the container element, and add a listener to it
const container = document.querySelector('.container');
container.addEventListener('mouseover', handleMouse);
// Create some squares HTML by pushing template
// strings into an array
const html = [];
for (let i = 1; i < 10; i++) {
html.push(`<div class="square">${i}</div>`);
}
// Add that HTML to the container making sure
// we join the array of strings into one string
container.innerHTML = html.join('');
// When a event is fired check that it was
// was from an element with a square class
// and then add an active class to it
function handleMouse(e) {
if (e.target.matches('.square')) {
e.target.classList.add('active');
}
}
.container { display: grid; grid-template-columns: repeat(3, 50px); grid-gap: 0.2em; }
.square { font-size: 1.2em; padding: 0.7em 0.2em; background-color: #565656; color: white; text-align: center; }
.square.active { background-color: thistle; color: black; cursor: pointer; }
<div class="container"></div>
Additional documentation
Template/string literals

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.

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 display items of a particular div on mouseover

I have the div structure
<div id="navigate">
<div class="menu">
<div class="group">Mail</div>
<div class="item">Folders</div>
<div class="item">Messages</div>
</div>
<div class="menu">
<div class="group">Contacts</div>
<div class="item">Friends</div>
<div class="item">Work</div>
</div>
<div class="menu">
<div class="group">Setting</div>
<div class="item">General</div>
<div class="item">Account</div>
</div>
</div>
Right now all items are hidden, and only divs with class 'group' is shown. What I would like to do is if I mouse over a specific menu div, only items of that menu would appear.
Right now I have this code:
function initialise()
{
hideAllItems();
setMouseOvers();
}
function hideAllItems()
{
var nav = document.getElementById("navigate");
var items = nav.getElementsByClassName("item");
for(var i = 0; i < items.length; i++)
{
items[i].style.visibility = "hidden";
items[i].style.display = "none";
}
}
function setMouseOvers()
{
var nav = document.getElementById("navigate");
var menuArr = nav.getElementsByClassName("menu");
for(var x = 0; x < menuArr.length; x++)
{
var itemArrs = menuArr[x].getElementsByClassName("item");
/*var show = function(){ show(itemArrs); };
var hide = function(){ hide(itemArrs); };*/
menuArr[x].onmouseover=function(){ show(itemArrs); };
menuArr[x].onmouseout=function(){ hide(itemArrs); };
}
}
function show(itemArr)
{
for(var i = 0; i < itemArr.length; i++)
{
alert(itemArr[i].innerHTML);
itemArr[i].style.visibility = "visible";
itemArr[i].style.display = "block";
}
}
function hide(itemArr)
{
for(var i = 0; i < itemArr.length; i++)
{
itemArr[i].style.visibility = "hidden";
itemArr[i].style.display = "none";
}
}
And this works, thought it only displays General and Account no matter which menu I hover over. I vaguely understand whats going wrong, but I can't see anyway to fix it. Any ideas? I do not want to change the html structure (e.g. add ids, or create specific classes) if i can help it!
I know that you most probably are looking for a javascript solution, but you could use a simple CSS solution:
.group:hover ~ .item {
display: block;
}
Working Fiddle
But be aware that it is not supported by older IE (< 8) browsers SUPPORT. It depends on your target group if you want to use it.
Why not simply using CSS: DEMO
.menu .item{
display:none;
}
.menu:hover .item{
display:block;
}
As you ask for an JavaScript Only solution (no change in HTML/css) i suggest the following:
The problem is using "itemArrs" in an anonymous function, as only the latest written "itemArrs" is used for all of them, use "this" instead.
for example:
...
groups[x].onmouseover=function(){ show(this); };
...
and
function show(item) {
var items = item.parentNode.getElementsByClassName("item");
...
A complete JS-only solution that works can be found here:
http://jsfiddle.net/Wn4d4/3/

Hiding overflow on body breaks CSS transitions

I am making a type of modal using with the CSS3 transition property, and would like to prevent body from scrolling when the modal is opened. When Javascript adds a "noscroll" class to body, all of the CSS transitions cease to work.
Here's the javascript:
var blurPage = function() {
$("body").addClass("noscroll"); //Add the noscroll class to body
//This part just adds classes to elements within body
var a = $('article.full').add('section.halfwrap');
for (var i = 0; i <= a.length; i++) {
if ($(a).hasClass('full')) {
a.addClass('shadow');
}
var b = $(a[i]).children();
for (var j = 0; j <= b.length; j++) {
$(b[j]).addClass('blur');
}
}
$('#post-main').addClass('active');
}
Here's the CSS:
body {
background: #F7F4F2;
}
body.noscroll {
overflow-y: hidden;
}
If I remove the overflow-y: hidden property from body.noscroll, the animations work just fine.
I also encounter the same problem if, instead of using a CSS class, I just do:
$('body').css("overflow-y", "hidden");

Categories

Resources