仅使用 CSS 的复杂比例三元素进度条设计
Sophisticated proportional triple element progress bar design using only CSS
我制作了一个自定义进度条,由三个独立的部分组成(一个独特的可自定义中心部分、一个左侧部分和一个右侧部分),但我无法正确对齐中心块 在所有阶段。
首先,我将使用三种图形布局显示所需的最终状态,然后我将描述当前的问题,最后我将提供我当前的解决方法 hack,它有问题,需要某种修复。
三个理想状态:
起始状态的期望结果显示 1%
左对齐:
中间块完全位于 50%
的中间状态的预期结果:
中心块完美停止在 100%
右对齐的所需结束状态:
body{margin: 100px; background: #CCC}
.fullbar{
background-color: blue;
width: 100%;
}
.progress{
background: green;
margin: 10px 0 0 0;
text-align: right;
padding: 0 0px 1px 0px;
line-height: 5px;
}
.number{
background: inherit;
color: #FFF;
padding: 4px;
padding: 0 2px 1px 3px;
}
<div class="fullbar">
<div class="progress" style="width:50%">
<div class="number">50%</div>
</div>
</div>
问题
当状态为 50% 时,中心块应完全水平对齐在中间。
然而事实并非如此。行尾居中,而不是包含实际数字“50%”的 div。
PS。由于未知原因,中心块的主体未在代码视图中正确呈现。
也许我大量的 css 重置使我的进度条看起来与此处的裸代码不同。
但是关于 div 和 class 名称 number
需要正确居中,目前还没有。
我的 Hacky 解决方案,工作不正确且不优雅
我尝试用 width:112%
包裹中心块作为进度条的 hack,使中心块完全居中,如下所示:
<div class="fullbar">
<div style="width:112%">
<div class="progress" style="width:50%">
<div class="number">50%</div>
</div>
</div>
</div>
然而,虽然这确实使 50%
看起来完全水平居中,但最终状态 100%
现在被推到右边的 div 边界之外,使解决方案不正确并且无法使用。
主要问题和第一个赏金(50 分)
最好找到另一个 CSS(flex 或 calc)解决方案,其中所有三个理想状态(见上面的三张图片)完美对齐,其中状态适合开始状态、结束状态以及介于两者之间的所有状态“按比例。
奖金问题和第二个赏金(100 分)
A 部分)一种仅使用 CSS 动画进度(中心部分和左侧彩色条)的优雅方式,采用缓入缓出动作,页面加载后延迟 1 秒。
B 部分)动画(和数字)应从 0% 开始,中间部分显示的数字然后在动画期间增长到 XX%(无论在 html 中设置为 %)和以正确的进度编号和正确的水平进度位置结束。
你可以像下面那样做。我使用不同的颜色来更好地查看结果
body {
margin: 100px;
background: #CCC
}
.fullbar {
background-color: blue;
}
.progress {
background: lightgreen;
margin: 10px 0 0 0;
height: 5px;
position:relative; /* relative here */
width:var(--p);
}
.number {
position:absolute; /* absolute here */
background: rgba(255,0,0,0.5);
left:100%; /* push to the right side */
transform:translateX(calc(-1*var(--p))); /* offset to the left based on --p */
top:-10px;
bottom:-10px;
color: #FFF;
padding: 0 2px 1px 3px;
}
<div class="fullbar">
<div class="progress" style="--p:0%">
<div class="number">0%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:20%">
<div class="number">20%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:50%">
<div class="number">50%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:80%">
<div class="number">80%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:100%">
<div class="number">100%</div>
</div>
</div>
另一个只使用一个的想法div:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/var(--p) 100% no-repeat blue;
}
.progress::before {
content: attr(style);
font-family: monospace;
font-size:20px;
white-space:nowrap;
text-indent: -4ch;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: var(--p);
transform: translate(calc(-1*var(--p)),-50%);
color: #FFF;
}
<div class="progress" style="--p:0%"></div>
<div class="progress" style="--p:20%"></div>
<div class="progress" style="--p:50%"></div>
<div class="progress" style="--p:80%"></div>
<div class="progress" style="--p:100%"></div>
更新
有动画:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/var(--p) 100% no-repeat blue;
animation:p1 1s 1s both;
}
.progress::before {
content: attr(style);
font-family: monospace;
font-size:20px;
white-space:nowrap;
text-indent: -4ch;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: var(--p);
transform: translate(calc(-1*var(--p)),-50%);
color: #FFF;
animation:p2 1s 1s both;
}
@keyframes p1 {from {background-size:0 100%}}
@keyframes p2 {from {left:0;transform: translate(0%,-50%)}}
<div class="progress" style="--p:0%"></div>
<div class="progress" style="--p:20%"></div>
<div class="progress" style="--p:50%"></div>
<div class="progress" style="--p:80%"></div>
<div class="progress" style="--p:100%"></div>
对于数字动画,我会使用 @property
,但它目前仅在 chrome 边缘可用:
body {
margin: 100px;
background: #CCC
}
@property --p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@property --s {
syntax: '<integer>';
inherits: true;
initial-value: 0;
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/calc(var(--p,0)*1%) 100% no-repeat blue;
animation:p1 1s 1s both;
--s:var(--p);
counter-set:num var(--s);
}
.progress::before {
content: counter(num) "%";
font-family: monospace;
font-size:20px;
white-space:nowrap;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: calc(var(--p)*1%);
transform: translate(calc(-1%*var(--p)),-50%);
color: #FFF;
}
@keyframes p1 {from {--p:0;--s:0}}
<div class="progress" style="--p:0"></div>
<div class="progress" style="--p:20"></div>
<div class="progress" style="--p:50"></div>
<div class="progress" style="--p:80"></div>
<div class="progress" style="--p:100"></div>
直到有更多的支持,你可以像下面这样伪造它:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/var(--p) 100% no-repeat blue;
animation:p1 1s 1s both;
}
.progress::before {
content: attr(style);
font-family: monospace;
font-size:20px;
white-space:nowrap;
text-indent: -4ch;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: var(--p);
transform: translate(calc(-1*var(--p)),-50%);
color: #FFF;
animation:p2 1s 1s both,p3 0.8s 1s both;
}
@keyframes p1 {from {background-size:0% 100%}}
@keyframes p2 {from {left:0%;transform: translate(0%,-50%)}}
@keyframes p3 { /* put some randome number to fake the animation*/
0% {content:"--p:0%"}
15% {content:"--p:5%"}
30% {content:"--p:9%"}
45% {content:"--p:10%"}
60% {content:"--p:11%"}
75% {content:"--p:40%"}
90% {content:"--p:20%"}
}
<div class="progress" style="--p:0%"></div>
<div class="progress" style="--p:20%"></div>
<div class="progress" style="--p:50%"></div>
<div class="progress" style="--p:80%"></div>
<div class="progress" style="--p:100%"></div>
或者像下面这样的疯狂想法:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/calc(var(--p)*1%) 100% no-repeat blue;
animation:p1 1s 1s both;
}
.progress::before {
content: "0% \A 1% \A 2% \A 3% \A 4% \A 5% \A 6% \A 7% \A 8% \A 9% \A 10% \A 11% \A 12% \A 13% \A 14% \A 15% \A 16% \A 17% \A 18% \A 19% \A 20% \A 21% \A 22% \A 23% \A 24% \A 25% \A 26% \A 27% \A 28% \A 29% \A 30% \A 31% \A 32% \A 33% \A 34% \A 35% \A 36% \A 37% \A 38% \A 39% \A 40% \A 41% \A 42% \A 43% \A 44% \A 45% \A 46% \A 47% \A 48% \A 49% \A 50% \A 51% \A 52% \A 53% \A 54% \A 55% \A 56% \A 57% \A 58% \A 59% \A 60% \A 61% \A 62% \A 63% \A 64% \A 65% \A 66% \A 67% \A 68% \A 69% \A 70% \A 71% \A 72% \A 73% \A 74% \A 75% \A 76% \A 77% \A 78% \A 79% \A 80% \A 81% \A 82% \A 83% \A 84% \A 85% \A 86% \A 87% \A 88% \A 89% \A 90% \A 91% \A 92% \A 93% \A 94% \A 95% \A 96% \A 97% \A 98% \A 99% \A 100%";
font-family: monospace;
font-size:20px;
width:4ch;
line-height:1em;
height:1em;
text-align:center;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: calc(var(--p)*1%);
transform: translate(calc(-1%*var(--p)),-50%);
color: #0000;
text-shadow:0 calc(var(--p)*-1em) 0 #fff;
animation:p2 1s 1s both,p3 1s 1s steps(var(--p)) both;
}
@keyframes p1 {from {background-size:0% 100%}}
@keyframes p2 {from {left:0%;transform: translate(0%,-50%)}}
@keyframes p3 {from {text-shadow:0 0 0 #fff}}
<div class="progress" style="--p:0"></div>
<div class="progress" style="--p:20"></div>
<div class="progress" style="--p:50"></div>
<div class="progress" style="--p:80"></div>
<div class="progress" style="--p:100"></div>
@Temani's 和他的解决方案。他完成了所有的跑腿工作;我在这里添加到他的最后一个解决方案。
我改变的是:
- 进度条的左侧和右侧现在是两个独立的(伪)元素,并使用
flex-grow
增长到正确的比例 — calc
更少。
- 拇指不再需要手动定位。它通过使用
flex-box
和伪元素来定位。
- 拇指再次成为其自身的一个元素。这样,我可以让数字再次成为真实的文本,而不是
text-shadow
s。如果你想把这个东西变成一个滑块,你也可以用JS来捕捉事件。
- 删除了透明边框。我认为它有点太老套了。刚刚将行高从
1em
增加到 2em
并添加了 (2em - 1em) * -0.5 = -0.5em
. 的偏移量
body {
margin: 0;
padding: 20px;
background: #EEE;
font-family: sans-serif;
}
.progress {
display: flex;
align-items: center;
margin: 20px;
animation-delay: 1s;
animation-duration: 1s;
animation-fill-mode: forwards;
}
.progress::before, .progress::after {
content: '';
height: 10px;
animation: inherit;
}
.progress::before {
background-color: #1306F8;
flex-grow: 0;
animation-name: p_before;
}
.progress::after {
background-color: #E1E1E1;
flex-grow: 100;
animation-name: p_after;
}
.progress .thumb {
height: 1em;
padding: .3em .4em;
font-size: 20px;
line-height: 2em;
text-align: center;
color: #FFF;
background-color: #1306F8;
overflow: hidden;
z-index: 1;
animation: inherit;
}
.progress .thumb::before {
content: "0% \A 1% \A 2% \A 3% \A 4% \A 5% \A 6% \A 7% \A 8% \A 9% \A 10% \A 11% \A 12% \A 13% \A 14% \A 15% \A 16% \A 17% \A 18% \A 19% \A 20% \A 21% \A 22% \A 23% \A 24% \A 25% \A 26% \A 27% \A 28% \A 29% \A 30% \A 31% \A 32% \A 33% \A 34% \A 35% \A 36% \A 37% \A 38% \A 39% \A 40% \A 41% \A 42% \A 43% \A 44% \A 45% \A 46% \A 47% \A 48% \A 49% \A 50% \A 51% \A 52% \A 53% \A 54% \A 55% \A 56% \A 57% \A 58% \A 59% \A 60% \A 61% \A 62% \A 63% \A 64% \A 65% \A 66% \A 67% \A 68% \A 69% \A 70% \A 71% \A 72% \A 73% \A 74% \A 75% \A 76% \A 77% \A 78% \A 79% \A 80% \A 81% \A 82% \A 83% \A 84% \A 85% \A 86% \A 87% \A 88% \A 89% \A 90% \A 91% \A 92% \A 93% \A 94% \A 95% \A 96% \A 97% \A 98% \A 99% \A 100%";
position: relative;
display: block;
text-align: center;
white-space: pre-line;
margin-top: -0.5em;
top: 0;
animation: inherit;
animation-timing-function: steps(var(--p));
animation-name: p_thumb;
}
@keyframes p_before { to { flex-grow: var(--p) } }
@keyframes p_after { to { flex-grow: calc( 100 - var(--p)) } }
@keyframes p_thumb { to { top: calc(-2em * var(--p)) } }
<div class="progress" style="--p:0"><div class="thumb"></div></div>
<div class="progress" style="--p:20"><div class="thumb"></div></div>
<div class="progress" style="--p:40"><div class="thumb"></div></div>
<div class="progress" style="--p:60"><div class="thumb"></div></div>
<div class="progress" style="--p:80"><div class="thumb"></div></div>
<div class="progress" style="--p:100"><div class="thumb"></div></div>
就像我说的,Temani 做了跑腿工作,应该可以按计划收到赏金(奖励现有答案)。
到目前为止答案非常好,尤其是 Temani Afif 的文本缩进技巧。
我最初考虑做类似的事情,但想朝不同的方向前进。最后,我确定了一个解决方案,它利用新的 CSS Houdini @property
definition and some counter-reset
技巧将数字 CSS 自定义属性转换为字符串,然后我们可以在 content
属性我们添加的伪选择器
TL;DR
这是我的解决方案的完整代码片段,下面还有详细说明。
@property --progress-value {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
:root {
--progress-bar-color: #cfd8dc;
--progress-value-color: #2196f3;
--progress-empty-color-h: 4.1;
--progress-empty-color-s: 89.6;
--progress-empty-color-l: 58.4;
--progress-filled-color-h: 122.4;
--progress-filled-color-s: 39.4;
--progress-filled-color-l: 49.2;
}
html, body {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
margin: 0;
font-family: "Roboto Mono", monospace;
}
progress[value] {
display: block;
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 6px;
border: 0;
--border-radius: 10px;
border-radius: var(--border-radius);
counter-reset: progress var(--progress-value);
--progress-value-string: counter(progress) "%";
--progress-max-decimal: calc(var(--value, 0) / var(--max, 0));
--progress-value-decimal: calc(var(--progress-value, 0) / var(--max, 0));
--progress-value-percent: calc(var(--progress-value-decimal) * 100%);
--progress-value-color: hsl(
calc((var(--progress-empty-color-h) + (var(--progress-filled-color-h) - var(--progress-empty-color-h)) * var(--progress-value-decimal)) * 1deg)
calc((var(--progress-empty-color-s) + (var(--progress-filled-color-s) - var(--progress-empty-color-s)) * var(--progress-value-decimal)) * 1%)
calc((var(--progress-empty-color-l) + (var(--progress-filled-color-l) - var(--progress-empty-color-l)) * var(--progress-value-decimal)) * 1%)
);
-webkit-animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
}
@supports selector(::-moz-progress-bar) {
progress[value] {
--progress-value-decimal: calc(var(--value, 0) / var(--max, 0));
}
}
progress[value]::-webkit-progress-bar {
background-color: var(--progress-bar-color);
border-radius: var(--border-radius);
overflow: hidden;
}
progress[value]::-webkit-progress-value {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::-moz-progress-bar {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::after {
display: flex;
align-items: center;
justify-content: center;
--size: 32px;
width: var(--size);
height: var(--size);
position: absolute;
left: var(--progress-value-percent);
top: 50%;
transform: translate(-50%, -50%);
background-color: var(--progress-value-color);
border-radius: 50%;
content: attr(value);
content: var(--progress-value-string, var(--value));
font-size: 12px;
font-weight: 700;
color: #fff;
}
@-webkit-keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
@keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
<progress value="0" max="100" style="--value: 0; --max: 100;"></progress>
<progress value="25" max="100" style="--value: 25; --max: 100;"></progress>
<progress value="50" max="100" style="--value: 50; --max: 100;"></progress>
<progress value="75" max="100" style="--value: 75; --max: 100;"></progress>
<progress value="100" max="100" style="--value: 100; --max: 100;"></progress>
CodePen Link: cdpn.io/e/RwpyZGo
最终产品(屏幕截图,单击上面附加代码片段底部的“运行 片段”以查看动画效果)。
详细解释
HTML 已经有一个内置的 <progress>
element with several pseudo-elements included, so I really wanted to stick with using that and styling around it. This turned out to be largely successful when combined with CSS Houdini's new @property
定义,它允许我们创建更多的动态动画等等。
事实上,Temani Afif 对这个问题发表了另一个很好的回答,在这里写了一篇很棒的文章(Temani Afif 的We can finally animate CSS gradient)。
不仅使用新的 @property
定义允许我们为进度条的实际值设置动画,我们可以使用它来更改进度条内进度值的宽度以及 %
标签,但它也允许我们随着进度变化生成动态颜色变化。
在我下面的示例中,我选择从红色过渡到绿色以表示进度。如果您更喜欢使用单一颜色而不是这种不断变化的颜色,只需将所有 --progress-value-color
HSL 值替换为单一颜色值即可。
同样,我在animation
行中使用了一个calc()
来调整每个进度条动画的animation-duration
以相同的速度移动,而不是所有进度bar 同时开始和结束它们的动画,每个进度条同时传递相同的值。这意味着,如果两个进度条达到 50% 并且其中一个的值为 50%,则该进度条将停止动画,而另一个将继续以其新值进行动画。
如果您希望所有进度条同步开始和结束,只需将 calc()
替换为单个 <time>
值(例如 750ms
、3s
等)。
@property --progress-value {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
:root {
--progress-bar-color: #cfd8dc;
--progress-value-color: #2196f3;
--progress-empty-color-h: 4.1;
--progress-empty-color-s: 89.6;
--progress-empty-color-l: 58.4;
--progress-filled-color-h: 122.4;
--progress-filled-color-s: 39.4;
--progress-filled-color-l: 49.2;
}
html, body {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
margin: 0;
font-family: "Roboto Mono", monospace;
}
progress[value] {
display: block;
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 6px;
border: 0;
--border-radius: 10px;
border-radius: var(--border-radius);
counter-reset: progress var(--progress-value);
--progress-value-string: counter(progress) "%";
--progress-max-decimal: calc(var(--value, 0) / var(--max, 0));
--progress-value-decimal: calc(var(--progress-value, 0) / var(--max, 0));
--progress-value-percent: calc(var(--progress-value-decimal) * 100%);
--progress-value-color: hsl(
calc((var(--progress-empty-color-h) + (var(--progress-filled-color-h) - var(--progress-empty-color-h)) * var(--progress-value-decimal)) * 1deg)
calc((var(--progress-empty-color-s) + (var(--progress-filled-color-s) - var(--progress-empty-color-s)) * var(--progress-value-decimal)) * 1%)
calc((var(--progress-empty-color-l) + (var(--progress-filled-color-l) - var(--progress-empty-color-l)) * var(--progress-value-decimal)) * 1%)
);
-webkit-animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
}
@supports selector(::-moz-progress-bar) {
progress[value] {
--progress-value-decimal: calc(var(--value, 0) / var(--max, 0));
}
}
progress[value]::-webkit-progress-bar {
background-color: var(--progress-bar-color);
border-radius: var(--border-radius);
overflow: hidden;
}
progress[value]::-webkit-progress-value {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::-moz-progress-bar {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::after {
display: flex;
align-items: center;
justify-content: center;
--size: 32px;
width: var(--size);
height: var(--size);
position: absolute;
left: var(--progress-value-percent);
top: 50%;
transform: translate(-50%, -50%);
background-color: var(--progress-value-color);
border-radius: 50%;
content: attr(value);
content: var(--progress-value-string, var(--value));
font-size: 12px;
font-weight: 700;
color: #fff;
}
@-webkit-keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
@keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
<progress value="0" max="100" style="--value: 0; --max: 100;"></progress>
<progress value="25" max="100" style="--value: 25; --max: 100;"></progress>
<progress value="50" max="100" style="--value: 50; --max: 100;"></progress>
<progress value="75" max="100" style="--value: 75; --max: 100;"></progress>
<progress value="100" max="100" style="--value: 100; --max: 100;"></progress>
CodePen Link: cdpn.io/e/RwpyZGo
对于每个进度条,我们需要将 value
和 max
都声明为属性以及 CSS 自定义属性(变量),这当然不理想。但是,CSSWG 目前正在对 attr()
进行一些不同的改进,这将使我们能够很快以任何指定格式访问这些属性值,而无需另外使用 CSS 自定义属性,因为我在上面的例子中做了。
浏览器支持 attr()
正如您在官方 MDN docs on attr()
的浏览器支持部分中看到的那样,目前很少有浏览器支持 attr()
的这些附加功能,例如回退和 type-or -单元。我们还需要能够在任何 CSS 属性 中使用 attr()
,尤其是 CSS 自定义属性,而不仅仅是 content
属性,为了完全摆脱 CSS 自定义属性解决方法。
这些改进目前处于“编辑草案”状态,没有生产浏览器支持,但这最早可能在明年发生变化。所以现在,除了属性之外,我们还需要使用 CSS 自定义属性。此外,Firefox 尚不支持这个新的 属性 定义,但我的解决方案包括一个 @supports
查询回退,它仍然可以确保进度条的宽度正确,并根据它们的值使用正确的颜色.
一旦所有这些 CSS 和 Houdini 更新在所有主要浏览器上可用,希望明年,所有这些都将可以通过本机 HTML 属性实现,如下所示:
<progress value="0" max="100"></progress>
<progress value="25" max="100"></progress>
<progress value="50" max="100"></progress>
<progress value="75" max="100"></progress>
<progress value="100" max="100"></progress>
到那时,我们将能够在 CSS这样:
progress[value] {
--value: attr(value number);
}
progress[max] {
--max: attr(max number);
}
其余逻辑将保持不变。有关 attr()
的更多详细信息,请在此处参考 MDN 文档:attr()
除了我的其他更自定义的解决方案之外,我还想 post 一个专门回答您最初提出的问题的答案,而不是提供不同或替代的解决方案。
你最里面的元素没有完全居中的原因是它的 div 本质上占据了其父级宽度的 100%,由于内联 width
应用到它的样式。文本是右对齐的,这意味着文本的右侧将紧靠中间的 50% 标记,但它不会完全位于 .fullbar
元素的中心。
在不太习惯的情况下,我个人更喜欢使 .number
元素绝对定位 (position: absolute
),以便其祖先元素的大小完全不受其自身大小的影响。这样就独立了。
以下是我们如何实现的:
html, body { height: 100%; }
body {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0 20px;
background-color: #ccc;
}
.fullbar {
background-color: blue;
width: 100%;
height: 10px;
}
.progress {
position: relative;
background: green;
height: 100%;
}
.number {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 0;
top: 50%;
transform: translate(50%, -50%);
background: inherit;
color: #fff;
padding: 4px;
padding: 0 2px 1px 3px;
}
<div class="fullbar">
<div class="progress" style="width: 50%">
<div class="number">50%</div>
</div>
</div>
最后,如果您确实希望使用 CSS 自定义属性使您的原始解决方案作为一行工作,我们可以这样做:
html, body { height: 100%; }
body {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0 20px;
background-color: #ccc;
}
.progress {
counter-reset: progress var(--value);
--value-percent: calc(var(--value) * 1%);
position: relative;
width: 100%;
height: 10px;
background-color: blue;
}
.progress::before {
content: '';
display: block;
position: relative;
width: var(--value-percent);
height: 100%;
background: green;
}
.progress::after {
content: counter(progress) '%';
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: var(--value-percent);
top: 50%;
transform: translate(-50%, -50%);
background: green;
color: #fff;
padding: 2px 4px;
}
<div class="progress" style="--value: 50"></div>
我制作了一个自定义进度条,由三个独立的部分组成(一个独特的可自定义中心部分、一个左侧部分和一个右侧部分),但我无法正确对齐中心块 在所有阶段。
首先,我将使用三种图形布局显示所需的最终状态,然后我将描述当前的问题,最后我将提供我当前的解决方法 hack,它有问题,需要某种修复。
三个理想状态:
起始状态的期望结果显示 1%
左对齐:
中间块完全位于 50%
的中间状态的预期结果:
中心块完美停止在 100%
右对齐的所需结束状态:
body{margin: 100px; background: #CCC}
.fullbar{
background-color: blue;
width: 100%;
}
.progress{
background: green;
margin: 10px 0 0 0;
text-align: right;
padding: 0 0px 1px 0px;
line-height: 5px;
}
.number{
background: inherit;
color: #FFF;
padding: 4px;
padding: 0 2px 1px 3px;
}
<div class="fullbar">
<div class="progress" style="width:50%">
<div class="number">50%</div>
</div>
</div>
问题
当状态为 50% 时,中心块应完全水平对齐在中间。
然而事实并非如此。行尾居中,而不是包含实际数字“50%”的 div。
PS。由于未知原因,中心块的主体未在代码视图中正确呈现。
也许我大量的 css 重置使我的进度条看起来与此处的裸代码不同。
但是关于 div 和 class 名称 number
需要正确居中,目前还没有。
我的 Hacky 解决方案,工作不正确且不优雅
我尝试用 width:112%
包裹中心块作为进度条的 hack,使中心块完全居中,如下所示:
<div class="fullbar">
<div style="width:112%">
<div class="progress" style="width:50%">
<div class="number">50%</div>
</div>
</div>
</div>
然而,虽然这确实使 50%
看起来完全水平居中,但最终状态 100%
现在被推到右边的 div 边界之外,使解决方案不正确并且无法使用。
主要问题和第一个赏金(50 分)
最好找到另一个 CSS(flex 或 calc)解决方案,其中所有三个理想状态(见上面的三张图片)完美对齐,其中状态适合开始状态、结束状态以及介于两者之间的所有状态“按比例。
奖金问题和第二个赏金(100 分)
A 部分)一种仅使用 CSS 动画进度(中心部分和左侧彩色条)的优雅方式,采用缓入缓出动作,页面加载后延迟 1 秒。
B 部分)动画(和数字)应从 0% 开始,中间部分显示的数字然后在动画期间增长到 XX%(无论在 html 中设置为 %)和以正确的进度编号和正确的水平进度位置结束。
你可以像下面那样做。我使用不同的颜色来更好地查看结果
body {
margin: 100px;
background: #CCC
}
.fullbar {
background-color: blue;
}
.progress {
background: lightgreen;
margin: 10px 0 0 0;
height: 5px;
position:relative; /* relative here */
width:var(--p);
}
.number {
position:absolute; /* absolute here */
background: rgba(255,0,0,0.5);
left:100%; /* push to the right side */
transform:translateX(calc(-1*var(--p))); /* offset to the left based on --p */
top:-10px;
bottom:-10px;
color: #FFF;
padding: 0 2px 1px 3px;
}
<div class="fullbar">
<div class="progress" style="--p:0%">
<div class="number">0%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:20%">
<div class="number">20%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:50%">
<div class="number">50%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:80%">
<div class="number">80%</div>
</div>
</div>
<div class="fullbar">
<div class="progress" style="--p:100%">
<div class="number">100%</div>
</div>
</div>
另一个只使用一个的想法div:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/var(--p) 100% no-repeat blue;
}
.progress::before {
content: attr(style);
font-family: monospace;
font-size:20px;
white-space:nowrap;
text-indent: -4ch;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: var(--p);
transform: translate(calc(-1*var(--p)),-50%);
color: #FFF;
}
<div class="progress" style="--p:0%"></div>
<div class="progress" style="--p:20%"></div>
<div class="progress" style="--p:50%"></div>
<div class="progress" style="--p:80%"></div>
<div class="progress" style="--p:100%"></div>
更新
有动画:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/var(--p) 100% no-repeat blue;
animation:p1 1s 1s both;
}
.progress::before {
content: attr(style);
font-family: monospace;
font-size:20px;
white-space:nowrap;
text-indent: -4ch;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: var(--p);
transform: translate(calc(-1*var(--p)),-50%);
color: #FFF;
animation:p2 1s 1s both;
}
@keyframes p1 {from {background-size:0 100%}}
@keyframes p2 {from {left:0;transform: translate(0%,-50%)}}
<div class="progress" style="--p:0%"></div>
<div class="progress" style="--p:20%"></div>
<div class="progress" style="--p:50%"></div>
<div class="progress" style="--p:80%"></div>
<div class="progress" style="--p:100%"></div>
对于数字动画,我会使用 @property
,但它目前仅在 chrome 边缘可用:
body {
margin: 100px;
background: #CCC
}
@property --p {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
@property --s {
syntax: '<integer>';
inherits: true;
initial-value: 0;
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/calc(var(--p,0)*1%) 100% no-repeat blue;
animation:p1 1s 1s both;
--s:var(--p);
counter-set:num var(--s);
}
.progress::before {
content: counter(num) "%";
font-family: monospace;
font-size:20px;
white-space:nowrap;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: calc(var(--p)*1%);
transform: translate(calc(-1%*var(--p)),-50%);
color: #FFF;
}
@keyframes p1 {from {--p:0;--s:0}}
<div class="progress" style="--p:0"></div>
<div class="progress" style="--p:20"></div>
<div class="progress" style="--p:50"></div>
<div class="progress" style="--p:80"></div>
<div class="progress" style="--p:100"></div>
直到有更多的支持,你可以像下面这样伪造它:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/var(--p) 100% no-repeat blue;
animation:p1 1s 1s both;
}
.progress::before {
content: attr(style);
font-family: monospace;
font-size:20px;
white-space:nowrap;
text-indent: -4ch;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: var(--p);
transform: translate(calc(-1*var(--p)),-50%);
color: #FFF;
animation:p2 1s 1s both,p3 0.8s 1s both;
}
@keyframes p1 {from {background-size:0% 100%}}
@keyframes p2 {from {left:0%;transform: translate(0%,-50%)}}
@keyframes p3 { /* put some randome number to fake the animation*/
0% {content:"--p:0%"}
15% {content:"--p:5%"}
30% {content:"--p:9%"}
45% {content:"--p:10%"}
60% {content:"--p:11%"}
75% {content:"--p:40%"}
90% {content:"--p:20%"}
}
<div class="progress" style="--p:0%"></div>
<div class="progress" style="--p:20%"></div>
<div class="progress" style="--p:50%"></div>
<div class="progress" style="--p:80%"></div>
<div class="progress" style="--p:100%"></div>
或者像下面这样的疯狂想法:
body {
margin: 100px;
background: #CCC
}
.progress {
margin: 20px 0;
height: 10px;
position: relative;
background: linear-gradient(lightgreen 0 0) 0/calc(var(--p)*1%) 100% no-repeat blue;
animation:p1 1s 1s both;
}
.progress::before {
content: "0% \A 1% \A 2% \A 3% \A 4% \A 5% \A 6% \A 7% \A 8% \A 9% \A 10% \A 11% \A 12% \A 13% \A 14% \A 15% \A 16% \A 17% \A 18% \A 19% \A 20% \A 21% \A 22% \A 23% \A 24% \A 25% \A 26% \A 27% \A 28% \A 29% \A 30% \A 31% \A 32% \A 33% \A 34% \A 35% \A 36% \A 37% \A 38% \A 39% \A 40% \A 41% \A 42% \A 43% \A 44% \A 45% \A 46% \A 47% \A 48% \A 49% \A 50% \A 51% \A 52% \A 53% \A 54% \A 55% \A 56% \A 57% \A 58% \A 59% \A 60% \A 61% \A 62% \A 63% \A 64% \A 65% \A 66% \A 67% \A 68% \A 69% \A 70% \A 71% \A 72% \A 73% \A 74% \A 75% \A 76% \A 77% \A 78% \A 79% \A 80% \A 81% \A 82% \A 83% \A 84% \A 85% \A 86% \A 87% \A 88% \A 89% \A 90% \A 91% \A 92% \A 93% \A 94% \A 95% \A 96% \A 97% \A 98% \A 99% \A 100%";
font-family: monospace;
font-size:20px;
width:4ch;
line-height:1em;
height:1em;
text-align:center;
overflow: hidden;
position: absolute;
background: rgba(255, 0, 0, 0.8);
border:5px solid transparent;
top:50%;
left: calc(var(--p)*1%);
transform: translate(calc(-1%*var(--p)),-50%);
color: #0000;
text-shadow:0 calc(var(--p)*-1em) 0 #fff;
animation:p2 1s 1s both,p3 1s 1s steps(var(--p)) both;
}
@keyframes p1 {from {background-size:0% 100%}}
@keyframes p2 {from {left:0%;transform: translate(0%,-50%)}}
@keyframes p3 {from {text-shadow:0 0 0 #fff}}
<div class="progress" style="--p:0"></div>
<div class="progress" style="--p:20"></div>
<div class="progress" style="--p:50"></div>
<div class="progress" style="--p:80"></div>
<div class="progress" style="--p:100"></div>
@Temani's
我改变的是:
- 进度条的左侧和右侧现在是两个独立的(伪)元素,并使用
flex-grow
增长到正确的比例 —calc
更少。 - 拇指不再需要手动定位。它通过使用
flex-box
和伪元素来定位。 - 拇指再次成为其自身的一个元素。这样,我可以让数字再次成为真实的文本,而不是
text-shadow
s。如果你想把这个东西变成一个滑块,你也可以用JS来捕捉事件。 - 删除了透明边框。我认为它有点太老套了。刚刚将行高从
1em
增加到2em
并添加了(2em - 1em) * -0.5 = -0.5em
. 的偏移量
body {
margin: 0;
padding: 20px;
background: #EEE;
font-family: sans-serif;
}
.progress {
display: flex;
align-items: center;
margin: 20px;
animation-delay: 1s;
animation-duration: 1s;
animation-fill-mode: forwards;
}
.progress::before, .progress::after {
content: '';
height: 10px;
animation: inherit;
}
.progress::before {
background-color: #1306F8;
flex-grow: 0;
animation-name: p_before;
}
.progress::after {
background-color: #E1E1E1;
flex-grow: 100;
animation-name: p_after;
}
.progress .thumb {
height: 1em;
padding: .3em .4em;
font-size: 20px;
line-height: 2em;
text-align: center;
color: #FFF;
background-color: #1306F8;
overflow: hidden;
z-index: 1;
animation: inherit;
}
.progress .thumb::before {
content: "0% \A 1% \A 2% \A 3% \A 4% \A 5% \A 6% \A 7% \A 8% \A 9% \A 10% \A 11% \A 12% \A 13% \A 14% \A 15% \A 16% \A 17% \A 18% \A 19% \A 20% \A 21% \A 22% \A 23% \A 24% \A 25% \A 26% \A 27% \A 28% \A 29% \A 30% \A 31% \A 32% \A 33% \A 34% \A 35% \A 36% \A 37% \A 38% \A 39% \A 40% \A 41% \A 42% \A 43% \A 44% \A 45% \A 46% \A 47% \A 48% \A 49% \A 50% \A 51% \A 52% \A 53% \A 54% \A 55% \A 56% \A 57% \A 58% \A 59% \A 60% \A 61% \A 62% \A 63% \A 64% \A 65% \A 66% \A 67% \A 68% \A 69% \A 70% \A 71% \A 72% \A 73% \A 74% \A 75% \A 76% \A 77% \A 78% \A 79% \A 80% \A 81% \A 82% \A 83% \A 84% \A 85% \A 86% \A 87% \A 88% \A 89% \A 90% \A 91% \A 92% \A 93% \A 94% \A 95% \A 96% \A 97% \A 98% \A 99% \A 100%";
position: relative;
display: block;
text-align: center;
white-space: pre-line;
margin-top: -0.5em;
top: 0;
animation: inherit;
animation-timing-function: steps(var(--p));
animation-name: p_thumb;
}
@keyframes p_before { to { flex-grow: var(--p) } }
@keyframes p_after { to { flex-grow: calc( 100 - var(--p)) } }
@keyframes p_thumb { to { top: calc(-2em * var(--p)) } }
<div class="progress" style="--p:0"><div class="thumb"></div></div>
<div class="progress" style="--p:20"><div class="thumb"></div></div>
<div class="progress" style="--p:40"><div class="thumb"></div></div>
<div class="progress" style="--p:60"><div class="thumb"></div></div>
<div class="progress" style="--p:80"><div class="thumb"></div></div>
<div class="progress" style="--p:100"><div class="thumb"></div></div>
就像我说的,Temani 做了跑腿工作,应该可以按计划收到赏金(奖励现有答案)。
到目前为止答案非常好,尤其是 Temani Afif 的文本缩进技巧。
我最初考虑做类似的事情,但想朝不同的方向前进。最后,我确定了一个解决方案,它利用新的 CSS Houdini @property
definition and some counter-reset
技巧将数字 CSS 自定义属性转换为字符串,然后我们可以在 content
属性我们添加的伪选择器
TL;DR
这是我的解决方案的完整代码片段,下面还有详细说明。
@property --progress-value {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
:root {
--progress-bar-color: #cfd8dc;
--progress-value-color: #2196f3;
--progress-empty-color-h: 4.1;
--progress-empty-color-s: 89.6;
--progress-empty-color-l: 58.4;
--progress-filled-color-h: 122.4;
--progress-filled-color-s: 39.4;
--progress-filled-color-l: 49.2;
}
html, body {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
margin: 0;
font-family: "Roboto Mono", monospace;
}
progress[value] {
display: block;
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 6px;
border: 0;
--border-radius: 10px;
border-radius: var(--border-radius);
counter-reset: progress var(--progress-value);
--progress-value-string: counter(progress) "%";
--progress-max-decimal: calc(var(--value, 0) / var(--max, 0));
--progress-value-decimal: calc(var(--progress-value, 0) / var(--max, 0));
--progress-value-percent: calc(var(--progress-value-decimal) * 100%);
--progress-value-color: hsl(
calc((var(--progress-empty-color-h) + (var(--progress-filled-color-h) - var(--progress-empty-color-h)) * var(--progress-value-decimal)) * 1deg)
calc((var(--progress-empty-color-s) + (var(--progress-filled-color-s) - var(--progress-empty-color-s)) * var(--progress-value-decimal)) * 1%)
calc((var(--progress-empty-color-l) + (var(--progress-filled-color-l) - var(--progress-empty-color-l)) * var(--progress-value-decimal)) * 1%)
);
-webkit-animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
}
@supports selector(::-moz-progress-bar) {
progress[value] {
--progress-value-decimal: calc(var(--value, 0) / var(--max, 0));
}
}
progress[value]::-webkit-progress-bar {
background-color: var(--progress-bar-color);
border-radius: var(--border-radius);
overflow: hidden;
}
progress[value]::-webkit-progress-value {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::-moz-progress-bar {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::after {
display: flex;
align-items: center;
justify-content: center;
--size: 32px;
width: var(--size);
height: var(--size);
position: absolute;
left: var(--progress-value-percent);
top: 50%;
transform: translate(-50%, -50%);
background-color: var(--progress-value-color);
border-radius: 50%;
content: attr(value);
content: var(--progress-value-string, var(--value));
font-size: 12px;
font-weight: 700;
color: #fff;
}
@-webkit-keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
@keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
<progress value="0" max="100" style="--value: 0; --max: 100;"></progress>
<progress value="25" max="100" style="--value: 25; --max: 100;"></progress>
<progress value="50" max="100" style="--value: 50; --max: 100;"></progress>
<progress value="75" max="100" style="--value: 75; --max: 100;"></progress>
<progress value="100" max="100" style="--value: 100; --max: 100;"></progress>
CodePen Link: cdpn.io/e/RwpyZGo
最终产品(屏幕截图,单击上面附加代码片段底部的“运行 片段”以查看动画效果)。
详细解释
HTML 已经有一个内置的 <progress>
element with several pseudo-elements included, so I really wanted to stick with using that and styling around it. This turned out to be largely successful when combined with CSS Houdini's new @property
定义,它允许我们创建更多的动态动画等等。
事实上,Temani Afif 对这个问题发表了另一个很好的回答,在这里写了一篇很棒的文章(Temani Afif 的We can finally animate CSS gradient)。
不仅使用新的 @property
定义允许我们为进度条的实际值设置动画,我们可以使用它来更改进度条内进度值的宽度以及 %
标签,但它也允许我们随着进度变化生成动态颜色变化。
在我下面的示例中,我选择从红色过渡到绿色以表示进度。如果您更喜欢使用单一颜色而不是这种不断变化的颜色,只需将所有 --progress-value-color
HSL 值替换为单一颜色值即可。
同样,我在animation
行中使用了一个calc()
来调整每个进度条动画的animation-duration
以相同的速度移动,而不是所有进度bar 同时开始和结束它们的动画,每个进度条同时传递相同的值。这意味着,如果两个进度条达到 50% 并且其中一个的值为 50%,则该进度条将停止动画,而另一个将继续以其新值进行动画。
如果您希望所有进度条同步开始和结束,只需将 calc()
替换为单个 <time>
值(例如 750ms
、3s
等)。
@property --progress-value {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
:root {
--progress-bar-color: #cfd8dc;
--progress-value-color: #2196f3;
--progress-empty-color-h: 4.1;
--progress-empty-color-s: 89.6;
--progress-empty-color-l: 58.4;
--progress-filled-color-h: 122.4;
--progress-filled-color-s: 39.4;
--progress-filled-color-l: 49.2;
}
html, body {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
margin: 0;
font-family: "Roboto Mono", monospace;
}
progress[value] {
display: block;
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 6px;
border: 0;
--border-radius: 10px;
border-radius: var(--border-radius);
counter-reset: progress var(--progress-value);
--progress-value-string: counter(progress) "%";
--progress-max-decimal: calc(var(--value, 0) / var(--max, 0));
--progress-value-decimal: calc(var(--progress-value, 0) / var(--max, 0));
--progress-value-percent: calc(var(--progress-value-decimal) * 100%);
--progress-value-color: hsl(
calc((var(--progress-empty-color-h) + (var(--progress-filled-color-h) - var(--progress-empty-color-h)) * var(--progress-value-decimal)) * 1deg)
calc((var(--progress-empty-color-s) + (var(--progress-filled-color-s) - var(--progress-empty-color-s)) * var(--progress-value-decimal)) * 1%)
calc((var(--progress-empty-color-l) + (var(--progress-filled-color-l) - var(--progress-empty-color-l)) * var(--progress-value-decimal)) * 1%)
);
-webkit-animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
animation: calc(1s * var(--progress-max-decimal)) ease-out 0s 1 normal both progress;
}
@supports selector(::-moz-progress-bar) {
progress[value] {
--progress-value-decimal: calc(var(--value, 0) / var(--max, 0));
}
}
progress[value]::-webkit-progress-bar {
background-color: var(--progress-bar-color);
border-radius: var(--border-radius);
overflow: hidden;
}
progress[value]::-webkit-progress-value {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::-moz-progress-bar {
width: var(--progress-value-percent) !important;
background-color: var(--progress-value-color);
border-radius: var(--border-radius);
}
progress[value]::after {
display: flex;
align-items: center;
justify-content: center;
--size: 32px;
width: var(--size);
height: var(--size);
position: absolute;
left: var(--progress-value-percent);
top: 50%;
transform: translate(-50%, -50%);
background-color: var(--progress-value-color);
border-radius: 50%;
content: attr(value);
content: var(--progress-value-string, var(--value));
font-size: 12px;
font-weight: 700;
color: #fff;
}
@-webkit-keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
@keyframes progress {
from {
--progress-value: 0;
} to {
--progress-value: var(--value);
}
}
<progress value="0" max="100" style="--value: 0; --max: 100;"></progress>
<progress value="25" max="100" style="--value: 25; --max: 100;"></progress>
<progress value="50" max="100" style="--value: 50; --max: 100;"></progress>
<progress value="75" max="100" style="--value: 75; --max: 100;"></progress>
<progress value="100" max="100" style="--value: 100; --max: 100;"></progress>
CodePen Link: cdpn.io/e/RwpyZGo
对于每个进度条,我们需要将 value
和 max
都声明为属性以及 CSS 自定义属性(变量),这当然不理想。但是,CSSWG 目前正在对 attr()
进行一些不同的改进,这将使我们能够很快以任何指定格式访问这些属性值,而无需另外使用 CSS 自定义属性,因为我在上面的例子中做了。
浏览器支持 attr()
正如您在官方 MDN docs on attr()
的浏览器支持部分中看到的那样,目前很少有浏览器支持 attr()
的这些附加功能,例如回退和 type-or -单元。我们还需要能够在任何 CSS 属性 中使用 attr()
,尤其是 CSS 自定义属性,而不仅仅是 content
属性,为了完全摆脱 CSS 自定义属性解决方法。
这些改进目前处于“编辑草案”状态,没有生产浏览器支持,但这最早可能在明年发生变化。所以现在,除了属性之外,我们还需要使用 CSS 自定义属性。此外,Firefox 尚不支持这个新的 属性 定义,但我的解决方案包括一个 @supports
查询回退,它仍然可以确保进度条的宽度正确,并根据它们的值使用正确的颜色.
一旦所有这些 CSS 和 Houdini 更新在所有主要浏览器上可用,希望明年,所有这些都将可以通过本机 HTML 属性实现,如下所示:
<progress value="0" max="100"></progress>
<progress value="25" max="100"></progress>
<progress value="50" max="100"></progress>
<progress value="75" max="100"></progress>
<progress value="100" max="100"></progress>
到那时,我们将能够在 CSS这样:
progress[value] {
--value: attr(value number);
}
progress[max] {
--max: attr(max number);
}
其余逻辑将保持不变。有关 attr()
的更多详细信息,请在此处参考 MDN 文档:attr()
除了我的其他更自定义的解决方案之外,我还想 post 一个专门回答您最初提出的问题的答案,而不是提供不同或替代的解决方案。
你最里面的元素没有完全居中的原因是它的 div 本质上占据了其父级宽度的 100%,由于内联 width
应用到它的样式。文本是右对齐的,这意味着文本的右侧将紧靠中间的 50% 标记,但它不会完全位于 .fullbar
元素的中心。
在不太习惯的情况下,我个人更喜欢使 .number
元素绝对定位 (position: absolute
),以便其祖先元素的大小完全不受其自身大小的影响。这样就独立了。
以下是我们如何实现的:
html, body { height: 100%; }
body {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0 20px;
background-color: #ccc;
}
.fullbar {
background-color: blue;
width: 100%;
height: 10px;
}
.progress {
position: relative;
background: green;
height: 100%;
}
.number {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
right: 0;
top: 50%;
transform: translate(50%, -50%);
background: inherit;
color: #fff;
padding: 4px;
padding: 0 2px 1px 3px;
}
<div class="fullbar">
<div class="progress" style="width: 50%">
<div class="number">50%</div>
</div>
</div>
最后,如果您确实希望使用 CSS 自定义属性使您的原始解决方案作为一行工作,我们可以这样做:
html, body { height: 100%; }
body {
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0 20px;
background-color: #ccc;
}
.progress {
counter-reset: progress var(--value);
--value-percent: calc(var(--value) * 1%);
position: relative;
width: 100%;
height: 10px;
background-color: blue;
}
.progress::before {
content: '';
display: block;
position: relative;
width: var(--value-percent);
height: 100%;
background: green;
}
.progress::after {
content: counter(progress) '%';
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: var(--value-percent);
top: 50%;
transform: translate(-50%, -50%);
background: green;
color: #fff;
padding: 2px 4px;
}
<div class="progress" style="--value: 50"></div>