React useState with className and styled components hover out - reactjs

I want to implement an animation both on the hover in and out. For hover in I use a normal css :hover. For hover out I tried to make a default state outside of the :hover but this means that when the page loads it already applies it. This is what I tried.
const Container = styled.div`
animation-name: move-card-down;
animation-duration: 0.5s;
animation-fill-mode: forwards;
:hover {
animation-name: move-card-up;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
#keyframes move-card-up {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-1rem);
}
}
#keyframes move-card-down {
0% {
transform: translateY(-1rem);
}
100% {
transform: translateY(0);
}
}
`
So I figured I could instead apply a class to the div whenever a user hovers over it for the first time instead on a page load.
const [hover, setHover] = useState(false);
const onMouseOverCard = (): void => {
setHover(true);
};
const hovered = true === hover ? 'hovered' : '';
<Container onMouseOver={() => onMouseOverCard()} className={`${hovered}`}>
</Container>
const Container = styled.div`
.hovered {
animation-name: move-card-down;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
`
It applies my class to the section but it doesn't load the styles for it. Can someone tell me why this is?

I'm not sure about the transition part, but the toggling of classes should work fine, example: https://codesandbox.io/s/silent-butterfly-qocvd?file=/src/App.js:0-291
import { useState } from "react";
import styled from "styled-components";
import "./styles.css";
const MyDiv = styled.div`
color: red;
&.foobar {
outline: dashed;
}
`;
export default function App() {
const [klass, setKlass] = useState("");
return (
<MyDiv
className={klass}
onMouseEnter={() => setKlass("foobar")}
onMouseLeave={() => setKlass("")}
>
<h1>Hello CodeSandbox</h1>
</MyDiv>
);
}

Related

Passing a direction prop into styled-components keyframe component?

I'm trying to implement a reusable text animation component where a direction prop can be passed in. I probably close but doesn't seem to be working. Also open to better ways of implementing it a better way.
import React from "react"
import styled from "styled-components"
import { GlobalStyles } from "./global"
import TextAnimations from "./Components/TextAnimations"
const Container = styled.div`
display: flex;
justify-content: space-between;
flex-wrap: wrap;
`
const NavBar = styled.nav`
background: #3a3a55;
padding: 0.25rem;
width: 100%;
height: 10vh;
`
const Main = styled.main`
background: #3a3a55;
color: white;
padding: 0.25rem;
flex: 10 1 auto;
height: 100vh;
`
const Break = styled.div`
flex-basis: 100%;
width: 0;
`
function App() {
return (
<>
<GlobalStyles />
<Container>
<NavBar>NavBar</NavBar>
<Break />
<Main>
<TextAnimations text='Sample text from the left' direction='left' />
<TextAnimations text='Sample text from the right' direction='right' />
</Main>
</Container>
</>
)
}
export default App
and then the animation component:
import { motion } from "framer-motion"
import styled, { keyframes } from "styled-components"
type TextAnimationProps = {
text: string
direction: string
}
const Left = keyframes`
0% { left: -3.125em; }
100% { left: 3em;}
`
const Right = keyframes`
0% { Right: -3.125em; }
100% { Right: 3em;}
`
const HomeHeader = styled.div`
h1 {
font-weight: lighter;
}
position: relative;
top: 0;
animation: ${(props) => (props.defaultValue === "left" ? Left : Right)} // is this right?
animation-duration: 3s;
animation-fill-mode: forwards;
`
const TextAnimations = ({ text, direction }: TextAnimationProps) => {
return (
<HomeHeader defaultValue={direction}>
<h1>{text}</h1>
</HomeHeader>
)
}
export default TextAnimations
I'd also like to make it more flexible so that I can add 'top', 'bottom', etc.
Is this the best way to handle text animations?
You can create a separate function to set the animation. The function will receive the props of the styled component from which the function will access the direction prop.
const setHomeHeaderAnimation = ({ direction = "left" }) => {
const animation = keyframes`
0% {
${direction}: -3.125em;
}
100% {
${direction}: 3em;
}
`;
return css`
position: relative;
animation: ${animation};
animation-duration: 3s;
animation-fill-mode: forwards;
`;
};
const HomeHeader = styled.div`
${setHomeHeaderAnimation}
h1 {
font-weight: lighter;
}
`;
const App = () => (
<div>
<HomeHeader>
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="right">
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="top">
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="bottom">
<div>Some text</div>
</HomeHeader>
</div>
);

