How to stop a css animation in react [duplicate] - reactjs

This question already has answers here:
How to pause and resume CSS3 animation using JavaScript?
(5 answers)
Closed 2 years ago.
I have a problem where I have made my img rotate in a 360 continously but I want it to only do it when the music is playing. I am passing the value of isPlaying into the function but I do not know how to access the CSS value from React.
import React from "react";
const Song = ({ currentSong, isPlaying }: any) => {
return (
<div className="song-container">
<img id="image" alt={currentSong.name} src={currentSong.cover}></img>
<h2>{currentSong.name}</h2>
<h3>{currentSong.artist}</h3>
</div>
);
};
export default Song;
This is my CSS file:
.song-container {
min-height: 70vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 40%;
border-radius: 50%;
animation: spin 10s linear infinite;
animation-play-state: paused;
}
#keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
h2 {
padding: 3rem 1rem 1rem 1rem;
font-size: 3rem;
}
h3 {
font-size: 1.5rem;
}
}
I would just like to be able to stop it when the state is not playing and when it is playing I want the animation to play. I am also using typescript as I want to learn the language but if it is too hard to implement over I will just transition my website over to javascript.

There are two ways of doing this: inline styles and toggling some CSS class.
For inline styles method, you should set the style attribute for the img element, and the properties should be in camel case:
<img id="image" alt={currentSong.name} src={currentSong.cover} style={{ animationPlayState: isPlaying ? "running" : "paused" }} />
For the class toggling method, you should create a class in your CSS that changes the animation state.
For example, in the CSS:
img.animate {
animation-play-state: running;
}
Then, in the img element:
<img id="image" alt={currentSong.name} src={currentSong.cover} className={isPlaying ? "animate" : undefined} />
Toggling classes is a best practice, but both will work and inline styles are better when the styles are based on dynamic values you cannot predict.
Reference: https://reactjs.org/docs/dom-elements.html#style

Related

Styled Components Injects wrong classes on wrong elements

I'm witnessing a weird behavior when in styled-components with SSR in remix.run
I have a ProductCard Component that renders a normal product card with styled-components
ProductCard.tsx
import Button from "../Button";
function ProductCard({ product }: props) {
return (
<>
<Wrapper>
....
<ButtonsWrapper>
<Cart
onClick={addToCart}
mode={addedToCart ? "secondary" : "primary"}
disabled={loading}
key="cart-button"
>
{addedToCart ? "Added!" : "Add to cart"}
{loading && <LoadingSpinner src="/images/responses/loader.svg" />}
</Cart>
<ShareButton mode="secondary" aria-label="share">
<Icon id="share" />
</ShareButton>
</ButtonsWrapper>
</Wrapper>
</>
);
}
const Cart = styled(Button)`
flex: 1.1;
display: flex;
justify-content: center;
gap: 10px;
`;
const ShareButton = styled(Button)`
padding: 0.9rem;
`;
const Wrapper = styled.div`
--border-radius: ${clamp(15, 20)};
--columnGap: ${clamp(20, 30)};
display: flex;
flex-direction: column;
gap: var(--columnGap);
justify-content: space-between;
width: 100%;
height: 100%;
margin: auto;
background-color: var(--azure-15);
padding: 1.9rem 1.5rem;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow-lg);
border: var(--border-lg);
`;
const ButtonsWrapper = styled.div`
display: flex;
justify-content: space-between;
gap: 0.625rem;
`;
export default ProductCard;
Button.tsx
const Button = styled.button<{ mode: "primary" | "secondary" | "dark" }>`
display: grid;
/* justify-content: center; */
align-items: center;
text-align: center;
color: var(--mintCream);
padding: ${clamp(9, 10)} ${clamp(20, 30)}; // this clamp function just generates the css clamp func with calculating the values with some equations
box-shadow: var(--box-shadow-md);
border: var(--border-md);
border-radius: 12px;
text-decoration: none;
cursor: pointer;
transition: all 500ms ease;
font-size: ${clamp(13, 16)};
&:disabled {
cursor: not-allowed;
opacity: 0.7;
}
#media (hover: hover) and (pointer: fine) {
&:hover:enabled {
transform: translateY(-2px); }
}
width: fit-content;
`;
The normal render of this Component is as follows
But when navigating to another path and returning to it on / , it renders like this
This problem only happens in production and works fine on local server...
when inspecting elements, I find that the class name of the Cart Component is also injected into the ShareButton Element
I can't find an explanation for this problem and it gets weirder... When I swap the order of the variables Cart and ShareButton or swap them with the Wrapper Element, some other weird behaviors happen like the one below
In this case, the class name of the Cart Component got injected on the parent elemnt of the parent element of the ProductCard Component
I've probably hit on 4 of these rendering issues but all of them share the same problem, the class name of the Cart Components gets injected on a wrong dom element, whether it's a parent or a sibiling
You can view the first weird behaviour here https://store.ieeenu.com
You will find the product component on the root path, navigate to some path like categories/circuits-1-ecen101 and return to the root and you will see the issue
also, you can review the second weird behavior in a previous build here
https://ieee-nu-store-r243eocii-omarkhled.vercel.app/
I just changed the initialization order of the Cart and ShareButton Components as I said earlier
I don't know whether this problem is from styled-components or from remix (this is the first time for me using remix), it's mentioned here https://github.com/remix-run/remix/issues/1032 that the lack of the babel-plugin-styled-components in remix.run introduces some problems in rehydration but I'm not sure that this is the issue I'm facing...
Thanks for reading this till the end and excuse my English, I'm not a native speaker :"

