Search code examples
cssreactjstailwind-cssgradient

How to align the color with React and tailwind.css


I'm using React with Tailwind CSS and I'm trying to do a skill progress bar with gradient color but the color don't match at all when it reaches a certain percentage.

For example 15% of the full width, it should be all blue and not different colors. How can I fix it?

Here's the code of it

enter image description here

Tailwind CSS


.progressbar {
    /* Size */
    height: 10px;

    /* Content alignment */
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: stretch;

    /* Style */
    border-radius: 60px;
    background-color: #f0ecec;
    overflow: hidden;
}

.bar {
    /* Size */
    width: 0%;

    /* Style */
    background: rgb(255, 174, 105);
    background: linear-gradient(to right, #12B0FF 0%, #4399E7 7%, #5282DF 15%, #6565D7 22%, #951CC1 30%, #A517B8 37%, #A717B3 45%, #AF18A8 52%, #C83E82 60%, #CF4978 67%, #DE5E62 75%, #E86E51 84%, #F27D42 93%, #FA8838 100%);
}



.progressbar-text-container {
    /* Size */
    width: 30px;

    /* Content alignment */
    display: flex;
    flex-direction: row;

    /* Styling */
    font-weight: bold;
    font-size: 30px;
}

ProgressBar.js

import { motion, animate } from 'framer-motion';
import { useEffect, useState } from "react";

function Progressbar({ name, value })
{
    // Replaced useRef with useState
    const [progressText, setProgressText] = useState("0");

    useEffect(() =>
    {
        animate(parseInt(progressText), value, {
            duration: 1.5,
            onUpdate: (cv) =>
            {
                // Updating state instead of direct DOM manipulation
                setProgressText(cv.toFixed(0));
            }
        });
    }, [value]);

    return (
        <div className="progressbar-container py-2">
            <div className="flex py-1 justify-between">
                <p className="progress-bar-title">{name}</p>
                <div className="flex justify-end">
                    {/* Replaced ref with state variable */}
                    <p className="progress-bar-title">{progressText}</p>
                    <p className="progress-bar-title">%</p>
                </div>
            </div>
            <div className="progressbar">
                <motion.div
                    className="bar"
                    animate={{
                        width: `${value}%`,
                    }}
                    transition={{
                        duration: 1.5
                    }}
                />
            </div>
        </div>
    );
}

export default Progressbar;


Solution

  • Consider having all the .bar elements the same width of 100% so that their background gradients are the same size. Then, use clip-path:inset() to set their painted size:

    const { motion, animate } = Motion;
    const { useEffect, useState } = React;
    
    function Progressbar({ name, value })
    {
        // Replaced useRef with useState
        const [progressText, setProgressText] = useState("0");
    
        useEffect(() =>
        {
            animate(parseInt(progressText), value, {
                duration: 1.5,
                onUpdate: (cv) =>
                {
                    // Updating state instead of direct DOM manipulation
                    setProgressText(cv.toFixed(0));
                }
            });
        }, [value]);
    
        return (
            <div className="progressbar-container py-2">
                <div className="flex py-1 justify-between">
                    <p className="progress-bar-title">{name}</p>
                    <div className="flex justify-end">
                        {/* Replaced ref with state variable */}
                        <p className="progress-bar-title">{progressText}</p>
                        <p className="progress-bar-title">%</p>
                    </div>
                </div>
                <div className="progressbar">
                    <motion.div
                        className="bar"
                        animate={{
                            clipPath: `inset(0 ${100 - value}% 0 0)`,
                        }}
                        transition={{
                            duration: 1.5
                        }}
                    />
                </div>
            </div>
        );
    };
    
    function App() {
      return (
        <React.Fragment>
          <Progressbar value={100} name="foo"/>
          <Progressbar value={50} name="bar"/>
          <Progressbar value={25} name="baz"/>
        </React.Fragment>
      );
    }
    
    ReactDOM.createRoot(document.getElementById('app')).render(<App/>);
    .progressbar {
      /* Size */
      height: 10px;
      /* Content alignment */
      display: flex;
      flex-direction: row;
      justify-content: flex-start;
      align-items: stretch;
      /* Style */
      border-radius: 60px;
      background-color: #f0ecec;
      overflow: hidden;
    }
    
    .bar {
      /* Size */
      width: 100%;
      /* Style */
      background: rgb(255, 174, 105);
      background: linear-gradient(to right, #12B0FF 0%, #4399E7 7%, #5282DF 15%, #6565D7 22%, #951CC1 30%, #A517B8 37%, #A717B3 45%, #AF18A8 52%, #C83E82 60%, #CF4978 67%, #DE5E62 75%, #E86E51 84%, #F27D42 93%, #FA8838 100%);
      clip-path: inset(0 100% 0 0);
    }
    
    .progressbar-text-container {
      /* Size */
      width: 30px;
      /* Content alignment */
      display: flex;
      flex-direction: row;
      /* Styling */
      font-weight: bold;
      font-size: 30px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js" integrity="sha512-8Q6Y9XnTbOE+JNvjBQwJ2H8S+UV4uA6hiRykhdtIyDYZ2TprdNmWOUaKdGzOhyr4dCyk287OejbPvwl7lrfqrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js" integrity="sha512-MOCpqoRoisCTwJ8vQQiciZv0qcpROCidek3GTFS6KTk2+y7munJIlKCVkFCYY+p3ErYFXCjmFjnfTTRSC1OHWQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.jsdelivr.net/npm/framer-motion@10.16.1/dist/framer-motion.js" integrity="sha256-sS+KKDemoS6qg22Hi++wc5PAlgej08U6vDUKk/Y9K8Y=" crossorigin="anonymous"></script>
    <script src="https://cdn.tailwindcss.com/3.3.3"></script>
    
    <div id="app"></div>