Why does this react-spring animation change its margin-top during an animation?

I have a react-spring animation that consists in make a component appear while sliding. But during this animation, the component change its margin top before going back to normal. How to fix this?
Here is the code & a sandbox:
import React, { useEffect, useMemo, useState } from "react";
import styled, { css, keyframes } from "styled-components";
import { animated, useTransition } from "react-spring";
const Tabs = styled.div`
display: flex;
margin-bottom: 12px;
`;
const Tab = styled.button<{ active: boolean }>`
margin: 0 4px;
border-bottom: 1px solid transparent;
font-weight: ${({ active }) => active && 600};
cursor: pointer;
background: transparent;
border: 0;
&:focus {
outline: none !important;
}
`;
export default function Inbox() {
const [tab, setTab] = useState(0);
const transitions = useTransition(tab, (p) => p, {
from: { opacity: 0, transform: "translate3d(100%,0,0)" },
enter: { opacity: 1, transform: "translate3d(0%,0,0)" },
leave: { opacity: 0, transform: "translate3d(-50%,0,0)" }
});
const getList = (name: string) => <div>{name}</div>;
const pages = [
({ style }) => (
<animated.div style={style}>{getList("test 1")}</animated.div>
),
({ style }) => (
<animated.div style={style}>{getList("test 2")}</animated.div>
)
];
return (
<>
<Tabs>
<Tab onClick={() => setTab(0)} active={tab === 0}>
Unread
</Tab>
<Tab onClick={() => setTab(1)} active={tab === 1}>
All
</Tab>
</Tabs>
{transitions.map(({ item, props, key }) => {
const Page = pages[item];
return <Page key={key} style={props} />;
})}
</>
);
}
The sandbox: https://codesandbox.io/s/amazing-ride-hwbv2?file=/src/App.tsx
It is not the margin top you see. It is the old component moving to the left side and changing opacity, but it is still there. Finally it is unmounted in that moment the new component is taking its vertical place. Most of the time we use absolute positionig that the old and new components are on top of each other. This way there is no bump at the unmount. Something like this:
from: { opacity: 0, transform: "translate3d(100%,0,0)", position: "absolute" },

Animation not working in styled-component using {keyframes}

I have a base component - Emoji.jsx
import styled from 'styled-components';
const StyledEmoji = styled.div`
font-size: 6rem;
cursor: pointer;
&:not(:last-child) {
margin-right: 3rem;
}
`;
function Emoji({ content, handleClick }) {
return (
<StyledEmoji onClick={() => handleClick(content)}>{content}</StyledEmoji>
);
}
export default Emoji;
I am extending this component and applying anmimation to it in EmojiBubble.jsx
import Emoji from './Emoji';
import styled, { keyframes } from 'styled-components';
const Bubble = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const EmojiBubble = styled(Emoji)`
animation: ${Bubble} 6s ease-in-out;
`;
export default EmojiBubble;
But, the animation is not working when I am using EmojiBubble component
<EmojiBubble content={emoji} /> //Emoji not rotating
What the issue here?
function Emoji({ content, handleClick })... doesn't pass the class name down to StyledEmoji, that's why styles created by EmojiBubble are not applied. All you need to do: pass the class name:
function Emoji({ content, handleClick, className }) {
return (
<StyledEmoji className={className} onClick={() => handleClick(content)}>
{content}
</StyledEmoji>
);
}
Working example

Creating a simple animation in React-Pose

