An interactive animated counter slider built with React and framer-motion.
How to use it:
1. Import the necessary libraries.
import React, { useState, useEffect, useRef } from "https://cdn.skypack.dev/[email protected]"; import ReactDOM from "https://cdn.skypack.dev/[email protected]"; import * as framerMotion from "https://cdn.skypack.dev/[email protected]";
2. The main script.
const { motion } = framerMotion; const usePrevious = (value) => { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; }; const getBackgroundSize = (value) => { return { backgroundSize: `${(value * 100) / 1000}% 100%` }; }; const formatForDisplay = (number, includeDecimals) => { return parseFloat(Math.max(number, 0)) .toFixed(includeDecimals ? 2 : 0) .split("") .reverse(); }; const DecimalColumn = ({ fontSize, color }) => { return ( <div> <span style={{ fontSize: fontSize, lineHeight: fontSize, color: color }} > . </span> </div> ); }; const NumberColumn = ({ digit, delta, fontSize, color, incrementColor, decrementColor }) => { const [position, setPosition] = useState(0); const [animationClass, setAnimationClass] = useState(null); const previousDigit = usePrevious(digit); const columnContainer = useRef(); const setColumnToNumber = (number) => { setPosition(columnContainer.current.clientHeight * parseInt(number, 10)); }; useEffect(() => setAnimationClass(previousDigit !== digit ? delta : ""), [ digit, delta ]); useEffect(() => setColumnToNumber(digit), [digit]); return ( <div className="ticker-column-container" ref={columnContainer} style={{ fontSize: fontSize, lineHeight: fontSize, color: color, height: "auto", "--increment-color": incrementColor, "--decrement-color": decrementColor }} > <motion.div animate={{ x: 0, y: position }} className={`ticker-column ${animationClass}`} onAnimationComplete={() => setAnimationClass("")} > {[9, 8, 7, 6, 5, 4, 3, 2, 1, 0].map((num) => ( <div key={num} className="ticker-digit"> <span style={{ fontSize: fontSize, lineHeight: fontSize, color: color, }} > {num} </span> </div> ))} </motion.div> <span className="number-placeholder">0</span> </div> ); }; // Counter component const AnimatedCounter = ({ value = 0, fontSize = "18px", color = "white", incrementColor = "#32cd32", decrementColor = "#fe6862", includeDecimals = true }) => { const numArray = formatForDisplay(value, includeDecimals); const previousNumber = usePrevious(value); let delta = null; if (value > previousNumber) delta = "increase"; if (value < previousNumber) delta = "decrease"; return ( <motion.div layout className="ticker-view"> {numArray.map((number, index) => number === "." ? ( <DecimalColumn key={index} fontSize={fontSize} color={color} /> ) : ( <NumberColumn key={index} digit={number} delta={delta} fontSize={fontSize} incrementColor={incrementColor} decrementColor={decrementColor} includeDecimals={includeDecimals} /> ) )} </motion.div> ); }; // Main app component const App = () => { const [counterValue, setCounterValue] = useState(500.00); return ( <div className="App"> <AnimatedCounter value={counterValue} fontSize='48px'/> <input type="range" min="0" max="1000" onChange={(e) => setCounterValue(e.target.value)} step="0.01" style={getBackgroundSize(counterValue)} value={counterValue} /> </div> ); }; ReactDOM.render(<App />, document.getElementById("root"));
3. Necessary CSS styles.
.ticker-view { height: auto; display: flex; flex-direction: row-reverse; overflow: hidden; position: relative; } .number-placeholder { visibility: hidden; } .ticker-column-container { position: relative; } .ticker-column { position: absolute; height: 1000%; bottom: 0; } .ticker-digit { width: auto; height: 10%; } .ticker-column.increase { animation: pulseGreen 500ms cubic-bezier(0.4, 0, 0.6, 1) 1; } .ticker-column.decrease { animation: pulseRed 500ms cubic-bezier(0.4, 0, 0.6, 1) 1; } @keyframes pulseGreen { 0%, 100% { color: inherit; } 50% { color: var(--increment-color);; } } @keyframes pulseRed { 0%, 100% { color: inherit; } 50% { color: var(--decrement-color); } } input[type="range"] { margin-top: 24px; width: 250px; -webkit-appearance: none; height: 7px; background: rgba(255, 255, 255, 0.8); border-radius: 5px; background-image: linear-gradient(#73d46a, #73d46a); background-repeat: no-repeat; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; height: 20px; width: 20px; border-radius: 50%; background: #73d46a; cursor: pointer; box-shadow: 0 0 2px 0 #555; transition: background 0.3s ease-in-out; } input[type="range"]::-webkit-slider-runnable-track { -webkit-appearance: none; box-shadow: none; border: none; background: transparent; } input[type="range"]::-webkit-slider-thumb:hover { box-shadow: #73d46a50 0px 0px 0px 8px; } input[type="range"]::-webkit-slider-thumb:active { box-shadow: #73d46a50 0px 0px 0px 11px; transition: box-shadow 350ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, left 350ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, bottom 350ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; }
Preview:
Download Details:
Author: tuckermassad
Live Demo: View The Demo
Download Link: Download The Source Code
Official Website: https://codepen.io/tuckermassad/pen/XWxKWry
License: MIT