React Transition Group - Does appear prop work with CSSTransition? - javascript

I want to run the animation on onLoad.
For this I used the appear prop as described in the docs and set in to true.
But no animation takes place. Does the appear prop not work with the CSSTransition component?
const [showStep, setShowStep] = useState(true);
...
<CSSTransition
in={showStep}
timeout={300}
classNames="fadein"
appear={true}
>
<p>Test</p>
</CSSTransition>
.fadein-enter {
opacity: 0.6;
transform: translateX(10%);
}
.fadein-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 200ms;
}
.fadein-exit {
opacity: 1;
}
.fadein-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}

Related

CSS Transition on open and close Menu in React

I have MenuBody component which is showed if state of isOpen is true. I want to add transition when menu is opening and also when is closing. Animation for opening is working but on closing it doesn't work.
This is my code
const [isOpen, setIsOpen] = useState(false);
<button onClick={()=>setIsOpen(!isOpen)}>open menu</button>
{isOpen ? <MenuBody /> : null}
....
const MenuBody = () => {
return (
<div className={styles.menuBody}>
//some content here to display when it's opened
</div>
)}
CSS
#-webkit-keyframes menuBody {
0% { opacity: 0; }
100% { opacity: 1; }
}
#-moz-keyframes menuBody {
0% { opacity: 0; }
100% { opacity: 1; }
}
#-o-keyframes menuBody {
0% { opacity: 0; }
100% { opacity: 1; }
}
#keyframes menuBody {
0% { opacity: 0; }
100% { opacity: 1; }
}
.menuBody {
....
-webkit-animation: menuBody 0.5s ease-in-out;
-moz-animation: menuBody 0.5s ease-in-out;
-o-animation: menuBody 0.5s ease-in-out;
animation: menuBody 0.5s ease-in-out;
}
I would appreciate any kind of help
Thanks
I think that the problem might be in the way how you wrote MenuBody or in isOpen since you didn't provide any code regarding closing I am gonna assume, that maybe it's missing. So I would recommend adding some button into the MenuBody which will be able to change the state of isOpen.
EDIT #1
I would recommend adding and removing a class that says if Modal is open or closed. I would recommend using this method described here: https://erikmartinjordan.com/react-on-disappear-animation

Timing in my React text animation gets worse on subsequent loops through an array

