skip to Main Content

I am working on a countdown in css to display time left for certain metrics. Everything is working well, with the exception that I can only display the remaining time in seconds, instead of minutes.

From my understanding of countdown animations, they only accept time in seconds or milliseconds. My timer is for 25 minutes so I have it set for 1500 seconds and it works great. I cannot find a way to divide the results of my counter s by 60 to get the time to display in minutes using countdown content. I have also tired just basic calc(1 + 1) and trying to get that result to display in content, but have be unsuccessful. It would seem that any calculated result will down display in content. It felt like a variable type issue, but don’t believe CSS uses typed variables.

I have been trying to avoid adding JavaScript into this project, but would that be the recommendation to move forward or is there something I am missing with the CSS?

    @property --t {
      syntax: "<number>";
      initial-value: 1500;      
      inherits: true;
    }
    
    /* won't be needed once Chrome supports round() */
    @property --s {
      syntax: "<integer>";
      initial-value: 0;
      inherits: true;
    }
    
    .countdown {
      --s: calc(var(--t)/1);
      display: grid;
      margin: 1em auto;
      width: 20em;      
      height: 20em;     
      animation: t 1500s linear forwards ;    
    }
    
    .countdown::after {
      grid-column: 1;
      grid-row: 1;
      place-self: center;
      font: 5em/2 ubuntu mono, consolas, monaco, monospace; 
      counter-reset: s var(--s);     
      --k: counter(s);
      content: var(--k);            
    }
    <body>
    <div style="font: 5em/2 ubuntu mono, consolas, monaco, monospace;color:black;text-align: center;">Processing</div>
    <!-- partial:index.partial.html -->
    <div class="countdown">
      <svg viewBox="-50 -50 100 100" stroke-width="10">
        <circle r="45"></circle>
        <circle r="45" pathLength="1"></circle>
      </svg>
    </div>
    <!-- partial -->
      
    </body>

I started with this code and adapted to my needs. In this example the leading 0: appears to just be a string prefix for aesthetics.

https://codepen.io/thebabydino/pen/RwPNaEd

2

