CSS
进度条是一个非常常见的功能,实现起来也不难,一般都会用 div 来实现。
作为一个这么常见的需求, whatwg 肯定是不会没有原生组件提供(虽然有也不一定会用),那么就来看看有哪些有意思的进度条实现方式。

常规版 — div 一波流

这是比较常规的实现方式,先看效果:
CSS 进度条实现的几种方式 - 图1
源码如下:

  1. <style>
  2. .progress1 {
  3. height: 20px;
  4. width: 300px;
  5. background-color: #f5f5f5;
  6. border-bottom-right-radius: 10px;
  7. border-top-right-radius: 10px;
  8. }
  9. .progress1::before {
  10. counter-reset: progress var(--percent, 0);
  11. content: counter(progress) '%\2002';
  12. display: block;
  13. height: 20px;
  14. line-height: 20px;
  15. width: calc(300px * var(--percent, 0) / 100);
  16. font-size: 12px;
  17. color: #fff;
  18. background-color: #2486ff;
  19. text-align: right;
  20. white-space: nowrap;
  21. overflow: hidden;
  22. border-bottom-right-radius: 10px;
  23. border-top-right-radius: 10px;
  24. }
  25. .btn {
  26. margin-top: 30px;
  27. }
  28. </style>
  29. <div id="progress1" class="progress1"></div>
  30. <button id="btn" class="btn">点我一下嘛~</button>
  31. <script>
  32. 'use strict';
  33. let startTimestamp = (new Date()).getTime();
  34. let currentPercentage = 0;
  35. let maxPercentage = 100;
  36. let countDelay = 100;
  37. let timer = null;
  38. let start = false;
  39. const percentageChange = () => {
  40. const currentTimestamp = (new Date()).getTime();
  41. if (currentTimestamp - startTimestamp >= countDelay) {
  42. currentPercentage++;
  43. startTimestamp = (new Date()).getTime();
  44. progress1.style = `--percent: ${currentPercentage}`;
  45. };
  46. if (currentPercentage < maxPercentage) {
  47. timer = window.requestAnimationFrame(percentageChange);
  48. } else {
  49. window.cancelAnimationFrame(timer);
  50. };
  51. };
  52. const clickHander = () => {
  53. if (!start) {
  54. start = true;
  55. percentageChange();
  56. };
  57. };
  58. btn.addEventListener('click', clickHander);
  59. </script>

这种方法的核心就是以当前盒子为容器,以 ::before 为内容填充。用 <div> 的好处就是实现简单,兼容性强,拓展性高,但是美中不足的是标签语义化不强。

进阶版 — input type="range"