I have React code with a CSS animation in a codesandbox and on my staging site.
You will notice that over time, the animation timing drifts. After a certain number of loops it presents the text too early and is not in sync with the animation.
I have tried changing the timing making the array switch happen faster and slower.
Any ideas would be greatly appreciated.
import "./styles.css";
import styled, { keyframes } from "styled-components";
import React, { useEffect, useState } from "react";
const animation = keyframes`
0% { opacity: 0; transform: translateY(-100px) skewX(10deg) skewY(10deg) rotateZ(30deg); filter: blur(10px); }
25% { opacity: 1; transform: translateY(0px) skewX(0deg) skewY(0deg) rotateZ(0deg); filter: blur(0px); }
75% { opacity: 1; transform: translateY(0px) skewX(0deg) skewY(0deg) rotateZ(0deg); filter: blur(1px); }
100% { opacity: 0; transform: translateY(-100px) skewX(10deg) skewY(10deg) rotateZ(30deg); filter: blur(10px); }
`;
const StaticText = styled.div`
position: absolute;
top: 100px;
h1 {
color: #bcbcbc;
}
span {
color: red;
}
h1,
span {
font-size: 5rem;
#media (max-width: 720px) {
font-size: 3rem;
}
}
width: 50%;
text-align: center;
left: 50%;
margin-left: -25%;
`;
const Animate = styled.span`
display: inline-block;
span {
opacity: 0;
display: inline-block;
animation-name: ${animation};
animation-duration: 3s;
animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
animation-fill-mode: forwards;
animation-iteration-count: infinite;
font-weight: bold;
}
span:nth-child(1) {
animation-delay: 0.1s;
}
span:nth-child(2) {
animation-delay: 0.2s;
}
span:nth-child(3) {
animation-delay: 0.3s;
}
span:nth-child(4) {
animation-delay: 0.4s;
}
span:nth-child(5) {
animation-delay: 0.5s;
}
`;
export default function App() {
const array = ["wood", "cork", "leather", "vinyl", "carpet"];
const [text, setText] = useState(array[0].split(""));
const [countUp, setCountUp] = useState(0);
useEffect(() => {
const id = setTimeout(() => {
if (countUp === array.length -1) {
setCountUp(0);
} else {
setCountUp((prev) => prev + 1);
}
}, 3000);
return () => {
clearTimeout(id);
};
}, [countUp]);
useEffect(() => {
setText(array[countUp].split(""));
}, [countUp]);
return (
<div className="App">
<StaticText>
<h1>More than just</h1>
<Animate>
{text.map((item, index) => (
<span key={index}>{item}</span>
))}
</Animate>
</StaticText>
</div>
);
}
There are multiple potential issues here. For one, the animation runs for up to 3.5 seconds (due to the delay) but the text changes every 3 seconds, so the text change would trigger before the last character finishes animating.
Even if the text and animation were both set to 3s, the problem is that CSS animation and setTimeout/setInterval timing aren't perfect. These should be considered rough estimates. A setTimeout can take 3s to fire, or 3.1s, and even if it fires on time, React has to do work before another one is set. Drift can and will occur, so the animation should run in an event-driven manner whenever the text changes, not as an infinite loop that we assume will stay in sync with React and the timeout.
Adjustments you can try to fix these issues with include:
Remove the animation-iteration-count: infinite; property. This holds us accountable for triggering the animation in response to re-renders, not in a separate, likely-out-of-sync loop.
Change the setTimeout timeout to 3500, or something that is at least as large as the longest animation duration to make sure the animation isn't chopped off partway through.
Provide random keys to your letter <span>s to force rerenders as described in How to trigger a CSS animation on EVERY TIME a react component re-renders. To be precise, that could be <span key={Math.random()}>{item}</span>.
You can have key clashes using Math.random(), so using an incrementing state counter or integrating Date.now() for keys is a more robust way to go here.

How to trigger a CSS animation from JS?

I have a balloon that when hovered, will expand n disappear (a popping-like animation). I made this in CSS but when the cursor moves, the balloon returned. I want the balloon to disappear forever until I refresh the page, so I guess it needs to be onclick, but that selector is not available in CSS.
Here's what I have in CSS
#keyframes pop
{
from{
opacity:1;
transform: translateZ(0) scale(1,1);
}
to{
opacity:0;
transform: translateZ(0) scale(1.5,1.5);
}
}
.balloon:hover
{
animation: pop 0.5s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
}
I saw another question that said the closest thing is :active but it requires the mouse to be held down. If I want it to be onclick, I need to use Javascript. But I don't know what I need to write to trigger the animation.
And is it also possible to make it so that when I pop 1 balloon, all the others will pop too automatically with a 1s delay inbetween? (There are 5 balloons).
You can add and remove the class of the animation with JS using classList.
Add:
object.classList.add('balloon');
Remove:
object.classList.remove('balloon');
Working example:
const add = () => {
document.getElementById('balloon').classList.add('animation')
}
const remove = () => {
document.getElementById('balloon').classList.remove('animation')
}
#keyframes pop {
from {
opacity: 1;
transform: translateZ(0) scale(1, 1);
}
to {
opacity: 0;
transform: translateZ(0) scale(1.5, 1.5);
}
}
.animation {
animation: pop 0.5s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
}
.balloon {
height: 125px;
width: 110px;
background-color: #FF6B6B;
border-radius: 50%;
}
.controls{
position: absolute;
bottom: 10px;
right: 10px;
}
<div id="balloon" class="balloon" onmouseover="add()"></div>
<div class="controls">
<button onClick="add()">Hide</button>
<button onClick="remove()">Show</button>
</div>
Here is a solution which makes balloons hiding one by one with interval .5s between them
var balloons = document.getElementsByClassName('balloon');
[...balloons].forEach( (e, i)=>{
e.onmouseover = function() {
this.classList.add('hidden');
setTimeout(hideAll, 500, balloons);
}
});
function hideAll(arg){
[...arg].forEach( (e, i)=>{
if ( ! e.classList.contains('hidden') ) {
e.style.animationDelay = i+'s';
e.classList.add('hidden');
}
});
}
#keyframes pop
{
from{
opacity:1;
transform: translateZ(0) scale(1,1);
}
to{
opacity:0;
transform: translateZ(0) scale(1.5,1.5);
}
}
.balloon.hidden
{
animation: pop .5s cubic-bezier(0.16, 0.87, 0.48, 0.99) forwards;
}
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>
<div class="balloon">Balloon</div>

