CSS transition is ignored due to parents display value? - javascript

There are plenty other post regarding CSS transition not working due to the usage of display and I do get the point. The proposed solutions are mostly the usage of opacity and visibility instead of display. Well I dind't want to do it that way even if I could as my positioning is absolute and wouldn't take any space with visibility set to 0, just like with display: none.
However, when I've read this thread, where the accepted answer says "...cannot be applied to a class that is transitioning from display: block; to display: none;" I thought I'm smart and applied the transition to it's child elements, meaning the parent toggles from display: none to display: flex and the children transition from opacity: 0 to opacity: 1. It's still not working and my assumption is that this is due to the same issue of display/transition not wanting each other, but my question is why? First I thought the display value may be inherited but it's not as the children aren't flex.
could anyone explain why it behaves like that?
To as well provide some code here is my react code using styled components for the styling.
const NavigationWrapper = styled.nav`
& {
position: relative;
display: flex;
flex: 1 0;
align-items: center;
justify-content: flex-end;
}
#media (max-device-width: 992px) {
& button {
border: none;
background-color: red;
margin-right: 10px;
}
}
#media (min-device-width: 993px) {
& button {
display: none;
}
}
`;
const NavList = styled.ul`
& {
list-style: none;
display: flex;
margin: 0;
padding: 0;
}
& li {
padding: 10px 20px;
}
#media (max-device-width: 992px) {
& {
position: absolute;
display: ${(props) => (props.isMobileNav ? "flex" : "none")};
flex-direction: column;
right: 0;
top: 100%;
width: 100vw;
background-color: transparent;
}
& li {
opacity: ${(props) => (props.isMobileNav ? 1 : 0)};
background-color: white;
transition: opacity 1s ease;
}
}
`;
const Navigation = (props) => {
const [isMobileNav, setIsMobileNav] = useState(false);
return (
<NavigationWrapper>
<button onClick={() => setIsMobileNav(!isMobileNav)}></button>
<NavList isMobileNav={isMobileNav}>
<li>Blog</li>
<li>Profiles</li>
<li>Contact</li>
</NavList>
</NavigationWrapper>
);
};
EDIT: I tried it obviously the other way around with opacity on parent and display on children, just to try and, yes, it works. But leads to the solution I didn't want as I could set visibility to hidden or opacity to 0. I just want to know why the visibility of the parent affects the transition of the child.

Well, an element with display: none; effectively gets pulled out of the flow. The code is still there, but it is treated as if it is not.
If a parent element is "removed" in this way, all children of that parent are "removed" with it, and this happens instantly. So that is why you can't do transitions of child elements, when the parent is "gone".
A solution though (if you absolutely have to use display), could be to use a setTimeout() that waits for the duration of the transition, before setting display: none; (the other way, setting display: flex; should work with transitions on child elements just fine).
Something like this, although this simple example poses issues regarding unmounting, and you should quard it with a cancel mechanism, that can interrupt the timeout when unmounting the component:
<button
onClick={() => {
// Delayed
if (isMobileNav) {
setTimeout(() => {
setIsMobileNav(false);
}, 1000);
return;
}
// Instant
setIsMobileNav(true);
}}
>
</button>

Related

Transition on multiple elements at the same time using JavaScript

Cheers! I'm having an issue debugging certain behavior that occurs mostly in the Chrome browser. Here is the simplified example: https://jsfiddle.net/pd3xb2uo/
The objective is to transition multiple elements via JS code at the same time. In the example when you click on the button, items are moved to the left using translate3d added via JS. It works fine, but there are some caveats:
There is a small gap appearing between the items most of the time
Sometimes when you click faster on the button there is a large gap appearing between the items.
Here are the screenshots of both cases:
Any help or ideas on why it is happening would be highly appreciated:) It looks like there is a few milliseconds delay before the style attribute is updated on certain elements, but I have no idea why:/
The problem occurs because you are transitioning 100 elements at the same time and because of half-pixel transitions.
If you know how wide and how many elements you have, then you can do it like so:
const container = document.querySelector('.container-inner');
for (let i = 1; i < 100; i++) {
const div = document.createElement('div');
div.classList.add('element');
div.textContent = `Element ${i}`;
container.appendChild(div);
}
let transition = 0;
document.querySelector('button').addEventListener('click', () => {
transition -= 100;
container.style.transform = `translateX(${transition}px)`;
});
.container{
width: 100%;
overflow: hidden;
}
.container-inner{
display: flex;
flex-direction: row;
transition: transform .3s;
}
.element {
width: 100px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
text-align: center;
transition: transform .3s;
background-color: #A67583;
}
<button>Move</button>
<div class="container">
<div class="container-inner"></div>
</div>
Now only one element gets transitioned and it is working smoothly.

