How to iterate through DOM elements changing id with Javascript - javascript

I have 36 divs with ids id="square-1", "square-2", etc., framed in a grid 6x6 container. I want them to be all square and be the size of each square of the grid.
I want to make a function to add css to each of them to determine their position in the grid. In my mind I can do something to iterate through them with a for loop and apply "gridColumn = "a/b"", where a and b are relative to the i in the for loop, so that I don't have to specify that 36 times in the css document.
Is this even possible? Does it make sense? Very beginner...
<div class="div-container">
<div id= "square-1"></div>
<div id= "square-2"></div>
<div id= "square-3"></div>
<div id= "square-4"></div>
<div id= "square-5"></div>
<div id= "square-6"></div>
<div id= "square-7"></div>
etc...
</div>

Keeping track of elements by a CSS property is fragile. If you are using JavaScript, then let it do the heavy lifting. For instance, instead of hard coding 36 <div> with ids, make a <div> on each iteration of a loop. In the example below, the container is a <main> and each sub-box is a <section>. Each <section> is given a CSS property of order and a corresponding number and an id: "sq"+ a corresponding number. The order property applies to the order in which a flex item appears within a flex container.
const box = document.querySelector('main');
for (let i = 0; i < 36; i++) {
const sec = document.createElement('section');
sec.style.order = i;
sec.id = `sq${i}`;
box.append(sec);
}
html {
font: 300 5vmin/1 Consolas
}
main {
display: flex;
flex-flow: row wrap;
width: 12rem;
height: 12rem;
border: 1px solid red
}
section {
width: 2rem;
height: 2rem;
outline: 1px dashed blue
}
<main></main>

You could grab the parent element and loop through the child elements:
var element = document.getElementById('parentDiv');
var children = element.children;
for(var i=0; i<children.length; i++){
var child = children[i];
//apply changes
}

So if you just want to iterate thru DOM elements you can do it by iterating thru every child that's div inside .div-container:
const squares = document.querySelectorAll('.div-container div')
for(let square of squares){
square.classList.add('YOUR_CLASS')
}

It seems like you can use a css grid here.
To use this, you simply need to add css to the parent item (here your div-container) and populate with child elements which will be arranged in grid.
If you're beginner in HTML and css, you can generate grid using websites like cssgrid-generator.
Small example
Small example here using a 2*2 grid
.parent {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
grid-column-gap: 10px;
grid-row-gap: 10px;
width: 210px
}
.child {
background: red;
width: 100px;
height: 100px;
}
<div class="parent">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
Note : You can also generate the child elements using a for loop in javascript which will make your code lighter