IntersectionObserver: Trigger event when scrolling back to the top

I am starting to use the IntersectionObserver API and could create some basic animations, which includes hiding and appearing of elements. However, once a person wants to scroll back to the top, the elements which disappeared by one of the triggers are not getting visible again.
My Solution so far
So I thought I might create another test variable within the intersection observer callback function (the stepI and stepII variable in my code), which checks if the callback function was previously triggered. If so, instead of disappearing the elements, let them appear again.
My current problem
So let's say a background image (id="hiddenImg") should appear when the first text block (id="I") passes the 50% border of the viewport and it disappears when the second text block (id="II") enters this area. Even though the image is getting visible again when scrolling back up, if the user does not scroll back completely (so that the second text block goes out of the viewport) and then scrolls back to the bottom, the disappearing trigger of that second text block is not called. This would mean that the background image would stay visible, which it shouldn't.
Here is the js part:
var stepI = false;
var stepII = false;
// list of options
let options = {
rootMargin: '0px 0px -50%' //WHEN reaching half of the viewport
};
// instantiate a new Intersection Observer
"use strict";
var intersectionObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(function (change) {
if (change.isIntersecting) {
if (change.target.id == "I") {
$("#hiddenImg").removeClass("hidden_img");
$("#hiddenImg").addClass("visible_img");
stepI = true;
observer.unobserve(change.target);
}
if (change.target.id == "II") {
if (stepII == false) {
$("#hiddenImg").removeClass("visible_map");
$("#hiddenImg").addClass("hidden_map");
stepII = true
} else {
$("#hiddenImg").removeClass("hidden_map");
$("#hiddenImg").addClass("visible_map");
stepII = false;
}
}
}
});
},options);
// list of paragraphs
let elements = document.querySelectorAll(".stepper");
for (let elm of elements) {
intersectionObserver.observe(elm);
}
Here is my complete code:
<html>
<head>
<!-- Load the polyfill. -->
<script src="/js/intersection-observer.js"></script>
<script src='https://unpkg.com/intersection-observer#0.5.0/intersection-observer.js'></script>
</head>
<body>
<style>
.intro-imgs {
display: block;
margin: 0 auto; /* Will not center vertically and won't work in IE6/7. */
left: 0;
right: 0;
position: fixed;
position: expression(fixed);
}
.hidden_img {
visibility: hidden;
opacity: 0;
-ms-transform: scaleX(0); /* IE 9 */
-webkit-transform: scaleX(0); /* Safari 3-8 */
-o-transform: scaleX(0);
-moz-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: visibility 0s 0.5s, opacity 0.5s linear, -webkit-transform 0.5s;
-moz-transition: visibility 0s 0.5s, opacity 0.5s linear, -moz-transform 0.5s;
-o-transition: visibility 0s 0.5s, opacity 0.5s linear, -o-transform 0.5s;
transition: visibility 0s 0.5s, opacity 0.5s linear, transform 0.5s;
}
.visible_img {
visibility: visible;
opacity: 1;
-ms-transform: scaleX(1); /* IE 9 */
-webkit-transform: scaleX(1); /* Safari 3-8 */
-o-transform: scaleX(1);
-moz-transform: scaleX(1);
transform: scaleX(1);
-webkit-transition: opacity 0.5s linear, -webkit-transform 0.5s;
-moz-transition: opacity 0.5s linear, -moz-transform 0.5s;
-o-transition: opacity 0.5s linear, -o-transform 0.5s;
transition: opacity 0.5s linear, transform 0.5s;
}
.stepper{
max-width: 70rem;
margin: 550px auto 600px auto;
width: 90%;
background-color: rgba(248, 248, 248, 0.95);
font-family: "Helvetica";
font-size: 17px;
line-height: 26px;
padding: 15px;
}
</style>
<!--HTML-->
<div class="headline">
<img id="hiddenImg" class="hidden_img intro-imgs" src="https://image.shutterstock.com/image-photo/funny-portrait-hero-260nw-410898763.jpg" >
</div>
<div id="I" class="stepper">
<p>Lorem Ipsum</p>
</div>
<div id="II" class="stepper">
<h1>THE HEADLINE</h1>
</div>
<!-- SCRIPT-->
<script>
var stepI = false;
var stepII = false;
// list of options
let options = {
rootMargin: '0px 0px -50%' //WHEN reaching half of the viewport
};
// instantiate a new Intersection Observer
"use strict";
var intersectionObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(function (change) {
if (change.isIntersecting) {
if (change.target.id == "I") {
$("#hiddenImg").removeClass("hidden_img");
$("#hiddenImg").addClass("visible_img");
stepI = true;
observer.unobserve(change.target);
}
if (change.target.id == "II") {
if (stepII == false) {
$("#hiddenImg").removeClass("visible_map");
$("#hiddenImg").addClass("hidden_map");
stepII = true
} else {
$("#hiddenImg").removeClass("hidden_map");
$("#hiddenImg").addClass("visible_map");
stepII = false;
}
}
}
});
},options);
// list of paragraphs
let elements = document.querySelectorAll(".stepper");
for (let elm of elements) {
intersectionObserver.observe(elm);
}
</script>
</body>