Javscript detect the state of a div so I can toggle between 2 functions? (without jQuery)

I have a sidebar and I have 2 functions.
One opens it and the other one closes it.
Here are the two functions.
function openNav() {
document.getElementById("myNav").style.width = "100%";
}
function closeNav() {
document.getElementById("myNav").style.width = "0%";
}
I want to create a condition between both so I can toggle them but for that I need to detect if it's opened or closed.
How can I detect it so I can create a toggle function calling the functions above?
You don't need two functions. You can do this with just one CSS class and one function that toggles that one class on the sidebar element.
To do this, add the default width of 0% on the sidebar and then create another class, lets call it .sidebarToggle, that changes the width to 100%.
In javascript, you just need one function, lets call it toggleSidebar() that just toggles the .sidebarToggle class. This way, you not only need less code but also don't need to worry about checking whether the sidebar is opened or closed.
A Better Solution
A better approach is to change the transform property of the sidebar instead of the width. Changing transform property, in this case, is better as compared to changing width, because if you change the width, you will have to handle the:
resizing of the child elements of the sidebar as the sidebar's width is increased or decreased.
remove the left or right padding (if there's any) on the sidebar when the width of the sidebar is 0px or 0%. If you don't remove the padding, sidebar will not completely hide on 0px or 0% width.
With transform, you don't need to worry about the above mentioned points because, instead of resizing, we just translate the sidebar from one point to another.
Animating transform property is also more efficient as compared to animating the width property because changing transform property doesn't causes the browser to go through Layout and Paint steps of its critical rendering path whereas changing the width property will cause the browser to again go through these steps.
Following code snippet shows an example:
const btn = document.querySelector('button');
const sidebar = document.querySelector('.sidebar');
btn.addEventListener('click', () => {
sidebar.classList.toggle('sidebarToggle');
});
body {
margin: 0;
}
.sidebar {
display: flex;
flex-direction: column;
background: #666;
color: #fff;
padding: 15px 25px;
box-sizing: border-box;
width: 30%;
height: 100vh;
transform: translateX(-100%);
position: absolute;
transition: transform 0.5s ease;
}
span {
margin: 5px 0;
}
.sidebarToggle {
transform: translateX(0);
}
button {
position: absolute;
right: 0;
background: green;
color: #fff;
padding: 15px 25px;
}
<div class="sidebar">
<span>Item 1</span>
<span>Item 2</span>
<span>Item 3</span>
<span>Item 4</span>
</div>
<button>Toggle Sidebar</button>
This is best done with classes. That way, stying is better separated from behaviour and there is even a toggle function built in:
document.getElementById("button").onclick = function() {
document.getElementById("myNav").classList.toggle("width100");
};
#myNav {
background-color: red;
display: block;
height: 100px;
width: 0%;
}
#myNav.width100 {
width: 100%;
}
<nav id="myNav"></nav>
<button id="button">Toggle</button>

adding onclick to an array of items

