I've been having this issue with a transition with a ReactJS accordion. Without seeing the problem I created this snippet in vanilla and happens the seame!
const acs = document.querySelectorAll('.accordion');
acs.forEach((a, i) => {
a.addEventListener('click', () => {
acs.forEach((aa, ii) => {
aa.classList.toggle('expanded', i === ii);
})
})
})
.accordion {
padding: 0.5em;
margin-bottom: 1em;
background: #f3f3f3;
}
.accordion p:last-child {
overflow: hidden;
margin: 0;
max-height: 0;
transition: 1s linear max-height;
}
.accordion.expanded p:last-child {
max-height: 100px;
}
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
Why transitions won't happen simultaneously? there's not setTimeout or transition-delay
Seems like I'm rusty with css transitions
-EDIT-
Noticed that if I apply transition on height instead it does work as expected but containers take more height than they need according to content. How can I achieve it with max-height?
const acs = document.querySelectorAll('.accordion');
acs.forEach((a, i) => {
a.addEventListener('click', () => {
acs.forEach((aa, ii) => {
aa.classList.toggle('expanded', i === ii);
})
})
})
.accordion {
padding: 0.5em;
margin-bottom: 1em;
background: #f3f3f3;
}
.accordion p:last-child {
overflow: hidden;
margin: 0;
height: 0;
transition: 1s linear height;
outline: 1px dashed red;
}
.accordion.expanded p:last-child {
height: 1em;
}
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
I think it is working - i.e. both transitions taking place at the same time - but you don't notice the beginning part of it transitioning down from 100px because there's nothing to show there (or rather, nothing to make not show). The amount showing in your example is not as big as 100px.
This snippet puts the max-height of 1.4em - which is enough to accommodate a line of the text - and you can see the transitions are taking place at the same time.
In your second example of transitioning the actual height of course you start to see the shinkage straightaway.
Here's the snippet with a reduced max-height as a demo:
const acs = document.querySelectorAll('.accordion');
acs.forEach((a, i) => {
a.addEventListener('click', () => {
acs.forEach((aa, ii) => {
aa.classList.toggle('expanded', i === ii);
})
})
})
.accordion {
padding: 0.5em;
margin-bottom: 1em;
background: #f3f3f3;
}
.accordion p:last-child {
overflow: hidden;
margin: 0;
max-height: 0;
transition: 1s linear max-height;
}
.accordion.expanded p:last-child {
max-height: 1.4em;
}
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
<div class="accordion">
<p>toggle me</p>
<p>lorem ipsum</p>
</div>
Related
Implemented the so-called "smart header", which hides when scrolling down and appears when scrolling up.
The problem: on the iPhone, I scrolled at the bottom, everything is ok hiding, but if I go back to the very top of the site, the header is hiding. It will appear only if you pull it down a little.
On the iPhone there is an animation when reaching the top of the screen, such as a rebound, I think it's about it. I tried safari, chrome, mozilla browsers. On android in chrome, everything is ok.
Tell me, has anyone encountered such a thing, how to treat it?
Sample code on Codepen
<div class="wrapper">
<div data-header class="header">
<p>header</p>
</div>
<div class="main">
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
<p>lorem ipsum</p>
</div>
</div>
.wrapper {
margin-top: -50px;
}
.header {
position: fixed;
width: 100%;
height: 70px;
top: 0;
left: 0;
text-align: center;
font-size: 25px;
background: red;
transition: transform .8s;
}
.header._hidden {
transform: translateY(-100%);
}
.main {
padding-top: 75px;
height: 100px;
}
let lastScroll = 0;
const header = document.querySelector('[data-header]');
const scrollPosition = () => window.pageYOffset || document.documentElement.scrollTop;
const containHide = () => header.classList.contains('_hidden');
window.addEventListener('scroll', () => {
if (scrollPosition() > lastScroll && !containHide()) {
header.classList.add('_hidden');
} else if (scrollPosition() < lastScroll && containHide()) {
header.classList.remove('_hidden');
}
Video with the problem on Yandex.Disk
I tried to make a negative margin on top to compensate a little did not help. No more ideas...
This question already has answers here:
Adding click event listener to elements with the same class
(5 answers)
Closed 8 months ago.
I am trying to add an event listener but no result came. I know JavaScript has a hoisting feature but I believe I tried all except the correct solution.
const cbox = document.querySelectorAll(".box");
function doit() {
for (let i = 0; i < cbox.length; i++){
cbox[i].classList.add("red");
}
}
cbox.addEventListener("click", doit, false);
Can somebody spot the mistake I make?
There are some dissimilarities between the code and the link you have provided. There is no function doit() in there.
You have attached addEvenListener to the NodeList in cbox.addEventListener("click",....., you have to loop through the list and attach the event to the current element:
Try the following:
const cbox = document.querySelectorAll(".box");
for (let i = 0; i < cbox.length; i++) {
cbox[i].addEventListener("click", function() {
cbox[i].classList.toggle("red");
});
}
*,
html,
body {
padding: 0;
margin: 0;
}
.box {
width: 10rem;
height: 10rem;
background-color: yellowgreen;
float: left;
position: relative;
margin: 0.5rem;
transition: .5s all;
}
h3 {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.box:not(:first-child) {
margin-left: 1rem;
}
.red {
background-color: orangered;
}
<div id="box1" class="box box1">
<h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
<h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
<h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
<h3>Box 4</h3>
</div>
You can also use Array.prototype.forEach() with arrow function syntax that will allow you to achieve the same with less code:
let cbox = document.querySelectorAll(".box");
cbox.forEach(box => {
box.addEventListener('click', () => box.classList.toggle("red"));
});
*,
html,
body {
padding: 0;
margin: 0;
}
.box {
width: 10rem;
height: 10rem;
background-color: yellowgreen;
float: left;
position: relative;
margin: 0.5rem;
transition: .5s all;
}
h3 {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.box:not(:first-child) {
margin-left: 1rem;
}
.red {
background-color: orangered;
}
<div id="box1" class="box box1">
<h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
<h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
<h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
<h3>Box 4</h3>
</div>
ES6 makes this a bit simpler:
document.querySelectorAll(".box").forEach(box =>
box.addEventListener("click", () => box.classList.toggle("red"))
)
Example implementation:
document.querySelectorAll(".box").forEach(box =>
box.addEventListener("click", () => box.classList.toggle("red"))
)
.box {
width: 5rem;
height: 5rem;
background-color: yellowgreen;
display: inline-block;
}
.box.red {
background-color: firebrick;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
You can use forEach on the class or use Event delegation.
const cboxes = document.querySelectorAll(".box");
function doit() {
... do something ...
}
cboxes.forEach(
function(cbox) {
cbox.addEventListener("click", doit,false);
}
);
Notice that I changed your variable name.
EventDelgation
HTML:
<div id="parent">
<div id="box1" class="box box1">
<h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
<h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
<h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
<h3>Box 4</h3>
</div>
</div>
The JS part:
const parent = document.querySelector("#parent");
parent.addEventListener('click', (e) => {
e.target.classList.add('red');
console.log(e.target);
});
Event delegation is much better and it uses fewer resources, as you only use 1 Event listener and no for loop.
Rather than iterate through multiple elements and attach a lot of separate listeners, I would recommend using event delegation and creating a single delegated listener at the root level, and then looking for matching elements that raised the event.
document.body.addEventListener("click", function(e) {
if (e.target.classList.contains("box")) {
doit();
}
})
Further Reading
window.EventTarget.prototype.addDelegatedListener
Adding click event listener to elements with the same class
addEventListener on NodeList
This question already has answers here:
Adding click event listener to elements with the same class
(5 answers)
Closed 8 months ago.
I am trying to add an event listener but no result came. I know JavaScript has a hoisting feature but I believe I tried all except the correct solution.
const cbox = document.querySelectorAll(".box");
function doit() {
for (let i = 0; i < cbox.length; i++){
cbox[i].classList.add("red");
}
}
cbox.addEventListener("click", doit, false);
Can somebody spot the mistake I make?
There are some dissimilarities between the code and the link you have provided. There is no function doit() in there.
You have attached addEvenListener to the NodeList in cbox.addEventListener("click",....., you have to loop through the list and attach the event to the current element:
Try the following:
const cbox = document.querySelectorAll(".box");
for (let i = 0; i < cbox.length; i++) {
cbox[i].addEventListener("click", function() {
cbox[i].classList.toggle("red");
});
}
*,
html,
body {
padding: 0;
margin: 0;
}
.box {
width: 10rem;
height: 10rem;
background-color: yellowgreen;
float: left;
position: relative;
margin: 0.5rem;
transition: .5s all;
}
h3 {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.box:not(:first-child) {
margin-left: 1rem;
}
.red {
background-color: orangered;
}
<div id="box1" class="box box1">
<h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
<h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
<h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
<h3>Box 4</h3>
</div>
You can also use Array.prototype.forEach() with arrow function syntax that will allow you to achieve the same with less code:
let cbox = document.querySelectorAll(".box");
cbox.forEach(box => {
box.addEventListener('click', () => box.classList.toggle("red"));
});
*,
html,
body {
padding: 0;
margin: 0;
}
.box {
width: 10rem;
height: 10rem;
background-color: yellowgreen;
float: left;
position: relative;
margin: 0.5rem;
transition: .5s all;
}
h3 {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.box:not(:first-child) {
margin-left: 1rem;
}
.red {
background-color: orangered;
}
<div id="box1" class="box box1">
<h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
<h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
<h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
<h3>Box 4</h3>
</div>
ES6 makes this a bit simpler:
document.querySelectorAll(".box").forEach(box =>
box.addEventListener("click", () => box.classList.toggle("red"))
)
Example implementation:
document.querySelectorAll(".box").forEach(box =>
box.addEventListener("click", () => box.classList.toggle("red"))
)
.box {
width: 5rem;
height: 5rem;
background-color: yellowgreen;
display: inline-block;
}
.box.red {
background-color: firebrick;
}
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
You can use forEach on the class or use Event delegation.
const cboxes = document.querySelectorAll(".box");
function doit() {
... do something ...
}
cboxes.forEach(
function(cbox) {
cbox.addEventListener("click", doit,false);
}
);
Notice that I changed your variable name.
EventDelgation
HTML:
<div id="parent">
<div id="box1" class="box box1">
<h3>Box 1</h3>
</div>
<div id="box2" class="box box2">
<h3>Box 2</h3>
</div>
<div id="box3" class="box box3">
<h3>Box 3</h3>
</div>
<div id="box4" class="box box4">
<h3>Box 4</h3>
</div>
</div>
The JS part:
const parent = document.querySelector("#parent");
parent.addEventListener('click', (e) => {
e.target.classList.add('red');
console.log(e.target);
});
Event delegation is much better and it uses fewer resources, as you only use 1 Event listener and no for loop.
Rather than iterate through multiple elements and attach a lot of separate listeners, I would recommend using event delegation and creating a single delegated listener at the root level, and then looking for matching elements that raised the event.
document.body.addEventListener("click", function(e) {
if (e.target.classList.contains("box")) {
doit();
}
})
Further Reading
window.EventTarget.prototype.addDelegatedListener
Adding click event listener to elements with the same class
addEventListener on NodeList
I've got three sections, inside of which there are two divs. Inside the first one I have a button and after clicking it I should have the next one opened. However, only one div should be visible at the time (so when you click the next one, previous one should be closed). And I've got this functionality, but after clicking on the button again - it doesn't close the corresponding div.
I set up an example of my problem on codepen:
https://codepen.io/hubertstrawa/pen/abOwWMJ
<section>
<div class="product">
<span class="btn">Show more</span>
<p>Lorem ipsum</p>
</div>
<div class="product-more displayNone">
Test
</div>
</section>
$('.btn').click(function(e) {
// only one div to be shown but can't be closed as well.
$('.product-more').each(function(i, v) {
$(this).removeClass('displayBlock');
$(this).addClass('displayNone');
})
if ($(e.target).parent().next().hasClass('displayNone')) {
$(e.target).parent().next().removeClass('displayNone');
$(e.target).parent().next().addClass('displayBlock');
} else {
$(e.target).parent().next().removeClass('displayBlock');
$(e.target).parent().next().addClass('displayNone');
}
});
Any ideas how can I make it work?
Thank you
Change a .is-open on a parent element.
<section class="product is-open"> <!-- is-open toggled by JS -->
<div class="product-more"></div> <!-- handle children styles using CSS -->
</section>
.product-more { display: none; } /* default */
.product.is-open .product-more { display: block; } /* when ancestor is .is-open*/
Use delegateTarget inside the .on() method to get back the .product delegator element
const $product = $('.product'); // Collect all current products
$product.on('click', '.btn', function(e) {
const $thisProd = $(e.delegateTarget); // The .product delegator
$product.not($thisProd).removeClass('is-open'); // Handle all (but not this)
$thisProd.toggleClass('is-open'); // Handle current
});
/* QuickReset */ * {margin: 0; box-sizing: border-box;}
.product {
background-color: #ededed;
width: 400px;
margin: 0 auto;
margin-bottom: 1rem;
}
.product-title {
position: relative;
padding: 1rem;
}
.product .btn {
position: absolute;
bottom: 0;
right: 0;
padding: .7rem;
background-color: cyan;
cursor: pointer;
}
.product-more {
width: 100%;
padding: 1rem;
background-color: cyan;
display: none; /* by default */
}
.product.is-open .product-more {
display: block;
}
<section class="product">
<div class="product-title">
<p>Lorem ipsum</p>
<span class="btn">Show more</span>
</div>
<div class="product-more">Test</div>
</section>
<section class="product">
<div class="product-title">
<p>Lorem ipsum</p>
<span class="btn">Show more</span>
</div>
<div class="product-more">Test</div>
</section>
<section class="product">
<div class="product-title">
<p>Lorem ipsum</p>
<span class="btn">Show more</span>
</div>
<div class="product-more">Test</div>
</section>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
This is preferred, since it lets you change HTML and CSS, and not worry any more about JavaScript - whilst by using .prev(), .next() or .parent() (like the other answers suggest) JS is just waiting for you to change the markup - to break.
No need to traverse back and forth your selectors.
No need for .displayNone and .displayBlock on the product-more element.
Handling dynamic .product
if your .product are dynamic elements, here's another solution to the above concept:
$('.allProducts').on('click', '.btn', function(e) {
const $product = $(e.delegateTarget).find('.product'); // Get all .product
const $thisProd = $(this).closest('.product'); // The closest .product ancestor
$product.not($thisProd).removeClass('is-open'); // Handle all (but not this)
$thisProd.toggleClass('is-open'); // Handle current
});
/* QuickReset */ * {margin: 0; box-sizing: border-box;}
.product {
background-color: #ededed;
width: 400px;
margin: 0 auto;
margin-bottom: 1rem;
}
.product-title {
position: relative;
padding: 1rem;
}
.product .btn {
position: absolute;
bottom: 0;
right: 0;
padding: .7rem;
background-color: cyan;
cursor: pointer;
}
.product-more {
width: 100%;
padding: 1rem;
background-color: cyan;
display: none; /* by default */
}
.product.is-open .product-more {
display: block;
}
<div class="allProducts">
<section class="product">
<div class="product-title">
<p>Lorem ipsum</p>
<span class="btn">Show more</span>
</div>
<div class="product-more">Test</div>
</section>
<section class="product">
<div class="product-title">
<p>Lorem ipsum</p>
<span class="btn">Show more</span>
</div>
<div class="product-more">Test</div>
</section>
<section class="product">
<div class="product-title">
<p>Lorem ipsum</p>
<span class="btn">Show more</span>
</div>
<div class="product-more">Test</div>
</section>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
You can use the toggleClass it will detect your class and change it to another.
In your each function you just need to set all items to be hide and then it will toggle classes for current item.
Codepen
https://codepen.io/nasser-ali-karimi/pen/rNVwwLy?editors=1010
$('.btn').click(function(e) {
// only one div to be shown but can't be closed as well.
$('.product-more').each(function(i, v) {
$(this).removeClass('displayBlock');
$(this).addClass('displayNone');
})
$(e.target).parent().next().toggleClass('displayNone displayBlock');
});
A shorter version using jQuery would be using hide() and toggle():
$('.btn').click(function(e) {
var more = $(e.target).parent().next() ;
$('.product-more').not(more).hide();
$(e.target).parent().next().toggle();
});
You are hiding all the product-more sections when clicking any btn button, and then, trying to show/hide the product-more section associated with the clicked button.
So, when the section product-more is already shown and you click its btn button what happens is that you first hide the associated section and then your code checks if it is not visible and then shows its again.
One possible solution is to discard the associated product-more section when hiding. Also, as divs are shown by default, you don't need the displayBlock class.
$('.btn').click(function(e) {
var $current = $(e.target).parent().next('.product-more');
// Hide all sections that are not the one associated to the current button.
$('.product-more').not($current).addClass('displayNone');
// Show or hide current section.
$current.toggleClass('displayNone');
});
I created this code, click on a picture and you'll come out the description below the image.
I would like to improve this code, so I want to be active only one description of a time, so if I click on a image see the description of that just clicked, and hide the last activated.
maybe with the code is more clear http://codepen.io/mp1985/pen/qOrpQX
$( ".spec").click(function() {
$(this).find(".image, .details-spec").toggle();
$(this).find(".block-content").toggleClass('white');
});
I tried with toggle(), toggleClass() and not() but without success.
any idea?
thanks
You can use not() here to avoid clicked element from selector
var $spec = $(".spec").click(function() {
// caching selector $(".spec") for future use
$spec
.not(this)
// avoiding clicked element
.find(".image")
// getting image selector
.show()
// showing back image
.end()
// back to previous selector
.find(".details-spec")
// getting details
.hide()
// hiding it
.end()
// back to previous selector
.find(".block-content")
// getting block content
.removeClass('white');
// removing class white
$(this)
.find(".image, .details-spec")
// getting elements by class
.toggle()
// toggling visibility
.end()
// back to previous selector
.find(".block-content")
// getting block content
.toggleClass('white');
// toggling class white
});
.block {
position: relative;
height: 300px;
width: 100%;
overflow: hidden;
background: #f9bda1;
margin-bottom: 1em;
}
.one-thirds > .block {
background-color: #484343;
cursor: pointer;
}
.block .image {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.one-thirds {
width: 32%;
float: left;
margin-right: 1%;
}
.full {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
h3 {
font-size: 20px;
}
.details-spec {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
}
.white {
color: white;
}
.active > .image {
visibility: hidden;
}
.active .details-spec {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>
<div class='one-thirds'>
<div class="block square spec">
<div class="full image" style="background-image:url('http://lorempixel.com/300/300/');"></div>
<div class="block-content full">
<h3>Title:</h3>
<div class="details-spec">
Lorem Lorem Lorem Lorem LoremLorem Lorem Lorem Lorem Lorem LoremLorem
</div>
</div>
</div>
</div>
<div class='one-thirds'>
<div class="block square spec">
<div class="full image" style="background-image:url('http://lorempixel.com/300/300/sports/1/');"></div>
<div class="block-content full">
<h3>Title:</h3>
<div class="details-spec">
Lorem Lorem Lorem Lorem LoremLorem Lorem Lorem Lorem Lorem LoremLorem
</div>
</div>
</div>
</div>
<div class='one-thirds last'>
<div class="block square spec">
<div class="full image" style="background-image:url('http://lorempixel.com/300/300/sports/3/');"></div>
<div class="block-content full">
<h3>Title:</h3>
<div class="details-spec">
Lorem Lorem Lorem Lorem LoremLorem
</div>
</div>
</div>
</div>
</div>
You don't need that over complex jQuery. Set the styles with css and toggle a single class on the root element.
CSS
.spec.active .image {
display: none;
}
.spec.active .details-spec {
display: block;
}
.spec.active .block-content {
color: white;
}
JavaScript
var $spec = $('.spec');
$spec.click(function() {
$spec.not($(this)).removeClass('active');
$(this).toggleClass('active');
});
DEMO: http://codepen.io/anon/pen/VvpXXe
First close all ($spec). Second open current.($this)
var $spec = $(".spec").click(function() {
$spec.find(".image, .details-spec").show();
$spec.find(".block-content").removeClass('white');
$(this).find(".image, .details-spec").hide();
$(this).find(".block-content").addClass('white');
});