React CSSTransition does not apply classes correctly

I'm working on a carousel application and I have to use the React Transition Group to animate it. For some reason I can't seem to get the classes to apply correctly.
It's a mix of proprietary and open-source code, so if this example isn't enough I'm glad to expand my examples.
React render() calls this:
{this.props.slides.map((slide, index) => (
<CSSTransition
key={this.index}
in={this.appearHome}
appear={true}
timeout={600}
classNames="carouselTransition"
>
<CarouselSlide
key={index}
index={index}
activeIndex={this.state.activeIndex}
slide={slide}
/>
</CSSTransition>
))}
And then the css looks like this:
/* appear on page load */
.carouselTransition-appear {
opacity: 0;
z-index: 1;
}
.carouselTransition-appear.carouselTransition-appear-active {
opacity: 1;
transition: opacity 600ms linear;
}
.carouselTransition-enter {
opacity: 0;
z-index: 1;
}
.carouselTransition-enter.CarouselTransition-enter-active {
opacity: 1;
transition: opacity 300ms linear;
}
.carouselTransition-exit {
opacity: 1;
}
.carouselTransition-exit.carouselTransition-exit-active {
opacity: 0;
transition: opacity 300ms linear;
}
.carouselTransition-exit-done {
opacity: 0;
}
The appear css applies but as I cycle the carousel I can see the enter and exit classes falling off of the divs, never to return. My suspicion is that I'm doing something wrong with the key={index} which I've read is an antipattern, but I'm not sure how to fix it.
Again, if more code is needed, say the word!
It's a tricky thing to work with, as I've heard from pretty much everywhere I turned to for help. They need to work on the documentation and build out some better examples! In any case, the CSS simply had the right things in the wrong places. Specifically, what I was trying to do in the done state needed to be done in the active state, and the stuff I was trying to do in the active state needs to be done in the enter state.
.carouselTransition-appear {
opacity: 1;
}
.carouselTransition-appear.carouselTransition-appear-active {
}
.carouselTransition-enter {
transform: translateX(110%);
}
.carouselTransition-enter.carouselTransition-enter-active {
transition: transform 600ms ease-in-out;
transform: translateX(0);
}
.carouselTransition-enter.carouselTransition-enter-done {
}
.carouselTransition-exit {
transform: translateX(0);
}
.carouselTransition-exit.carouselTransition-exit-active {
transition: transform 600ms ease-in-out;
transform: translateX(-100%);
}
.carouselTransition-exit-done {
left: -10000px;
opacity: 0;
height: 0px;
}

Categories