If you use CSS Grid use a loop to create the markup (I've used template strings pushed into an array) and add that (joined array) as the innerHTML of a container. You can add the array index as the data id attribute, along with a row/column attribute if you want to pinpoint the box's position in the grid.
In this example I've added a function that changes the box background color (an ,active class) when it's clicked on.
function makeGrid(n) {
const boxes = [];
let row = 0;
let column = 1;
for (let i = 1; i < n * n; i++) {
column++;
if (i % 6 === 1) {
row++;
column = 1;
}
const div = `
<div
class="box"
data-id="${i}"
data-row="${row}"
data-column="${column}"
>${i}
</div>`;
boxes.push(div);
}
return boxes.join('');
}
const main = document.querySelector('main');
main.innerHTML = makeGrid(6);
main.addEventListener('click', handleClick);
function handleClick(e) {
if (e.target.matches('.box')) {
e.target.classList.add('active');
}
}
main { display: grid; grid-template-columns: repeat(6, 30px); gap: 0.5em; }
.box { display: flex; flex-direction: column; justify-content: center; align-items: center; width: 30px; height: 30px; border: 1px solid #676767; }
.box:not(.active):hover { background-color: lightblue; cursor: pointer; }
.active { background-color: salmon };
<main></main>
Additional documentation
Event delegation
Template/string literals

Related

How to make the last element of every flex-wrapped row take up the remaining space? [duplicate]

My problem is that I want the flexbox with variable range width, and all works well, but not on the last row. I want the same dimension for all children even where the row is not full of children (the last row).
#products-list {
position:relative;
display: flex;
flex-flow: row wrap;
width:100%;
}
#products-list .product {
min-width:150px;
max-width:250px;
margin:10px 10px 20px 10px;
flex:1;
}
I created a dynamic situation in jsFiddle
My flex divs can shrink until 150px and grow up to 250px, but all must be with the same size (and obviously I want a CSS solution, with JS I know the way).
Unfortunately, in the current iteration of flexbox (Level 1), there is no clean way to solve the last-row alignment problem. It's a common problem.
It would be useful to have a flex property along the lines of:
last-row
last-column
only-child-in-a-row
alone-in-a-column
This problem does appear to be a high priority for Flexbox Level 2:
CSS Working Group Wiki - Specification Issues and Planning
https://lists.w3.org/Archives/Public/www-style/2015Jan/0150.html
Although this behavior is difficult to achieve in flexbox, it's simple and easy in CSS Grid Layout:
Equal width flex items even after they wrap
In case Grid is not an option, here's a list of similar questions containing various flexbox hacks:
Properly sizing and aligning the flex item(s) on the last row
Flex-box: Align last row to grid
Flexbox wrap - different alignment for last row
How can a flex item keep the same dimensions when it is forced to a new row?
Selector for an element alone in a row?
Aligning elements in last flexbox row
How can I allow flex-items to grow while keeping the same size?
Left-align last row of flexbox using space-between and margins
Inconsistent margin between flex items on last row
How to keep wrapped flex-items the same width as the elements on the previous row?
How to align left last row/line in multiple line flexbox
Last children of grid get giant gutter cause of flexbox space-between
Managing justify-content: space-between on last row
Flexbox space between behavior combined with wrap
Possible to use CSS Flexbox to stretch elements on every row while maintaining consistent widths?
As a quick and dirty solution one can use:
.my-flex-child:last-child/*.product:last-child*/ {
flex-grow: 100;/*Or any number big enough*/
}
You could try using grid instead of flexbox here:
#products-list {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(100px, 250px)); //grid automagic
justify-content: start; //start left
}
Fiddle link
There is a great solution that works always.
add a div with class product (The same class for other items that are under flex) and add a style for this div:height:0px;
you need to add as many dives that are possible to be in one row.
<div class="product" style="height:0px">
as many that can be in one row.
That's all. Works always.
If all your rows have the same number of items, you can use :nth-last-child. For example, if all the rows have 3 items, you can do something like this to remove the margin of the last 3 items:
.container{
display: flex;
flex-wrap: wrap;
background: yellow;
}
.item{
width: calc((100% - 2*10px)/3);
height: 50px;
background: blue;
color: white;
margin-right: 10px;
margin-bottom: 10px;
padding: 5px;
box-sizing: border-box;
}
/* last item of each row */
.item:nth-child(3n){
margin-right: 0;
font-size: 150%;
}
/* last 3 items */
.item:nth-last-child(-n+3){
margin-bottom: 0;
background: green;
}
<div class="container">
<div class="item" >1</div>
<div class="item" >2</div>
<div class="item" >3</div>
<div class="item" >4</div>
<div class="item" >5</div>
<div class="item" >6</div>
<div class="item" >7</div>
</div>
A simple trick adds a flexible space to fill the rest of the last row:
#products-list{
display:flex;
flex-flow: row wrap;
justify-content:space-between;
}
#products-list::after {
content: "";
flex: auto;
flex-basis: 200px;/*your item width*/
flex-grow: 0;
}
But you shouldn't use margins on items then. Rather wrap them into containers with padding.
I used this workaround, even if it's not very elegant and it doesn't use the power of Flexbox.
It can be carried out on the following conditions:
All the items have the same width
The items have a fixed width
You use SCSS/SASS (can be avoided though)
If this is the case, you can use the following snippet:
$itemWidth: 400px;
$itemMargin: 10px;
html, body {
margin: 0;
padding: 0;
}
.flex-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 auto;
border: solid 1px blue;
}
#for $i from 1 through 10 {
#media only screen and (min-width: $i * $itemWidth + 2 * $i * $itemMargin) {
.flex-container {
width: $i * $itemWidth + 2 * $i * $itemMargin;
}
}
}
.item {
flex: 0 0 $itemWidth;
height: 100px;
margin: $itemMargin;
background: red;
}
<div class="flex-container">
<div class="item"></div>
<div class="item" style="flex: 500 0 200px"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Here I have created an example on codepen which also implements margin.
The second and the third conditions can be avoided respectively using css variables (if you decided to provide support for it) and compiling the above scss snippet.
Well, it's true, we could do it also before flexbox, but display: flex can be still essential for a responsive design.
I was facing this same issue where I wanted to have a variable number of items in a resizable container.
I wanted to use all of the horizontal space, but have all of the flex items at the same size.
I ultimately came up with a javascript approach that dynamically added padding spacers as the container was resized.
function padLastFormRow() {
let topList = [];
let nSpacersToAdd = 0;
$('#flexContainer').find('.formSpacer').remove();
$('#flexContainer').find('.formItem').each(function(i, formItem) {
topList.push($(formItem).position().top);
});
let allRowLengths = getFlexLineLengths(topList);
let firstRowLength = allRowLengths[0];
let lastRowLength = allRowLengths[((allRowLengths.length) - 1)];
if (lastRowLength < firstRowLength) {
nSpacersToAdd = firstRowLength - lastRowLength ;
}
for (var i = 1; i <= nSpacersToAdd; i ++) {
$('#flexContainer').append(formSpacerItem);
}
}
Please see my Fiddle:
http://jsfiddle.net/Harold_Buchman/z5r3ogye/11/
I was having a similar challenge with menu rows. I wanted more spacing on the top of the second row of menu items.
The use of flex-box's row-gap worked well.
https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap
.menu {
display: flex;
flex-wrap: wrap;
row-gap: 10px;
}
This added a margin-top type effect to menu items were wrapped to the second line.
If all your rows have the same number of items, you can use :nth-last-child. For example, if all the rows have 3 items, you can do something like this:
.container{
display: flex;
flex-wrap: wrap;
background: yellow;
}
.item{
width: calc((100% - 2*10px)/3);
height: 50px;
background: blue;
color: white;
margin-right: 10px;
margin-bottom: 10px;
padding: 5px;
box-sizing: border-box;
}
// last item of each row
.item:nth-child(3n){
margin-right: 0;
background: green;
}
// last 3 items
.item:nth-last-child(-n+3){
margin-bottom: 0;
font-size: 150%;
}
<div class="container">
<div class="item" >1</div>
<div class="item" >2</div>
<div class="item" >3</div>
<div class="item" >4</div>
<div class="item" >5</div>
<div class="item" >6</div>
<div class="item" >7</div>
</div>