I have this problem where if I click on an item, all the items in that column gets affected by the click. I want only that very item where I clicked to have the class. but in my code, all the other items of that column are getting the class when I click on a certain single item. I want to make a div go fullscreen when I click on that particular div. kinda like modal pop up, here I want that the div slowly animates to fullscreen
export default class App extends React.Component {
constructor(props) {
super(props);
this.addActiveClass = this.addActiveClass.bind(this);
this.state = {
activeIndex: -1
};
}
addActiveClass(activeIndex) {
this.setState(prev => ({
activeIndex: prev.activeIndex === activeIndex ? -1 : activeIndex
}));
}
render() {
return (
<div className="container">
{Array.from({ length: 30 }).map((item, index) => {
return (
<div
key={index}
style={{
background: randomColor(colors),
height: randomHeight(100, 200)
}}
className={this.state.activeIndex === index ? "full" : ""}
onClick={() => this.addActiveClass(index)}
/>
);
})}
</div>
);
}
}
* {
box-sizing: border-box;
}
body {
margin: 40px;
background-color: #fff;
color: #444;
font: 2em Sansita, sans-serif;
}
.container {
display: flex;
flex-direction: column;
flex-wrap: wrap;
max-height: 100vh;
}
.container > * {
border: 2px solid orange;
border-radius: 5px;
margin: 10px;
padding: 10px 20px;
background-color: red;
color: #fff;
}
.full{
width: 100%;
height: 100%;
transition: 2s;
}
sandbox demonstrating the problem
I don't know exactly what kind of effect you would like to achieve with the selected item but items in the column are affected because of the default value of flexbox align-items which is stretch. The other items are stretching to match the selected element. Try to add align-items: flex-start to your .container class.
You'll have to make sure you're adding the class to the event target. Your onClick function needs to take the event as one of it's arguments, and you need to use event.target in the function body to make sure you're only affecting the element that is clicked. Hope that makes sense!
https://developer.mozilla.org/en-US/docs/Web/API/Event/target
The class adding logic works ok. You can observe this by checking the elements on developer tools. The problem is you are adding an inline style to your elements.
style={{
background: randomColor(colors),
height: randomHeight(100, 200)
}}
So, after each new render, this overrides the full class and give new height and new color.
The other problem is your full class. You are using 100% for height and width and probably this causes problems for your flex.
Just remove the inline style part and instead of using a percentage give custom width and height to full class, then you can see adding the class logic works well.
Pass down an event (e) from your onClick, then access that specific item through e.target.className. This way only the clicked item is updated, even if dynamically rendered from an array:
handleClick = e => { e.target.classNamme = 'class' }
<div className='' onClick={e => handleClick(e)}></div>
Alternatively, if you're keeping your classes in the state, you can just replace the string value with your variable and control in inside the function, as you've done in the example you've provided.
Adding !important overrides the inline-style height.
Height is set to 100vh to expand the div into full screen.
.full{
width: 100%;
height: 100vh !important;
transition: 2s;
}

from :hover to a togglebutton in react

What I am trying to do:
I have a card that when hovered over expands and does a bunch of different animations i worked on. this is great for desktop and not mobile. So when the screen is a certain res I make a toggle button visible. I want this button in react to when clicked on enable the save:hover State its grabbing from my css.
The Problem
The button is inside the card aleady and everything in the css is setup for the parent div card.
my code structure simplified
<div className="card">
<-- CODE HERE -->
<Button className="myToggleButton" />
<div/>
I cant from what i can tell in css exclusively say myToggleButton:focus do these changes to the following classes since they are parent ones. So i think my only other way to do that is by telling it in react somehow to say that my div is in :hover state, but I can't quite figure out how despite my efforts. Thank you in advance for any help on this.
Instead of activating your animation with the :hover pseudo class, you can simply add or toggle another custom class, which would contain the values to transition or keyframes to animate, to the card element when the button is clicked. In this example I just use a transition, but you could also employ a more complex keyframe animation.
const card = document.querySelector(".card");
const button = document.querySelector(".card > button");
const animate = () => {
card.classList.toggle("small");
card.classList.toggle("big");
}
button.addEventListener("click", animate);
.card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
transition: all 250ms ease-in-out;
}
.small {
width: 200px;
height: 50px;
background-color: gray;
}
.big {
width: 400px;
height: 150px;
background-color: blue;
}
<div class="card small">
My Amazing Card
<button>Animate</button>
</div>

