I want to reproduce the effect I coded in this vanilla JS example in my React app.
This is my Sass
.item
opacity: 0
transition-property: opacity
transition-timing-function: ease-in
.is-transitioning
opacity: 1
the loop generating the images and their containers:
this.props.images.map((image, index) => <ImageContainer key={`img-${index}`}
image={image}
transitionDuration={Math.trunc(index * Math.random() * 1000) + 200} />
)
and finally the ImageContainer component:
const ImageContainer = (props) => (
<div className="item is-transitioning"
style={{ transitionDuration: `${props.transitionDuration}ms` }}
>
<img src={`${props.image.path}`} />
</div>
);
export default ImageContainer;
Even though the inline class is correctly applied and the css is there, I can't figure out why the effect doesn't show up.
The issue is that the is-transitioning is added from the beginning so your elements are already at opacity:1 and notihng will happen. You need to add the class in order to trigger the opacity change and see the transition.
Another way, in case you cannot add the class is to use animaton. Here is the JS example that you can convert to react:
Array.from(document.querySelectorAll('.item')).map((i, index) => {
i.style.animationDuration = `${(index * Math.trunc(Math.random() * 1000)) + 200}ms`;
});
.container {
display: flex;
}
.item {
display: flex;
justify-content:center;
align-items: center;
width: 1rem;
height: 1rem;
background-color: teal;
padding: 2rem;
margin-right: 1.2rem;
border-radius: 10px;
font-size: 2rem;
color: white;
opacity: 0;
animation:change 2s forwards;
}
#keyframes change{
to {
opacity:1;
}
}
<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>
Simply keep the same code you wrote and replace transitionDuration with animationDuration and adjust the CSS.
(As said in another answer to this thread,) The issue is that the CSS (classes) didn't change, so the transition is "not needed", causing no animation.
For React people who wants to use transition over animation or other libraries, here is a "working" fiddle with a dirty hack:
https://jsfiddle.net/s16nd2j5/
The trick is to add the class to <ImageContainer> in componentDidMount() with a setTimeout for 0ms.
setTimeout( _ => this.setState({ transitioning: true }), 0);
This kinda forces the state update to be "postponed" to another render, causing the "CSS class change" to take place.
P.S. it is a hack. The code smells when a setTimeout / setInterval is used like this.
P.P.S. The shuffle() part from OP's fiddle is omitted for simplicity.
Related
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>
I have several images that I need to horizontally cross the page to the right, exit the page and then re-enter the page from the left. Some of the images will already be out of view, so they will have to enter first.
This is a sketch of what I've tried so far:
var elems = document.getElementsByClassName("child");
for (const elem of elems) {
elem.animate(
[
// keyframes
{transform: "translateX(300px)"},
],
{
// timing options
duration: 5000,
iterations: Infinity
},
);
}
.container {
background-color: aqua;
width: 1000px;
display: flex;
flex-direction: row;
overflow:hidden;
padding: 20px 0;
gap: 10px;
}
.child {
background-color: red;
flex: 0 0 20%;
}
<div class="container">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
<div class="child">5</div>
<div class="child">6</div>
<div class="child">7</div>
<div class="child">8</div>
</div>
For start I tried to slide out all the divs, but even that I don't understand why is not working.
I'm using your code as a starting point however there are 2 major differences between my code and yours. The first is that this solution is not using JavaScript, which is a plus, but it may not be what you are looking for. The second difference is that rather of animating the div elements with the class child, this solution is animating a wrapper div with the class slider.
One important thing to note, is that some calculations must be used for the animation to work properly. Adding or removing elements will require that the values are updated. The formula is the following:
Child div size: 20% (CHILD_SIZE)
Gap between children divs: 10px (GAP)
Amount of the children: 8 (CHILDREN_AMOUNT)
So together it goes like this: translateX(calc((CHILD_SIZE - GAP) * CHILDREN_AMOUNT));
var slider = document.getElementsByClassName('slider')[0];
slider.innerHTML += slider.innerHTML;
.container {
background-color: aqua;
width: 100%;
overflow: hidden;
}
.slider {
display: flex;
flex-direction: row;
padding: 20px 0;
gap: 10px;
animation: slideRight 10s infinite linear;
}
.child {
background-color: red;
flex: 0 0 20%;
}
#keyframes slideRight {
from {
transform: translateX(calc((-20% - 10px) * 8));
}
to {
transform: translateX(100% + 10px);
}
}
<div class="container">
<div class="slider">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
<div class="child">5</div>
<div class="child">6</div>
<div class="child">7</div>
<div class="child">8</div>
</div>
</div>
Updated considering the comment:
There are a few ways, the simpler way though is just to duplicate the div.child elements without touching the animation formula. This can be done just in the markup or using JavaScript to have a more dynamic solution (I have updated the code above to have the desired result).
What I consider a better way, though (not going to elaborate here as many libraries already solve this problem, just search for carousel js libraries), is to just prepend and append the necessary amount of elements to have the desired result instead of duplicating all of them.
Example
i need to make divs appear like this, or any other fancy way. Can someone provide source or something that can help?
We can add the class name of a CSS attribute with a transition, and animate property to switch it from an opacity: 0 element to an opacity: 1 element, and assign it an animation attribute that slides it in using the CSS transform property.
We can use getBoundingClientRect and scrollTop to check when the element is in view, and add the class when it is.
This gives us something close to the desired effect.
document.addEventListener("scroll", (event) => {
const elementsToAnimate = Array.from(document.querySelectorAll(".stationary"));
for (element of elementsToAnimate) {
const {
scrollTop
} = document.querySelector("html");
const {
top
} = element.getBoundingClientRect();
const containsAnimateClass = element.classList.contains("from-left");
if (top < scrollTop / 2 && !containsAnimateClass)
element.classList.add("from-left");
}
});
body {
display: flex;
flex-direction: column;
gap: 1rem;
}
.stationary {
opacity: 0;
width: 100px;
height: 100px;
background: red;
}
.from-left {
transition: 1s ease;
opacity: 1;
animate: 1s ease slide-in-from-left;
}
#keyframes slide-in-from-left {
from {
transform: translateX(-50%);
}
to {
transform: translateX(0);
}
}
<body>
<div>Scroll!</div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
<div class="stationary"></div>
</body>
You can use simple libraries already implemented these animations:
https://github.com/matthieua/WOW
https://scrollrevealjs.org/
https://scrollmagic.io/examples/basic/reveal_on_scroll.html
https://michalsnik.github.io/aos/
(I personally love this, its easy to use)
Or in other hand make your own animation.
add event listener for scroll and create a keyframe animation. Then assign the class to the element when scroll reached to your desire number.
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>
I have a button in React and when I hover over it a funky saw animation happens. However, the effect I truly want is for the H1 tag on the page to have the saw animation in the background occur when I hover over the button. Yet, whenever I add a className and try to target that in the button:hover css, I get no effect. I've tried .btn:hover h1 and .btn:hover."classname" and a number of other combinations. yet none work.
How can I target a class, div, or h1 when hovering over a button that is not directly connected to the class, div, or h1 that will have the effect?
The current CSS I'm using for this, which works for the button itself, is:
.btn {
color: black;
}
.btn:hover {
animation: sawtooth 0.35s infinite linear;
background: linear-gradient(45deg, #d3f169 0.5em, transparent 0.5em) 0 0 / 1em 1em
, linear-gradient(-45deg, #d3f169 0.5em, transparent 0.5em) 0 0 / 1em 1em;
color: adjust-hue($color,180);
}
#keyframes sawtooth {
100% {
background-position: 1em 0;
}
}
The template I have is:
return (
<div className="App">
<h1>Question Genie</h1>
<button className="btn" onClick={this.displayQuestion}>View Unanswered Questions</button>
{questions}
</div>
);
}
}
You can use a next sibling selector (~) and flexbox column-reverse to accomplish this. The main issue here is that there is no previous sibling selector in CSS
So, you can reorder the HTML so that <h1> is after the <button>, like this:
<div class="App">
<button class="btn" onClick={this.displayQuestion}>View Unanswered Questions</button>
<h1>Question Genie</h1>
</div>
and then you can use flex-direction: column-reverse; (or even order: -1 on the button) to make the <h1> appear above the <button>
CSS:
.App {
display: flex;
flex-direction: column-reverse;
align-items: flex-start;
}
.btn:hover ~ h1 {
animation: sawtooth 0.35s infinite linear;
... rest of the stuff
}
Here's the codepen: https://codepen.io/palash/pen/ZvVNav
Be sure to check flexbox support here https://caniuse.com/#feat=flexbox
Just use javascript...
<button onmouseover="H1_ID.style.animation='sawtooth 0.35s infinite linear';" onmouseout="H1_ID.style.animation='none';"></button>