css scroll-snap: focusing on the element which got snapped to

I implemented a horizontal grid with some cards in them. The grid uses CSS scroll-snap and it works nicely when navigating with mouse/touchscreen.
The problem occurs when the grid is navigated using a keyboard. Pressing tab after navigating through the grid with arrow keys causes the view to jump back to the element that got the focus, not the card which is current snapped to.
My ideal behaviour when pressing tab is, to focus on the card which is currently snapped to.
Any suggestions to make this possible?
As far as I can tell there is currently no way to handle this natively. Nils Schwebel's answer is not going to be very elegant, but it looks like the best way to go.
Here's a working example:
Note: I've added quite a bit of pure decoration to make it easier to understand, so you may need to pick out the relevant parts after some testing.
const main = document.getElementById("Main"),
sections = document.getElementsByClassName("section");
main.addEventListener('scroll', (e) => {
// Grab the position yo are scrolled to (the top of the viewport)
let pos = main.scrollTop;
for (let i = 0, l = sections.length; i < l; i++) {
// Since our stap-align is centered, get the position of the middle of the viewport relative to the current section's top (if your snap items are not full-height, it might require using half the viewport's height instead)
let relativePos = sections[i].offsetTop - pos + (sections[i].offsetHeight / 2);
// Check if the point we found falls within the section
if (relativePos >= 0 && relativePos < sections[i].offsetHeight) {
sections[i].focus();
break;
}
}
});
body {
margin: unset;
}
main {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
width: 100%;
height: 100vh;
background-color: #222;
overflow-y: auto;
scroll-snap-type: y mandatory;
}
section {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
scroll-snap-align: center;
}
section:focus {
border: none;
outline: none;
}
#s1 {
background-color: #d72748;
}
#s2 {
background-color: #b51f7e;
}
#s3 {
background-color: #e64869;
}
#s4 {
background-color: #e79946;
}
section h2 {
color: white;
}
<main id="Main">
<section class="section" id="s1" tabindex="1" aria-labelledby="a1">
<h2 id="a1">AREA 1</h2>
</section>
<section class="section" id="s2" tabindex="1" aria-labelledby="a3">
<h2 id="a2">AREA 2</h2>
</section>
<section class="section" id="s3" tabindex="1" aria-labelledby="a2">
<h2 id="a3">AREA 3</h2>
</section>
<section class="section" id="s4" tabindex="1" aria-labelledby="a4">
<h2 id="a4">AREA 4</h2>
</section>
</main>
I would add a scroll listener and just check if the element is at the top of the scroll view. You may be able to modify one of these solutions: How to check if element is visible after scrolling?

