Why is this keyframe animation not applied when using styled-jsx? - javascript

I tried adding the animation on a separate line without any condition, but than the transition is not applied. I also tried backticks instead of double quotes for the animation property without success.
How to have the both the animation applied when clicked is false and play the transition for the radius when clicked is true?
import { useState } from "react";
export default function Home() {
const [clicked, setClicked] = useState(false);
return (
<>
<main>
<svg onClick={() => setClicked((c) => !c)}>
<circle cx="50%" cy="40%" stroke="black" strokeWidth={2} fill="gray" />
</svg>
</main>
<style jsx>{`
svg {
width: 100%;
height: 100%;
}
circle {
r: ${clicked ? "10%" : "5%"};
animation: ${clicked ? "none" : "bounce 2s infinite"};
transition: r 0.8s ease-in-out;
}
#keyframes bounce {
0% {
r: 5%;
}
50% {
r: 6%;
}
100% {
r: 5%;
}
}
`}</style>
</>
);
}

This has to do with how styled-jsx applies their css rules.
each <style jsx> tag will be transpiled by babel into a piece of js code that will generate and keep track of an actual <style type="txt/css"/> tag in the html.
That tag will contain a unique ID, if you inspect that tag it will look something like this:
<style type="text/css" data-styled-jsx="">
svg.jsx-1097321267 {
width: 100%;
height: 100%;
}
circle.jsx-1097321267 {
r: 5%;
-webkit-animation: bounce 2s infinite;
animation: bounce 2s infinite;
-webkit-transition: r 0.8s ease-in-out;
transition: r 0.8s ease-in-out;
}
#-webkit-keyframes bounce-jsx-1097321267 {
0% {
r: 5%;
}
50% {
r: 6%;
}
100% {
r: 5%;
}
}
#keyframes bounce-jsx-1097321267 {
0% {
r: 5%;
}
50% {
r: 6%;
}
100% {
r: 5%;
}
}
</style>
Notice how the animation is also generated with the same ID.
bounce-jsx-1097321267.
Any static references/classes in the styles-jsx tag also get this id.
This is all done through babel at compile time.
The resulting js code will do all that referencing for you.
A problem arises when a assigning the css code dynamically.
It seems, that the ${clicked ? "none" : "bounce 2s infinite"}; rule fails to add the generated id to bounce animation name.
This might be by design or might be a bug, or simply a limitation in styled-jsx. IDK.
You have a couple of options to work around this,
probably the easiest way is the make the css style static, and add a class when new styling should be applied.
IE
circle {
r: 5%;
animation: bounce 2s infinite;
transition: r 0.8s ease-in-out;
}
.is-clicked {
animation: none;
r: 10%;
}
and applying a class to circle like
className={clicked && "is-clicked"}
, that way the animation name will be contain an id, and any rule using that animation will also receive the same id.
code sandbox for reference

Related

How can i pass prop to sass keyframe percent from jsx

Hi in my react project i need to create dynamic keyframes. I need to pass percent variable's value from jsx code. How can i do it? My SCSS Code :
:root {
--percent: 95%;
--dim: 0.75;
--period: 1s;
--delay: 1s;
}
$percent: 95%;
$dim: var(--dim);
$period: var(--period);
$delay: var(--delay);
#mixin animation($percent,$dim, $period, $delay) {
#keyframes blink {
#{$percent} {
opacity: 0
}
100% {
opacity: $dim
}
}
animation: blink $period infinite $delay ;
}
.MarkerWithId{
#include animation($percent, $dim, $period, $delay)
}
I tried to change the percent with JavaScript after page render but it didnt make any change.And I cant use var(--percent) for $percent

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.

vue enter transition not working properly

