How I can animate circular progress bar from left to right?

前端 未结 2 1190
猫巷女王i
猫巷女王i 2020-12-11 09:51

I have a circle progress bar, only with HTML and CSS, I used keyframes for loading (animation). But the loading is from right to left I wanna reverse it. I edit my CSS keyfr

相关标签:
2条回答
  • 2020-12-11 10:31

    This progress works in new blink/webkit browsers since it uses conic-gradient(). In addition, to change the progress we use css variables, so animation will require JS.

    The idea is to create a conic gradient of black to transparent, and change the degrees according to the progress. To get a line instead of a circle, I use an inner gradient from white to white, that doesn't cover the border (background-clip: content-box) as suggested by @TemaniAfif.

    Play with the values of the input box to see the progress.

    const progress = document.querySelector('.circular-progress')
    
    const updateProgress = value => {
      progress.style.setProperty('--percentage', `${value * 3.6}deg`)
      progress.innerText = `${value}%`
    }
    
    updateProgress(36)
    
    document.querySelector('input')
      .addEventListener('input', e => {
        updateProgress(e.currentTarget.value)
      })
    .circular-progress {
      display: flex;
      width: 150px;
      height: 150px;
      border:5px solid transparent;
      border-radius: 50%;
      align-items: center;
      justify-content: center;
      font-size: 1.5em;
      background:
        linear-gradient(#fff, #fff) content-box no-repeat,
        conic-gradient(black var(--percentage,0), transparent var(--percentage,0)) border-box; 
      --percentage: 0deg;
    }
    <div class="circular-progress"></div>
    
    <br />
    
    Progress value: <input type="number" min="0" max="100" value="36">

    And for the other direction (added by @TemaniAfif):

    const progress = document.querySelector('.circular-progress')
    
    const updateProgress = value => {
      progress.style.setProperty('--percentage', `${value * 3.6}deg`)
      progress.innerText = `${value}%`
    }
    
    updateProgress(36)
    
    document.querySelector('input')
      .addEventListener('input', e => {
        updateProgress(e.currentTarget.value)
      })
    .circular-progress {
      display: flex;
      width: 150px;
      height: 150px;
      border:5px solid transparent;
      border-radius: 50%;
      align-items: center;
      justify-content: center;
      font-size: 1.5em;
      background:
        linear-gradient(#fff, #fff) content-box no-repeat,
        conic-gradient(from calc(-1*var(--percentage)), black var(--percentage,0), transparent var(--percentage,0)) border-box; 
      --percentage: 0deg;
    }
    <div class="circular-progress"></div>
    
    <br />
    
    Progress value: <input type="number" min="0" max="100" value="36">

    A variation on the same idea, is to create progress circle with multiple colors, and then hide it using a gradient from transparent to white. Make the transparent area bigger to expose the colored line.

    const progress = document.querySelector('.circular-progress')
    
    const updateProgress = value => {
      progress.style.setProperty('--percentage', `${value * 3.6}deg`)
      progress.innerText = `${value}%`
    }
    
    updateProgress(80)
    
    document.querySelector('input')
      .addEventListener('input', e => {
        updateProgress(e.currentTarget.value)
      })
    .circular-progress {
      display: flex;
      width: 150px;
      height: 150px;
      border: 5px solid transparent;
      border-radius: 50%;
      align-items: center;
      justify-content: center;
      font-size: 1.5em;
      background: 
        linear-gradient(#fff, #fff) content-box no-repeat,
        conic-gradient(transparent var(--percentage, 0), white var(--percentage, 0)) border-box,
        conic-gradient(green 120deg, yellow 120deg 240deg, red 240deg) border-box;
      --percentage: 0deg;
    }
    <div class="circular-progress"></div>
    
    <br /> Progress value: <input type="number" min="0" max="100" value="80">

    0 讨论(0)
  • 2020-12-11 10:41

    As I commented, the trivial solution is to rotate the whole animation:

    * {
     box-sizing:border-box;
    }
    .progress {
      width: 150px;
      height: 150px;
      background: none;
      margin: 0 auto;
      box-shadow: none;
      position: relative;
      transform: scaleX(-1);
    }
    
    .progress-value {
      transform: scaleX(-1);
    }
    
    .progress:after {
      content: "";
      width: 100%;
      height: 100%;
      border-radius: 50%;
      border: 3px solid #fff;
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0.5;
    }
    
    .progress>span {
      width: 50%;
      height: 100%;
      overflow: hidden;
      position: absolute;
      top: 0;
      z-index: 1;
    }
    
    .progress .progress-left {
      left: 0;
    }
    
    .progress .progress-bar {
      width: 100%;
      height: 100%;
      background: none;
      border-width: 2px;
      border-style: solid;
      position: absolute;
      top: 0;
    }
    
    .progress .progress-left .progress-bar {
      left: 100%;
      border-top-right-radius: 80px;
      border-bottom-right-radius: 80px;
      border-left: 0;
      -webkit-transform-origin: center left;
      transform-origin: center left;
    }
    
    .progress .progress-right {
      right: 0;
    }
    
    .progress .progress-right .progress-bar {
      left: -100%;
      border-top-left-radius: 80px;
      border-bottom-left-radius: 80px;
      border-right: 0;
      -webkit-transform-origin: center right;
      transform-origin: center right;
      animation: loading 1.8s linear forwards;
    }
    
    .progress .progress-value {
      width: 79%;
      height: 79%;
      border-radius: 50%;
      background: none;
      font-size: 24px;
      color: black;
      line-height: 135px;
      text-align: center;
      position: absolute;
      top: 5%;
      left: 5%;
    }
    
    .progress.one .progress-bar {
      border-color: black;
    }
    
    .progress.one .progress-left .progress-bar {
      animation: loading-1 1s linear forwards 1.8s;
    }
    
    @keyframes loading {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(180deg);
        transform: rotate(180deg);
      }
    }
    
    @keyframes loading-1 {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(90deg);
        transform: rotate(90deg);
      }
    }
    <div class="progress one">
      <span class="progress-left">
                    <span class="progress-bar"></span>
      </span>
      <span class="progress-right ">
                    <span class="progress-bar"></span>
      </span>
      <div class="progress-value">73%</div>
    </div>

    By the way here is another idea that rely on less code. The trick is to consider clip-path where you will adjust the position of the different points in order to create the needed animation

    .box {
      width:150px;
      height:150px;
      margin:20px;
      box-sizing:border-box;
      
      font-size:30px;
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
      z-index:0;
    }
    .box:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border:5px solid #000;
      border-radius:50%;
      transform:rotate(45deg);
      clip-path:polygon(50% 50%,0 0,0 0,0 0, 0 0,0 0);
      animation:change 2s linear forwards;
    }
    
    @keyframes change {
      25% {
        clip-path:polygon(50% 50%,0 0,   0 100%,0 100%,0 100%,0 100%);
      }
      50% {
        clip-path:polygon(50% 50%,0 0,0 100%,   100% 100%, 100% 100%,100% 100%);
      }
      75% {
        clip-path:polygon(50% 50%,0 0,0 100%,100% 100%,    100% 0,100% 0);
      }
      100% {
        clip-path:polygon(50% 50%,0 0,0 100%,100% 100%, 100% 0,     0% 0%);
      }
    }
    
    body {
     background:pink;
    }
    <div class="box">
      73%
    </div>

    To better understand the animation, add background and remove the radius. We basically have 6 points in the polygon where 2 are fixed (the center (50% 50%) and top one (0 0)) then we move the 4 others to put them in the corners. The trick is to move them together and we leave one at each corner (each state of the animation).

    .box {
      width:100px;
      height:100px;
      margin:50px;
      box-sizing:border-box;
      
      font-size:30px;
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
      z-index:0;
      background:rgba(0,0,0,0.5);
    }
    .box:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border:5px solid #000;
      background:red;
      transform:rotate(45deg);
      clip-path:polygon(50% 50%,0 0,0 0,0 0, 0 0,0 0);
      animation:change 5s linear forwards;
    }
    
    @keyframes change {
      25% {
        clip-path:polygon(50% 50%,0 0,   0 100%,0 100%,0 100%,0 100%);
      }
      50% {
        clip-path:polygon(50% 50%,0 0,0 100%,   100% 100%, 100% 100%,100% 100%);
      }
      75% {
        clip-path:polygon(50% 50%,0 0,0 100%,100% 100%,    100% 0,100% 0);
      }
      100% {
        clip-path:polygon(50% 50%,0 0,0 100%,100% 100%, 100% 0,     0% 0%);
      }
    
    }
    
    body {
     background:pink;
    }
    <div class="box">
      73%
    </div>

    With this code you have the full animation, simply adjust the final state or remove some states to stop it where you want.

    Ex with 75% (we remove the last state)

    .box {
      width:150px;
      height:150px;
      margin:20px;
      box-sizing:border-box;
      
      font-size:30px;
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
      z-index:0;
    }
    .box:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border:5px solid #000;
      border-radius:50%;
      transform:rotate(45deg);
      clip-path:polygon(50% 50%,0 0,0 0,0 0, 0 0,0 0);
      animation:change 3s linear forwards;
    }
    
    @keyframes change {
      33% {
        clip-path:polygon(50% 50%,0 0,   0 100%,0 100%,0 100%,0 100%);
      }
      66% {
        clip-path:polygon(50% 50%,0 0,0 100%,   100% 100%, 100% 100%,100% 100%);
      }
      100% {
        clip-path:polygon(50% 50%,0 0,0 100%,100% 100%,    100% 0,100% 0);
      }
    }
    body {
     background:pink;
    }
    <div class="box">
      75%
    </div>

    With 66% (we remove the last state and we change the percentage of the third one)

    .box {
      width:150px;
      height:150px;
      margin:20px;
      box-sizing:border-box;
      
      font-size:30px;
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
    }
    .box:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border:5px solid #000;
      border-radius:50%;
      transform:rotate(45deg);
      clip-path:polygon(50% 50%,0 0,0 0,0 0, 0 0,0 0);
      animation:change 2s linear forwards;
    }
    
    @keyframes change {
      33% {
        clip-path:polygon(50% 50%,0 0,   0 100%,0 100%,0 100%,0 100%);
      }
      66% {
        clip-path:polygon(50% 50%,0 0,0 100%,   100% 100%, 100% 100%,100% 100%);
      }
      100% {
        clip-path:polygon(50% 50%,0 0,0 100%,100% 100%,    100% 0,100% 40%);
      }
    }
    <div class="box">
      75%
    </div>

    with 10% (only one state)

    .box {
      width:150px;
      height:150px;
      margin:20px;
      box-sizing:border-box;
      
      font-size:30px;
      display:flex;
      align-items:center;
      justify-content:center;
      position:relative;
    }
    .box:before {
      content:"";
      position:absolute;
      z-index:-1;
      top:0;
      left:0;
      right:0;
      bottom:0;
      border:5px solid #000;
      border-radius:50%;
      transform:rotate(45deg);
      clip-path:polygon(50% 50%,0 0,0 0,0 0, 0 0,0 0);
      animation:change 1s linear forwards;
    }
    
    @keyframes change {
      100% {
        clip-path:polygon(50% 50%,0 0,   0 40%,0 40%,0 40%,0 40%);
      }
    }
    body {
     background:pink;
    }
    <div class="box">
      10%
    </div>

    0 讨论(0)
提交回复
热议问题