Strange transition behavior for inline elements styles in certain places

This is a jsfiddle example file that replicates the problem: https://jsfiddle.net/Lhr0d6cw/11/
I wanted the element (when clicked) to expand for 6seconds from its original position but notice that when you click the red card (or any card), it doesn't start expanding from the originals position it used to be, but rather from the middle, I assume that its because transition of 6s to top and left is not being applied for some reason.
Only places I was able to make it work properly so far are stackoverflow editor below or by inserting a debugger in the code and doing it manually but when using my localhost or jsfiddle it doesn't transition properly.
This is the same example on stackoverflow which works as desired:
const productCards = document.querySelectorAll(".products__card");
productCards.forEach(c => {
// console.log("clicked1");
c.addEventListener("click", openCard)
});
function openCard(e) {
console.log("clicked");
console.dir(this);
let top = this.getBoundingClientRect().top;
let left = this.getBoundingClientRect().left;
// this.style.transition = "top 0.9s, left 0.9s";
this.style.top = top + "px";
this.style.left = left + "px";
this.style.position = "fixed";
console.log(`top: ${top}, left: ${left}`);
// debugger;
this.classList.add("open");
}
.products {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: center;
min-width: 1000px;
max-width: 1500px;
margin-bottom: 300px;
}
.products .products__card {
display: flex;
flex-direction: column;
width: 150px;
height: 250px;
margin-bottom: 30px;
margin-right: 30px;
margin-left: 30px;
background-color: red;
transform: scale(1);
/* box-shadow: 3px 7px 55px -10px c(very-light); */
transition: width 0.9s, height 0.9s, z-index 0.9s, top 6s, left 6s;
}
.products .products__card.card-1 {
background-color: red;
}
.products .products__card.card-2 {
background-color: blue;
}
.products .products__card.card-3 {
background-color: green;
}
.products .products__card.card-4 {
background-color: yellow;
}
.products .products__card.card-5 {
background-color: pink;
}
.products .products__card.card-6 {
background-color: gray;
}
.products .products__card.open {
width: 550px;
height: 800px;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 120;
box-shadow: 0 0 1000px 1000px c(box-overlay);
}
<div class="products">
<div class="products__card card-1">
</div>
<div class="products__card card-2">
</div>
<div class="products__card card-3">
</div>
<div class="products__card card-4">
</div>
<div class="products__card card-5">
</div>
<div class="products__card card-6">
</div>
</div>
works when debugging:
The strange thing as mentioned above is that my problem in the browser using localhost is also solved when I insert debugger in the code and manually skip through the last step of adding .open class. If you have the same problem in jsfiddle or your own editor, try adding debugger; before this.classList.add("open"); and then open the console and then click the card and go over the last step manually in the console. you will notice that the card expanded from its original place as desired taking 6s to finish which means the transition was applied in this case.
My questions:
Why is transition for top and left only working in certain environments? is it a browser problem? I'm using the latest chrome. does someone know of a better way to achieve the same results?
code comments:
-obviously, 6 seconds is not what I will be using in my code, its used here just to make the transition obvious.
-In my source code, you can see that because I can't transition from position static to position fixed I had to use Javascript to add position fixed style inline to the element before the .open class is added, that way transition can take place properly when .open is added.
-I also added top and left values inline to keep the card in its original place when position: fixed style is applied because as you might know fixed position takes the element out of its flow, so top and left keep it in place.
-I added !important in css .open class because without it I can't override inline css as you might also know.
Thank you
I was able to solve my problem just now by applying a little hack. It seems that in some environments (localhost, jsfiddle) the javascript engine is adding the .open class faster than expected and the fact that it is working fine when debugging (slow process) indicated that to me. so I added a setTimeout() to the last piece of code delayed it by 20. this solved my problem and now it works fine on JSfiddle and on my computer. here is the new edited sample that works:
https://jsfiddle.net/Lhr0d6cw/14/
setTimeout(() => {
this.classList.add("open");
}, 20);
I would still like to know if there is a better way of doing this animation if someone would like to share!

Categories

Resources