<input /> 是一个非常实用的替换元素,不同的 type 可以做不同的事情。第二种就是用 <input type="range" /> 来实现的。首先我们来看看效果:
CSS 进度条实现的几种方式 - 图2
源码如下:

  1. <style>
  2. .progress2[type='range'] {
  3. display: block;
  4. font: inherit;
  5. height: 20px;
  6. width: 300px;
  7. pointer-events: none;
  8. background-color: linear-gradient(to right, #2376b7 100%, #FFF 0%);
  9. }
  10. .progress2[type='range'],
  11. .progress2[type='range']::-webkit-slider-thumb {
  12. -webkit-appearance: none;
  13. };
  14. .progress2[type='range']::-webkit-slider-runnable-track {
  15. border: none;
  16. border-bottom-right-radius: 10px;
  17. border-top-right-radius: 10px;
  18. height: 20px;
  19. width: 300px;
  20. }
  21. .btn {
  22. margin-top: 30px;
  23. }
  24. </style>
  25. <input id="progress2" class="progress2" type='range' step="1" min="0" max="100" value="0"/>
  26. <button id="btn" class="btn">点我一下嘛~</button>
  27. <script>
  28. 'use strict';
  29. let startTimestamp = (new Date()).getTime();
  30. let currentPercentage = 0;
  31. let maxPercentage = 100;
  32. let countDelay = 100;
  33. let timer = null;
  34. let start = false;
  35. let percentageGap = 10;
  36. const percentageChange = () => {
  37. const currentTimestamp = (new Date()).getTime();
  38. if (currentTimestamp - startTimestamp >= countDelay) {
  39. currentPercentage++;
  40. startTimestamp = (new Date()).getTime();
  41. progress2.value = currentPercentage;
  42. progress2.style.background = `linear-gradient(to right, #2376b7 ${currentPercentage}%, #FFF 0%`;
  43. };
  44. if (currentPercentage < maxPercentage) {
  45. timer = window.requestAnimationFrame(percentageChange);
  46. } else {
  47. window.cancelAnimationFrame(timer);
  48. };
  49. };
  50. const clickHander = () => {
  51. if (!start) {
  52. start = true;
  53. percentageChange();
  54. };
  55. };
  56. btn.addEventListener('click', clickHander);
  57. </script>

写完这个 demo 才发现,<input type="range" /> 并不适合做这个功能。。一个是实现困难,这个 type 组件的每个元件都可以单独修改样式,但是效果并不是很好。
另一个是因为 range 有专属语意 —— 范围,所以它更适合做下面这种事:
image.png
以上demo来自:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range

高级版 — progress 鸭

当然,上述两种方式都是模拟进度条,实际上我们并不需要模拟,因为 whatwg 有为我们提供原生的进度条标签 —— <progress>
我们先看效果:
CSS 进度条实现的几种方式 - 图4
实现如下:

  1. <style>
  2. .progress3 {
  3. height: 20px;
  4. width: 300px;
  5. -webkit-appearance: none;
  6. display: block;
  7. }
  8. .progress3::-webkit-progress-value {
  9. background: linear-gradient(
  10. -45deg,
  11. transparent 33%,
  12. rgba(0, 0, 0, .1) 33%,
  13. rgba(0,0, 0, .1) 66%,
  14. transparent 66%
  15. ),
  16. linear-gradient(
  17. to top,
  18. rgba(255, 255, 255, .25),
  19. rgba(0, 0, 0, .25)
  20. ),
  21. linear-gradient(
  22. to left,
  23. #09c,
  24. #f44);
  25. border-radius: 2px;
  26. background-size: 35px 20px, 100% 100%, 100% 100%;
  27. }
  28. .btn {
  29. margin-top: 30px;
  30. }
  31. </style>
  32. <progress id="progress3" class="progress3" max="100" value="0"></progress>
  33. <button id="btn" class="btn">点我一下嘛~</button>
  34. <script>
  35. 'use strict';
  36. let startTimestamp = (new Date()).getTime();
  37. let currentPercentage = 0;
  38. let maxPercentage = 100;
  39. let countDelay = 100;
  40. let timer = null;
  41. let start = false;
  42. const percentageChange = () => {
  43. const currentTimestamp = (new Date()).getTime();
  44. if (currentTimestamp - startTimestamp >= countDelay) {
  45. currentPercentage++;
  46. startTimestamp = (new Date()).getTime();
  47. progress3.setAttribute('value', currentPercentage);
  48. };
  49. if (currentPercentage < maxPercentage) {
  50. timer = window.requestAnimationFrame(percentageChange);
  51. } else {
  52. window.cancelAnimationFrame(timer);
  53. };
  54. };
  55. const clickHander = () => {
  56. if (!start) {
  57. start = true;
  58. percentageChange();
  59. };
  60. };
  61. btn.addEventListener('click', clickHander);
  62. </script>

虽然有原生的进度条标签,但是规范里并没有规定它的具体表现,所以各个浏览器厂商完全可以按照自己的喜好去定制,样式完全不可控,所以标签虽好。。可用性却不强,有点可惜。

终极版 — meter 赛高

当然,能够实现进度条功能的标签,除了上面所说的,还有 <meter> 标签。先看效果:
CSS 进度条实现的几种方式 - 图5
代码如下:

  1. <style>
  2. .progress4 {
  3. display: block;
  4. font: inherit;
  5. height: 50px;
  6. width: 300px;
  7. pointer-events: none;
  8. }
  9. .btn {
  10. margin-top: 30px;
  11. }
  12. </style>
  13. <meter id="progress4" class="progress4" low="60" high="80" min="0" max="100" value="0"></meter>
  14. <button id="btn" class="btn">点我一下嘛~</button>
  15. <script>
  16. 'use strict';
  17. let startTimestamp = (new Date()).getTime();
  18. let currentPercentage = 0;
  19. let maxPercentage = 100;
  20. let countDelay = 100;
  21. let timer = null;
  22. let start = false;
  23. const percentageChange = () => {
  24. const currentTimestamp = (new Date()).getTime();
  25. if (currentTimestamp - startTimestamp >= countDelay) {
  26. currentPercentage++;
  27. startTimestamp = (new Date()).getTime();
  28. progress4.value = currentPercentage;
  29. };
  30. if (currentPercentage < maxPercentage) {
  31. timer = window.requestAnimationFrame(percentageChange);
  32. } else {
  33. window.cancelAnimationFrame(timer);
  34. };
  35. };
  36. const clickHander = () => {
  37. if (!start) {
  38. start = true;
  39. percentageChange();
  40. };
  41. };
  42. btn.addEventListener('click', clickHander);
  43. </script>

这个标签可能比较陌生,实际上它跟 <input type="range"> 的语义是一样的,用来显示已知范围的标量值或者分数值。不一样的就是。它样式改起来更麻烦。

总结

这里测评了4种实现进度条的方式,得出的结论就是 —— <div> 赛高。。。虽然有的时候想优雅一点追求标签语义化,但是资源不支持,也很尴尬。
嗯,万能的 <div>