How to pass boolean from html to scss mixin in react project

I don't know if this is possible with SCSS but I'm trying, how do I pass a react boolean variable of true to an scss mixin?
My React component,
Here I pass a variable from the react-html side to scss
type Props = {
isGrey?: boolean;
isOrange?: boolean;
};
export default function Button({ isGrey, isOrange }: Props) {
return (
<div className="button" style={{ '--isGrey': true }} >
{isGrey.toString()}
</div>
);
}
My Sass code,
Here I try to use the boolean passed from the React code in a mixin, but it doesn't work
#mixin button-style($isGrey: false) {
// #error $isGrey; // this prints "--isGrey" on screen, I need it to print true
#if $isGrey == true {
border: 2px solid #d5d5d5;
} #else {
border: 2px solid red;
}
}
.button {
#include button-style($isGrey: --isGrey);
height: 50px;
width: 100%;
border-radius: 4px;
}
Any help will be much appreciated...
Maybe I'm just oldskool, but I'd stay away from even attempting something like this and just stick with a classical way of changing the style of your elements, specifically something like a button.
I'd just have your base button style
.button {
height: 50px;
width: 100%;
border-radius: 4px;
}
Then make use of some additional classes to change the style based on the boolean.
.btn-primary {
border: 2px solid #d5d5d5;
}
.btn-secondary {
border: 2px solid red;
}
Within your component then just append the additional class based on the boolean.
return (
<div className=`button ${isGrey ? 'btn-Primary' : 'btn-Secondary'}`>
{isGrey.toString()}
</div>
);
It's cleaner and easier to read.

I want the arrow icon to flip up and down every time the state changes.and I want to animate it