How can I change color of more than one div of the same id using addEventListener("click")? [duplicate]

This question already has answers here:
JavaScript and getElementById for multiple elements with the same ID
(13 answers)
Closed 1 year ago.
Good day,
I have a CSS grid of 9 square divs, and I would like to add a click event for all of them so that their color changes from limegreen to black, and then changes back to limegreen when the mouse leaves. I am able to do so if I give each div a unique ID and use .addEventListener, but then the issue is I have to write a click event for each div. When I try to give each div the same ID and use .addEventListener, the click event only happens with the first div.
I have spent the past hour or two searching Stackoverflow, Google, forums, and other websites, along with tinkering with my code based on what I've found, but I can't find anything that has helped so far.
Here is my code, but I've only included the HTML/CSS for the first two divs, since the rest of the divs are like the 2nd div and don't respond to clicks:
const dude = document.getElementById("dude");
dude.addEventListener("click", function(){
dude.style.backgroundColor = "black";
});
dude.addEventListener("mouseleave", function(){
dude.style.backgroundColor = "limegreen";
})
.container {
display: grid;
margin: 7rem;
position: relative;
grid-template-columns: auto auto auto;
align-items: center;
justify-content: center;
column-gap: 2.5rem;
row-gap: 2.5rem;
}
.box {
background: limegreen;
width: 10rem;
height: 10rem;
position: relative;
}
.box2 {
background: limegreen;
width: 10rem;
aspect-ratio: 1/1;
position: relative;
}
<div class="container">
<div class="box" id="dude"></div>
<div class="box2" id="dude"></div>
</div>
Thank you very much for your help!
In HTML, two or more elements cannot have the same ID.
In your HTML, add a common class to the div(s) inside of .container.
<div class="container">
<div class="box gridbox"></div>
<div class="box2 gridbox"></div>
</div>
Now use this Javascript code:
/**
* Use this because we're getting the elements with
* their class, not id. This method returns an array
* of the elements with matching class.
*/
const dudes = document.getElementsByClassName("gridbox");
/** Loop over the whole array */
for(let dude of dudes){
/** Add click event handler */
dude.addEventListener("click", () => {
dude.style.backgroundColor = "black";
});
/** Add mouseleave event handler */
dude.addEventListener("mouseleave", () => {
dude.style.backgroundColor = "limegreen";
});
}
This should work fine.

How do I make the center element move to the line above when lacking space?

