Jumping
JSX
import React, { useEffect, useRef, useState } from "react"; import clsx from "clsx"; import useOnScreen from "../../hooks/useOnScreen"; import classes from "./style.module.scss"; const Jumping = ({ children, className, speed = 50 }) => { const ref = useRef(); const [reverse, setReverse] = useState(false); const [animating, setAnimating] = useState(false); const isOnScreen = useOnScreen(ref); const text = children.split(""); const timerLengthS = `${(text.length - 1) * 0.1}s`; const reverseAnimation = () => { setAnimating(false); const timer = setTimeout(() => { setAnimating(true); setReverse(!reverse); }, speed); return () => clearTimeout(timer); }; useEffect(() => { setAnimating(true); }, []); return ( <span className={clsx( classes.Jumping, animating && classes.Jumping_animation, isOnScreen && classes.Jumping_onScreen, className )} ref={ref} > {text.map((letter, i) => { if (letter.trim() === "") return " "; if (reverse ? i === 0 : text.length - 1 === i) { return ( <span key={`Jumping-${i}`} onAnimationEnd={() => reverseAnimation()} style={{ animationDelay: reverse ? `${text.length * 0.1 - 0.1 * i - 0.09}s` : `${0.1 * i}s`, animationDuration: timerLengthS, }} > {letter} </span> ); } return ( <span key={`Jumping-${i}`} style={{ animationDelay: reverse ? `${text.length * 0.1 - 0.1 * i - 0.09}s` : `${0.1 * i}s`, animationDuration: timerLengthS, }} > {letter} </span> ); })} </span> ); }; export default Jumping;
SCSS
.Jumping { $block: &; white-space: nowrap; &_animation span { animation: jump 1s alternate; } span { animation-play-state: paused; display: inline-block; } &_onScreen span { animation-play-state: running; @media (prefers-reduced-motion) { animation-play-state: paused; } } @keyframes jump { 0% { transform: none; } 50% { transform: translateY(-10px); } 100% { transform: none; } } }