Answers


  1. It’s overall simple.

    Doing the calculation

    You need to calculate the minutes based on the seconds you got. The simple formula is --m: round(var(--s) / 60); where m is minutes and s is seconds. Because round is not globally available (yet), you have to use some tricks to emulate it. So the final formula would be something like this: --m: max(0, var(--t)/60 - 0.5); (the full explanation for the formula is here, gut in short the - 0.5 is used to round down, the max is used to prevent negatives when the number is below 1 and --m is defined as integer to round the value)

    If you want also to show the seconds the process is similar. The formula would be s = t – m * 60 which translated to css with the limitations we have would be --s: calc(var(--t) - var(--m)*60 - 0.5);

    Displaying both values

    finally you just have to define both minutes and seconds on your counter to display them like so: counter-reset: s var(--s) m var(--m);

    and then, instead of the ’00:’ which was burned in, we display the minutes like so: content: counter(m) ':' counter(s, decimal-leading-zero);

    if you only want to show the minutes, you can delete the seconds part like so: content: counter(m); (and remove the calculation as well)

    Fully working example

    and that’s it, this is a fully working example, based on the pen you provided (this is how it would look like with about 90 seconds set up)
    https://codepen.io/danicruz0415/pen/pomvzXe
    enter image description here

    Full code reference

    here is the full code in case the pen stops working. I dont put it like a runnable snipped because its done in scss and pug but you can adapt it later to your code, because the code you provided was not fully working

    - let d = 100, o = -.5*d;
    - let sw = .1*d, r = .5*(d - sw);
    
    .countdown
        svg(viewBox=[o, o, d, d].join(' ') stroke-width=sw)
            circle(r=r)
            circle(r=r pathLength='1')
    
    $d: 20em;
    $t: 90;
    $c: #8a9b0f #940a3d;
    
    @property --t {
        syntax: '<number>';
        initial-value: #{$t};
        inherits: true
    }
    
    @property --m {
      syntax: '<integer>';
      initial-value: 0;
      inherits: true;
    }
    
    /* won't be needed once Chrome supports round() */
    @property --s {
        syntax: '<integer>';
        initial-value: 0;
        inherits: true
    }
    
    .countdown {
        /* when Chrome supports round(), this can be simplified */
        /* more on this rounding trick
         * https://css-tricks.com/using-absolute-value-sign-rounding-and-modulo-in-css-today/ */
        --m: max(0, var(--t)/60 - 0.5);
      --s: calc(var(--t) - var(--m)*60 - 0.5);
        display: grid;
        margin: 1em auto;
        width: $d; height: $d;
        animation: t $t*1s linear infinite;
        
        &::after {
            grid-column: 1;
            grid-row: 1;
            place-self: center;
            font: #{.25*$d}/ 2 ubuntu mono, consolas, monaco, monospace;
            counter-reset: s var(--s) m var(--m);
            content: counter(m) ':' counter(s, decimal-leading-zero) 
        }
    }
    
    @keyframes t { to { --t: 0 } }
    
    svg {
        grid-column: 1;
        grid-row: 1
    }
    
    [r] {
        fill: none;
        stroke: silver;
        
        + [r] {
            --k: calc(var(--t)/#{$t});
            transform: rotate(-90deg);
            stroke-linecap: round;
            stroke: color-mix(in hsl shorter hue, #{nth($c, 1)} calc(var(--k)*100%), #{nth($c, 2)});
            stroke-dasharray: var(--k) 1
        }
    }
    
    Login or Signup to reply.
  2. I thought you might enjoy seeing how this could be accomplished with js instead of pure css solution.

    const wait = ms => new Promise(r => setTimeout(r, ms));
    
    const countdownEl = document.querySelector(".countdown-wrap");
    
    const setDeg = deg => {
      countdownEl.style.setProperty(
        "--deg",
        deg + "deg"
      );
    };
    
    const setColor = color => {
      countdownEl.style.setProperty(
        "--color",
        color
      );
    };
    
    const setPercent = percent => {
      const deg = 360 * percent / 100;
      setDeg(deg.toFixed(2));
      const green = 255 * percent / 100;
      const red = 255 * (1 - percent / 100);
      const rgb = `rgb(${red}, ${green}, 0)`;
      setColor(rgb);
    };
    
    const disableButtons = () => {
      const buttons = document.querySelectorAll("button");
      buttons.forEach(button => {
        button.disabled = true;
      });
    };
    
    const enableButtons = () => {
      const buttons = document.querySelectorAll("button");
      buttons.forEach(button => {
        button.disabled = false;
      });
    };
    
    const time = document.querySelector(".time-display");
    
    const countdown = async s => {
      disableButtons();
      let max = Number(s);
      let current = -1;
      while (current !== max) {
        current++;
        const remaining = max - current;
        setPercent(remaining / max * 100);
        time.innerText = remaining + "s";
        await wait(1000);
      }
      enableButtons();
    };
    
    const thirtyButton = document.querySelector("button.thirty");
    
    const fifteenButton = document.querySelector("button.fifteen");
    
    thirtyButton.addEventListener("click", () => {
      countdown(30);
    });
    
    fifteenButton.addEventListener("click", () => {
      countdown(15);
    });
    
    const customButton = document.querySelector("button.custom");
    const timeInput = document.querySelector("input");
    
    customButton.addEventListener("click", () => {
      countdown(timeInput.value);
    });
    body {
      margin: 0;
      display: grid; 
      place-items: center;
      align-content: center;
      gap: 1rem;
      height: 100dvh;
    }
    
    input {
      width: 5ch;
    }
    
    @property --deg {
      syntax: '<angle>';
      inherits: false;
      initial-value: 0deg;
    }
    
    @property --color {
      syntax: '<color>';
      inherits: false;
      initial-value: green;
    }
    
    .countdown-wrap {
      --size: 150px;
      --deg: 0deg;
      --color: rgb(0, 200, 0);
      width: var(--size);
      height: var(--size);
      border-radius: 50%;
      background: conic-gradient(
        var(--color) var(--deg), lightgrey var(--deg)
      );
      transition: --deg .2s linear, --color .2s linear;
    }
    
    .countdown-wrap {
      display: grid; 
      place-items: center;
    }
    
    .time-display {
      background: #fff;
      width: 80%;
      aspect-ratio: 1;
      border-radius:50%;
      display: grid;
      place-items: center;
    }
    <div class="countdown-wrap">
      <div class="time-display">0s</div>
    </div>
    <div class="button-wrap">
      <button class="fifteen">15s</button>
      <button class="thirty">30s</button> <br>
      <input min="1" value="5" type="number">       
      <button class="custom">start</button>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search