I have three elements (divs for simplicity's sake). How do I make it so that, when the space is narrowed, the center element pops up to the line above the other two? I'm using media queries right now, but the center content can change width, so it breaks.
For example with:
<div class="container">
<div class="Q">QWERTYUIOP</div>
<div class="A">ASDFGHJKL0</div>
<div class="Z">ZXCVBNM123</div>
</div>
When there is extra space it looks like this
QWERTYUIOP ASDFGHJKL0 ZXCVBNM123
and when there is limited space it looks like this
ASDFGHJKL0
QWERTYUIOP ZXCVBNM123
You can achieve this behavior using media queries. So if you want to force the last 2 elements to next line when screen size is less than 560px, your css and html should be like this:
.container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.nl {
display: none;
width:100%;
}
#media only screen and (max-width:560px){
.A {
order: 1;
flex: 0 0 100%;
text-align: center;
}
.Q {
order: 2;
}
.Z {
order: 3;
}
}
<div class="container">
<div class="Q">QWERTYUIOP</div>
<div class="A">ASDFGHJKL0</div>
<div class="Z">ZXCVBNM123</div>
</div>
I don't know if the author is allowed to use javascript?! ...but I don't know any other solution.
The css was not shown, so I made my own rules for the main .container. Flex rules are used. Also, I don't know if the class has a .container specific width ?!
In my example, I used the window resize event to visualize how my code works:
window.onresize = function() { ... }
but in production it needs to be replaced with a window load event:
window.onload = function() { ... }
If this example does not suit the author, then I think that this example will be useful to others :)
window.onresize = function() {
let container = document.querySelector('.container');
let Q_div = document.querySelector('.Q');
let A_div = document.querySelector('.A');
let Z_div = document.querySelector('.Z');
let sum_div = Q_div.offsetWidth + A_div.offsetWidth + Z_div.offsetWidth;
if (container.offsetWidth <= sum_div) {
A_div.classList.add('class_for_A');
} else {
A_div.classList.remove('class_for_A');
}
}
.container {
display: flex;
flex-wrap: wrap-reverse;
justify-content: space-between;
width: 500px;
max-width: 100%;
}
.A.class_for_A {
order: 1;
margin: auto;
}
<div class="container">
<div class="Q">QWERTYUIOP</div>
<div class="A">ASDFGHJKL0</div>
<div class="Z">ZXCVBNM123</div>
</div>

Creating a Pyramid using CSS and JS

I have a wrapper div and many content blocks. The content block can be of any number.
<div class="wrapper">
<div class="content-block">Something goes here</div>
<div class="content-block">Something goes here</div>
.
.
.
<div class="content-block">Something goes here</div>
</div>
I wish to form a pyramid structure using these content-blocks as it appears below:
Is it possible to achieve pyramid like this? The above image is just an example, there can be more than 10 content-blocks or even less.
Check out this very simple JavaScript/CSS solution:
var objContainer = document.getElementById("container"),
intLevels = 10,
strBlocksHTML = '';
// Using innerHTML is faster than DOM appendChild
for (var i = 0; i < intLevels; i++) {
for (var n = 0; n < i + 1; n++) {
strBlocksHTML += '<div class="buildingBlock"></div>';
}
strBlocksHTML += '<div></div>'; // Line break after each row
}
objContainer.innerHTML = strBlocksHTML;
.buildingBlock {
display: inline-block;
width: 20px;
height: 20px;
margin: 2px 5px;
background-color: #eee;
border: 2px solid #ccc;
}
#container {
text-align: center;
}
<div id="container"></div>
Yes, it is perfectly possible, but hard to write down without more precise requirements. Number of divs would obviously equal number of elements = 10. Length of bottom row = (10/2 - 1) with each next row to top taking one less element, etc. Either use absolute positioning in div style or treat table as matrix and draw with cells. Table solution will be progressively slower with more rows, because all the empty "pixels" and quadratically increasing overhead on recalculating cell sizes and positions in browser.
Hm, not a trivial task. I don't think it is possible to write (finite) CSS for any number of elements. It would need something like this:
#wrapper {
text-align: center;
}
.content-block {
display: inline-block;
width: 5em;
height: 4em;
margin: 0 2.5em;
}
.content-block:nth-child(n*(n+1)/2)::after {
display: block; /* linebreak */
}
Where the nth-child-selector would contain a triangular number, but it must have the form an+b.

Categories