I'm having trouble creating a simple animation in React-Pose. The two problems are
1) I can't get the animation to revert to the initial condition. The hovering variable is changing to false when the mouse leaves, but it the animation doesn't change back.
2) I can't manipulate the animation, I wanted to have a longer duration and maybe an ease out or something, but its just an instant snap to the hovered status.
import React, { useState } from 'react';
import styled from 'styled-components';
import posed from 'react-pose';
import { render } from 'react-dom';
const UpFor = () => {
const [hovering, setHovering] = useState(false);
const HoverContainer = posed.div({
hoverable: true
})
const Container = styled(HoverContainer)`
font-family: 'Baumans';
font-size: 220px;
display: flex;
cursor: pointer;
`
const Up = styled.div`
color: #81D6E3;`
const Four = styled.div`
color: #FF101F
`
const Fours = styled.div`
display: flex;
`
const MirroredFour = posed.div({
unhovered: {transform: 'rotatey(0deg)'},
hovered: {transform: 'rotateY(180deg)',
transition: {
type: 'tween',
duration: '2s'
}}
})
const SecondFour = styled(MirroredFour)`
color: #FF101F
position: absolute;
transform-origin: 67%;
`
return (
<Container onMouseEnter={() => {setHovering({ hovering: true }), console.log(hovering)}}
onMouseLeave={() => {setHovering({ hovering: false }), console.log(hovering)}}>
<Up>Up</Up><Fours><Four>4</Four>
<SecondFour pose={hovering ? "hovered" : "unhovered"}
>4</SecondFour></Fours>
</Container>)
}
export default UpFor
There were two main issues with your code:
duration does not appear to support string values like '2s'. I changed this to 2000.
You were defining your components (e.g. using styled.div, posed.div) inside of your render function. This caused these components to be treated by React as unique component types with each re-render. This results in those components being unmounted and re-mounted each render which prevents transitions from working since the element isn't changing -- instead it is being replaced by a new component of a different type.
Below is a working version of your code which moves the component definitions outside of the render (UpFor) function. You can play around with it in the sandbox provided.
import React, { useState } from "react";
import styled from "styled-components";
import posed from "react-pose";
const Container = styled.div`
font-family: "Baumans";
font-size: 220px;
display: flex;
cursor: pointer;
`;
const Up = styled.div`
color: #81d6e3;
`;
const Four = styled.div`
color: #ff101f;
`;
const Fours = styled.div`
display: flex;
`;
const MirroredFour = posed.div({
unhovered: { transform: "rotateY(0deg)" },
hovered: {
transform: "rotateY(180deg)",
transition: {
type: "tween",
duration: 2000
}
}
});
const SecondFour = styled(MirroredFour)`
color: #FF101F
position: absolute;
transform-origin: 67%;
`;
const UpFor = () => {
const [hovering, setHovering] = useState(false);
console.log("hovering", hovering);
return (
<Container
onMouseEnter={() => {
setHovering(true);
}}
onMouseLeave={() => {
setHovering(false);
}}
>
<Up>Up</Up>
<Fours>
<Four>4</Four>
<SecondFour pose={hovering ? "hovered" : "unhovered"}>4</SecondFour>
</Fours>
</Container>
);
};
export default UpFor;

React styled-components fade in/fade out

I am trying to build a React component to handle fading in and fading out. In the following code, if I pass out as a prop to the component, it is disaplayed as hidden before animating out. I'm trying to have it fade in by default, then fade out when I pass in the out prop. Anyone see a solution to this problem?
import React from 'react';
import styled, { keyframes } from 'styled-components';
const fadeIn = keyframes`
from {
transform: scale(.25);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
`;
const fadeOut = keyframes`
from {
transform: scale(1);
opacity: 0;
}
to {
transform: scale(.25);
opacity: 1;
}
`;
const Fade = styled.div`
${props => props.out ?
`display: none;`
: `display: inline-block;`
}
animation: ${props => props.out ? fadeOut : fadeIn} 1s linear infinite;
`;
function App() {
return (
<div>
<Fade><💅test></Fade>
</div>
);
}
export default App;
WebpackBin running example
The issue with your code is that you're setting the display property to none when props.out is true. That's why you're not seeing any animation, because before that can even start you've already hidden the component!
The way to do a fade out animation is to use the visibility property instead and transition that for the same amount of time as the animation takes. (see this old SO answer)
Something like this should solve your issues:
const Fade = styled.default.div`
display: inline-block;
visibility: ${props => props.out ? 'hidden' : 'visible'};
animation: ${props => props.out ? fadeOut : fadeIn} 1s linear;
transition: visibility 1s linear;
`;
const fadeIn = styled.keyframes`
from {
transform: scale(.25);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
`;
const fadeOut = styled.keyframes`
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(.25);
opacity: 0;
}
`;
const Fade = styled.default.div`
display: inline-block;
visibility: ${props => props.out ? 'hidden' : 'visible'};
animation: ${props => props.out ? fadeOut : fadeIn} 1s linear;
transition: visibility 1s linear;
`;
class App extends React.Component {
constructor() {
super()
this.state = {
visible: true,
}
}
componentDidMount() {
setTimeout(() => {
this.setState({
visible: false,
})
}, 1000)
}
render() {
return (
<div>
<Fade out={!this.state.visible}><💅test></Fade>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id="root" />
Note: Your fadeOut animation also went from 0 to 1 opacity, instead of the other way around. I've fixed that in the snippet too.

Resources