I am using react, styled-components.
When state(visible) is set to true, DropMenu box1 and box2 will be displayed.
We want the ArrowDown icon to flip upward when state is true, and downward when false.
I also want to apply an animation when flipping it.
I want to add an animation like the Dropdown in the following site.  
Reference site
code
import "./styles.css";
import styled from "styled-components";
import React, { useState, useCallback } from "react";
import { ArrowDown } from "./ArrowDown";
const Item = styled.div<{ active?: boolean }>`
height: 40px;
width: 300px;
padding: 0px 30px;
&:hover {
background: #fafbfb;
}
`;
const DropMenu = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
color: #899098;
width: 100%;
height: 100%;
font-size: 14px;
font-weight: bold;
gap: 12px;
:hover {
color: gray;
}
div {
display: flex;
align-items: center;
gap: 12px;
}
`;
const DropText = styled.div`
padding-left: 32px;
`;
export const App = () => {
const [visible, setVisible] = useState(false);
const handleDropVisibleChange = useCallback(() => {
setVisible((prevVisible) => !prevVisible);
}, [visible]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Item onClick={handleDropVisibleChange}>
<DropMenu>
<div>
<span>Menu</span>
</div>
<ArrowDown />
</DropMenu>
</Item>
{visible && (
<div style={{ transition: "all 0.5s ease" }}>
<Item>
<DropMenu>
<DropText>box1</DropText>
</DropMenu>
</Item>
<Item>
<DropMenu>
<DropText>box2</DropText>
</DropMenu>
</Item>
</div>
)}
</div>
);
};
export default App;
TLDR
Change your MenuItem component warpper to something like
const DropMenuWrapper = styled.div<{ visible: boolean }>`
transition: all 0.5s ease;
opacity: ${(props) => (props.visible ? 1 : 0)};
`;
replace the visibility switch mechanism with following
- {visible && (
- <div style={{ transition: "all 0.5s ease" }}>
+ <DropMenuWrapper visible={visible}>
similar action can be added to the arrow-down icon also with style
(The ArrowDown SVG icon must accept style if it is custom written component)
<ArrowDown
style={{
transition: "all 0.5s ease",
transform: `rotate(${visible ? 0 : "0.5turn"})`
}}
/>
Why this happened:
When a component (sub-component/element) is mounted in react, it starts a complete life cycle toward browser paint.
So it is must have the property which causes the element to animate, for example, I added the opacity transition to the example itself, forcing it to animate in the first look and in disappearing.
Although it comes with some performance cost of having unseen elements still in the dom (but not visible), making it bad for accessibility too, it is the simplest way to achieve this behavior.
Consider this example If you have an animated element, does it show the animation if you refresh the browser if the answer is yes, it will show animation in react too.
Another way of doing some animation in react.
Using third-party library react-transtion-group which is heavily used in lots of packages e.g. Material-UI.
In this case you can also trigger the end event and start to unmount the component as the animation disappears and end completely.
Using framer motion
If you want to take your understanding of what is needed for the transition when the component is unmounted and removed from aka dom, I highly encourage you to read the animation section of svelte docuementation
What I did, what might look stupid to more advanced developers was implement a simple check that would switch icons.
Note: This doesn't have an animation, though. It's just a simple switcharoo
define state in component
const [isOpen, setIsOpen] = useState(false);
Check whether icon is open or closed, if open, ExpandLessIcon, if closed ExpandMoreIcon.
<ExpandLessIcon
onClick={() => {
setIsOpen(!isOpen);
}}
/>
) : (
<ExpandMoreIcon
onClick={() => {
setIsOpen(!isOpen);
}}
/>
)}
The way it works is, once clicked, it'll just flip the true false state over and over, which in turn will change icons.

Reactjs Module CSS multiple class selector

I'm trying to implement or add multiple class in out container when I click a button. But it seems that the styling is not being applied. Below are my code
Layout.module.css
.Content {
padding-left: 240px;
min-height: calc(100vh);
top: 0;
display: block;
position: relative;
padding-top: 80px;
background: #eee;
padding-bottom: 60px;
transition: padding-left 0.2s linear;
}
.Content.collapse {
padding-left: 100px;
display: block;
transition: padding-left 0.2s linear ;
}
Now I added the collapse class in my nav component like so
const layout = (props) => (
<Aux>
<Sidebar collapse={props.collapse} />
<div className={`${classes.Content} ${props.collapse ? 'collapse' : ''}`}>
<TopNavigation toggle={props.toggle}/>
{props.children}
<Footer />
</div>
</Aux>
);
So basically I'm just checking the props if it's collapse or not. If it is then I'll add a text collapse in the class.
Now when I click on a button it sets the state.collapse = true/false. It was able to do it's job. Now it seems that it's not reading my css style. Below is the generated class in my DOM
Notice the class .Content styling was detected. But as you can see here
Layout_Content__2WLOk collapse the div container has a class of collapse. So I was thinking it should read the .Content.collapse selector. Am I missing something here?
When using CSS modules, it creates a unique classname for each class for each instance of the component.
So you need to use the imported classes to have access to the generated classnames, just like you do for the .Content
So
<div className={`${classes.Content} ${props.collapse ? classes.collapse : ''}`}>
You are using a string not the generated hash
this part will not work
${props.collapse ? 'collapse' : ''}
Quick fix
Try not chaining it.
.collapse {
padding-left: 100px;
display: block;
transition: padding-left 0.2s linear ;
}
and add
classes.collapse instead of collapse
${props.collapse ? classes.collapse : ''}
In React you need to use the keyword 'className' instead of 'class'
https://reactjs.org/docs/dom-elements.html#classname
Also if you want to use CSS Modules you need to import your Layout.module.css file like this
import styles from './Layout.module.css';
And you can add CSS selector like this
<div className={styles.Content}></div>
you can study this here https://www.w3schools.com/react/react_css.asp

Styled components on hover change img src attribute

I have a styled component as below. It is a google social login button imgGoogleLogin is a path loaded by webpack.
I want to change the src attribute to another src when it is on hover.
Is there any way to achieve this? Thanks a lot
const GoogleLoginButton = styled.img.attrs({
src: imgGoogleLogin,
})`
width: 190px;
height: 45px;
&:hover {
cursor: pointer;
}
`;
I wanted to achieve something similar but I found that styled components cannot explicitly do this. I had to do it this way, ie create two components one hidden and when the parent is hovered I unhide it and hide the other one. Seems hacky but better than using e.setAttribute I think.
const GoogleLoginButton = styled.img.attrs(
props => ({'src': props.img})
)`
display: inline;
width: 190px;
height: 45px;
&:hover {
cursor: pointer;
}
`;
const GoogleLoginButtonHover = styled.img.attrs(
props => ({'src': props.img})
)`
display: none;
width: 190px;
height: 45px;
&:hover {
cursor: pointer;
}
`;
const GoogleLoginButtonParent = styled.div`
&:hover ${GoogleLoginButtonHover} {
display: inline;
}
&:hover ${GoogleLoginButton} {
display: none;
}
`;
In your render you use it like this:
<GoogleLoginButtonParent>
<GoogleLoginButton
img = {props.img}
/>
<GoogleLoginButtonHover
img = {props.imgHover}
/>
</GoogleLoginButtonParent>
You can achieve this with pure CSS:
By replacing your img tag with a div and setting its CSS as follow:
div {
background: url('to_first_image');
}
div:hover {
background: url('to_second_image');
}
If you rather keep your img tag and use some JS:
onHover = (e) => {
e.setAttribute('src', 'source_to_first_img');
}
onUnhover = (e) => {
e.setAttribute('src', 'source_to_second_img');
}
Credit to this answer : https://stackoverflow.com/a/18032363/10449875

Resources