如何将圆形进度条从左到右动画化?

3
我有一个圆形进度条,只使用HTML和CSS实现,我使用关键帧进行加载动画。但是加载是从右到左的,我想要反向加载。我编辑了我的CSS关键帧,但没有任何效果。我还尝试过使用反向动画,但也无效。
示例:https://jsfiddle.net/d20wu8e4/ 我的结果(图片):https://ibb.co/0KCSsZY 我想要的效果(图片):https://ibb.co/MGCpHqS

* {
 box-sizing:border-box;
}
.progress {
  width: 150px;
  height: 150px;
  background: none;
  margin: 0 auto;
  box-shadow: none;
  position: relative;
}

.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="container bg-danger">
  <div class="row mt-5">
    <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>
  </div>
</div>


最好使用SVG,否则在尝试将其动画化到不同的百分比值时会感到头疼。 - Temani Afif
2
一个简单的解决方案是像这样旋转整个动画:https://jsfiddle.net/4wby1j2e/ - Temani Afif
1
@TemaniAfif:这只是通过添加 transform: scaleX(-1) 实现的吗?它是如何实现翻转的?我是通过否定变换值并在右侧而不是左侧放置延迟来实现的:https://jsfiddle.net/1bu8790f/1/ - Adrift
@TemaniAfif:非常酷!你的解决方案更加简洁 :) - Adrift
谢谢@TemaniAfif,你太棒了。现在一切都很出色。 - Carolyn Meete
显示剩余5条评论
2个回答

4

正如我所评论的那样,解决问题的简单方法是旋转整个动画:

* {
 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>

顺便提一下,还有一个使用更少代码的想法。诀窍是考虑clip-path,您需要调整不同点的位置以创建所需的动画。

.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>

为了更好地理解动画,添加背景并移除半径。我们基本上有6个点组成多边形,其中2个是固定的(中心点(50% 50%)和顶部点(0 0)),然后我们移动剩下的4个点将它们放在角落里。诀窍是一起移动它们,并且我们每次动画状态只保留一个点在每个角落。

.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>

使用此代码,您可以获得完整的动画效果,只需调整最终状态或删除一些状态,即可在所需位置停止。

例如,使用75%(我们删除了最后一个状态):

.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>

有 66% 的比例(我们排除最后一个状态并且更改第三个状态的百分比)

.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>

仅限一个州,10%的折扣

.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>


4
此进度仅在使用 conic-gradient() 的新的 blink/webkit 浏览器中有效。此外,为了更改进度,我们使用 CSS 变量,所以动画需要 JS。
这种方法的思路是创建一个从黑色到透明的圆锥渐变,并根据进度更改角度。为了得到一条线而不是一个圆形,我使用了一个从白色到白色的内部渐变,它不覆盖边框(background-clip: content-box),正如 @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(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">

对于另一个方向(由@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">

同一个思路的变化是,创建具有多种颜色的进度圆,并使用从透明到白色的渐变来隐藏它。将透明区域变大以显示彩色线条。

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">


1
您还可以使用基于background-origin/background-clip的多重背景技巧进行优化:https://jsfiddle.net/a91ebko8/1/ - Temani Afif
1
很好地运用了多个背景和剪裁。我已经使用优化更新了所有示例。 - Ori Drori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接