i'm working on a project where i have to render some components with an enter and leave animation, when a component enters the screen it has to enter form the bottom, and when it leaves, it has to do it going upwards, the desired behavior is that when i change the :is property of the component tag, the current component goes upwards and the next one comes from the bottom, the code looks like this:
<template>
<div class="home">
<transition name="section">
<component :is="activeSection"></component>
</transition>
</div>
</template>
<script>
import comp1 from './comp1';
import comp2 from './comp2';
export default {
components: {
comp1,
comp2
},
data() {
activeSection: 'comp1'
}
</script>
<style scoped>
.section-enter {
top: 100vh;
}
.section-enter-to {
top: 0vh;
}
.section-enter-active {
animation-name: 'slideIn';
animation-duration: 1s;
}
.section-leave {
top: 0vh;
}
.section-leave-active {
animation-name: 'slideOut';
animation-duration: 1s;
}
.section-leave-to {
top: -100vh;
}
#keyframes slideIn {
from {
top: 100vh;
}
to {
top: 0
}
}
#keyframes slideOut {
from {
top: 0vh;
}
to {
top: -100vh;
}
}
</style>
but the actual behavior is that the first component goes upwards but the second appears inmediatly after without animation.
if i render one at a time (not destructing one and rendering another with the same action) everything works perfectly. I dont know what is happening.
There are a few problems in your CSS.
CSS Transitions and CSS Animations
A transition can be implemented using either CSS Transitions or CSS Animations. Your CSS incorrectly mixes the two concepts in this case.
In particular, the slideIn keyframes and .section-enter/.section-enter-to rules are effectively performing the same task of moving .section into view. However, this is missing a transition rule with a non-zero time, required to animate the change, so the change occurs immediately. The same issue exists for the slideOut keyframes and leave rules.
.section-enter {
top: 100vh;
}
.section-enter-to {
top: 0;
}
.section-enter-active {
transition: .5s; /* MISSING RULE */
}
.section-leave {
top: 0;
}
.section-leave-to {
top: -100vh;
}
.section-leave-active {
transition: .5s; /* MISSING RULE */
}
Removing the keyframes, and adding the missing rules (as shown above) would result in a working CSS Transition.
demo 1
Using CSS Animations
Alternatively, you could use keyframes with CSS Animations, where the animation is applied only by the *-active rules, and no *-enter/*-leave rules are used. Note your question contained unnecessary quotes in animation-name: 'slideIn';, which is invalid syntax and would be silently ignored (no animation occurs). I use a simpler shorthand in the following snippet (animation: slideIn 1s;).
.section-enter-active {
animation: slideIn 1s;
}
.section-leave-active {
animation: slideOut 1s;
}
#keyframes slideIn {
from {
top: 100vh;
}
to {
top: 0;
}
}
#keyframes slideOut {
from {
top: 0;
}
to {
top: -100vh;
}
}
demo 2
Optimizing CSS Transitions
You could also tweak your animation performance by using translateY instead of transitioning top.
/* top initially 0 in .wrapper */
.section-leave-active,
.section-enter-active {
transition: .5s;
}
.section-enter {
transform: translateY(100%);
}
.section-leave-to {
transform: translateY(-100%);
}
demo 3
Use a Mixin
Thanks for the explanation #tony19
please use a mixin for this so the logic can be repeated easily.
Also, your slideIn and slideOut can be combined by using reverse:
#mixin animationmixin($type:'animation', $style:'', $duration:1s) {
#keyframes #{$type}-#{$style} { // register animation
0% { opacity: 1; transform: none; } // reset style
100% { #content; } // custom style
}
.#{$style} { // add '.section'
&-enter-active, &-leave-active { // add '.section-enter-active', ...
transition: #{$duration};
}
&-enter, &-leave-to {
animation: #{$type}-#{$style} #{$duration}; // use animation
}
&-leave, &-enter-to {
animation: #{$type}-#{$style} #{$duration} reverse; // use animation in reverse
}
}
}
Use it like this:
#include animationmixin($style:'section') { // set custom styling
transform: translateY(100%);
};
And like this:
#include animationmixin($style:'fade') {
opacity: 0;
transform: scale(0.9);
};

Fade the same image in and out in one line of CSS automatically

I have an image that I want to fade in and out automatically. I've read about transitions and animations and would like to use one or two styles (not style declarations). It's OK to start the animation via JavaScript.
In this example on MDN you can see that the items are animated on page load by switching classes. I would like it to be simpler than that.
Here is what I have so far and it seems like it should work but it's not.
function updateTransition(id) {
var myElement = document.getElementById(id);
var opacity = myElement.style.opacity;
if (opacity==null || opacity=="") opacity = 1;
myElement.style.opacity = opacity==0 && opacity!="" ? 1 : 0;
}
var id = window.setInterval(updateTransition, 5000, "myElement");
updateTransition("myElement");
#myElement {
background-color:#f3f3f3;
width:100px;
height:100px;
top:40px;
left:40px;
font-family: sans-serif;
position: relative;
animation: opacity 3s linear 1s infinite alternate;
}
<div id="myElement"></div>
Also, here is an example of an animation on infinite loop using a slide animation (3 example in the list). I'd like the same but with opacity.
https://developer.mozilla.org/en-US/docs/Web/CSS/animation
The linked question is not the same as this. As I stated, "single line styles (not style declarations)".
What you need is to define your animation using keyframes. If you are trying to apply multiple animations, you can provide a list of parameters to the animation CSS properites. Here's an example that applies a slide in and fade animation.
.fade {
width:100px;
height:100px;
background-color:red;
position:relative;
animation-name:fadeinout, slidein;
animation-duration:2s, 1s;
animation-iteration-count:infinite, 1;
animation-direction:alternate, normal;
}
#keyframes fadeinout {
0% {
opacity:0
}
100% {
opacity:100
}
}
#keyframes slidein {
from {
left:-100px;
}
to {
left:0px;
}
}
<div class='fade'>
</div>
You can use animation-iteration-count :
#myElement {
background-color: #f3f3f3;
width: 100px;
height: 100px;
top: 40px;
left: 40px;
font-family: sans-serif;
position: relative;
animation: slidein 2s linear alternate;
animation-iteration-count: infinite;
}
#keyframes slidein {
0% {
opacity: 0;
left: -100px;
}
50% {
opacity: 1;
left: 40px;
}
100% {
opacity: 0;
left: -100px;
}
}
<div id="myElement"></div>

Continuous Horizontal CSS Scroll

I have a Create-React-app project that requires me to have some information constantly scroll across the screen. My current implementation:
import React from 'react'
import './styles/animation.css'
const Tickerbar = (props) => {
const displayNameStyle = {
color:'white',
}
const midStyle = {
color:'#CDBFBF',
marginRight:'20px'
}
const displayNames = Object.keys(props.tickers)
const displayNameAndMid = displayNames.map(cusip => {
return(
<span key={cusip}>
<span style={displayNameStyle}>
<b>{cusip.split('/').join('.')}: </b>
</span>
<span style={midStyle}>
<b>{(props.tickers[cusip]).toFixed(2)}</b>
</span>
</span>
)
})
return(
<div className="marquee">
<p>
{displayNameAndMid}
</p>
</div>
)
}
export default Tickerbar;
.marquee {
width: 100vw;
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
height: 51px;
position: absolute;
bottom: 10px;
}
.marquee p {
display: inline-block;
padding-left: 100%;
animation: marquee 100s linear 1s infinite;
-webkit-animation: marquee 100s linear 1s infinite;
color:white;
}
#keyframes marquee {
0% { transform: translate(-50%, 0); }
100% { transform: translate(-100%, 0); }
}
#-webkit-keyframes marquee {
0% { -webkit-transform: translate(-50%, 0); }
100% { -webkit-transform: translate(-100%, 0); }
The problem with this however is that the text scrolls across, then when it ends, I can see it start up again. Ideally, as the text is ending on the left side of the screen, it will start scrolling in again on the right side of the screen. I've seen some implementations that work by writing the text twice, and having the second set of text start scrolling halfway through the animation time, but when I try this the letters end up on top of each other and are unreadable.

Categories