面试问题:实现进度条

2025-05-27

面试问题:实现进度条

我在社交媒体上看到这个问题热度不减。据说一些顶尖公司正在用它来筛选前端工程师。我的未婚妻很快就要开始求职了,我让她试试看。她几乎完成了最后一步(做了一些研究),但一个棘手的递归错误让她犯了难。我写了这篇文章来帮助她。希望这篇文章能帮到你,如果你也遇到类似的问题!

虽然存在一些变化,但这个问题通常是分阶段提出的,并且难度会逐渐增加。

1. 实现一个在 3 秒内从 0 到 100% 的加载栏

这完全可以用 CSS 来实现。如果某些东西可以完全用 CSS 实现,我倾向于选择这种方式。我的理由是,重构纯 CSS 代码比尝试扩展 JavaScript 代码更容易。CSS 声明性很强,易于阅读和理解其底层机制。

对于纯 CSS 的进度条,我将使用两个 div — 一个容器和一个进度条 — 和keyframes。这里最重要的一行是animation: 1s linear fill;。有很多内容需要讨论。transition-timing-function我们要用什么呢 — easelinearcubic-bezier

至少,这个快速的回答表明你知道它是什么keyframes,并且可以在基本层面上使用它。

<div class="container">
  <div class="progress-bar"></div>
</div>
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 100%;
  height: 50px;
  background-color: #90EE90;
  animation: 3s linear fill;
}

@keyframes fill {
    0% {
        width: 0%;
    }
    100% {
        width: 100%;
    }
}

2. 点击按钮后开始加载栏动画

现在我们进入 JavaScript 的世界。我一直觉得 JavaScripttransition非常棒,所以我打算用 JavaScript 为progress-bar元素添加一个类。它能让你“定义元素两种状态之间的转换” (MDN)

我正在确保缓存对加载栏的引用。我经常使用智能电视,缓存是我们用来保持一切流畅的众多技巧之一。HTML 元素具有Element#classList,这是一个非常棒的与类交互的 API。它使用起来非常安全。您可以添加多个类,它只会添加一个实例;您还可以删除不存在的类,而不会出现任何错误。

classList#toggle特别有用。“当只有一个参数时:切换类值;例如,如果该类存在,则将其移除并返回 false;如果不存在,则添加并返回 true” (MDN)

<div class="container">
  <div class="progress-bar"></div>
</div>
<button onclick="loadBar()">Load</button>
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 0%;
  height: 50px;
  background-color: #90EE90;
  transition: width 3s linear;
}

.load {
  width: 100%;
}
const bar = document.querySelector('.progress-bar');

function loadBar () {
  bar.classList.add('load');
}

3. 如果按钮被多次点击,则将多个加载条排队。加载条 N 开始动画,加载条 N-1 完成动画。

这里变得更有趣了。我们之前的解决方案可以通过移除和添加类来迭代,但感觉有点老套。我觉得这里的目的是让你更多地使用 JavaScript。技术面试题其实没有终点,总是会有一些限制、扩展和假设。在继续阅读之前,我很想看看你能想到什么👀。

这里有几个陷阱。你必须确保耗时恰好是三秒,而不是多一刻或少一刻。一刻的合适长度是多少?我想这取决于你的进度条有多宽。每次增加一个百分比似乎是一个最佳选择。注意:最好自己管理状态,而不是依赖 DOM。

缓存在这里非常重要。你肯定不想每秒 33 次遍历 DOM 元素。为了保持代码简洁,可能需要两个函数。我使用了递归超时函数,并使用全局标志来跟踪进度条是否正在运行。无论如何,我们都希望将一个进度条添加到队列中,但我们不希望同时添加两个进度条,否则进度条的加载速度会翻倍!

<div class="container">
  <div class="progress-bar"></div>
</div>
<div>Queued bars: <span class="queued">0</span></div>
<button onclick="loadBar()">Load</button> 
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 0%;
  height: 50px;
  background-color: #90EE90;
}
const bar = document.querySelector('.progress-bar');
const queued = document.querySelector('.queued');

let loader = false;
let width = 0;
let count = 0;

function loadBar() {
  queued.innerText = ++count;
  if (loader === false) {
    bar.style.width = 0;
    tick();
  }
}

function tick() {
  loader = true;
  if (++width > 100) {
    queued.innerText = --count;
    width = 0;
    if (count < 1) {
      loader = false;
      return;
    }
  }
  bar.style.width = `${width}%`;
  setTimeout(tick, 30);
}

4. 做同样的事情,但不使用计时器!

好吧,他们在采访中并没有真正问到这个问题,但是有人requestAnimationFrame在 DEV 的评论中提到了这一点,我认为在模拟前面的答案的同时使用它来构建一个示例会很有趣。

如果不需要排队加载条形图,逻辑会简洁得多。我最终决定使用两个耦合函数。最近,我看到有人说,else代码中的任何一个实例都是重构的机会。我一直在思考这个问题,虽然没有固定的规则,但它最近影响了我如何设计函数。去看看吧。

<div class="container">
  <div class="progress-bar"></div>
</div>
<div>Queued bars: <span class="queued">0</span></div>
<button onclick="loadBar(1)">Load</button> 
.container {
  width: 300px;
  height: 50px;
  background-color: #D3D3D3;
}

.progress-bar {
  width: 0%;
  height: 50px;
  background-color: #90EE90;
}
const bar = document.querySelector('.progress-bar');
const queued = document.querySelector('.queued');

let loading = false;
let count = 0;

function tick (timestamp, dist, duration) {
  const runtime = timestamp - starttime;
  let progress = runtime / duration;
  progress = Math.min(progress, 1);
  bar.style.width = `${dist * progress}%`;
  if (runtime > duration) {
    loading = false;
    count--;
    loadBar(0);
    return;
  }
  requestAnimationFrame (function(timestamp) {
      tick(timestamp, dist, duration)
  });
}

function loadBar (increment) {
  count += increment;
  queued.innerText = count;
  if (loading === true || count < 1) { return; }
  bar.style.width = 0;
  loading = true;
  requestAnimationFrame (function(timestamp) {
    starttime = timestamp;
    tick(timestamp, 100, 3000);
  });
}

结束语

<progress>如果你整篇文章都在跟我嚷嚷,那就给我满分。没错,这个元素从 HTML5 开始就存在了。你可以通过设置两个属性max和来管理它value。CSS Tricks 有一篇文章讲解了如何设置它的样式和动画。文章还介绍了不同的状态:determinate 和 indeterminate——后者表示“进度未知”。这些状态非常棒,因为它们为我们提供了一种原生的方式与用户进行交互。

这道面试题的重点不在于答案是否完美,而在于你如何表达自己的想法,以及你提出的澄清问题的方式。是否应该用百分比来表示?这是否在低功耗设备上运行?如果是,不要以百分之一为增量。也许百分之五或百分之十会更好。

我认为一个好的扩展可能是要求面试者构建一个界面,接收描述进度状态的 WebSocket 消息并将其传达给用户。

你觉得这道面试题怎么样?它是否符合你解决问题和浏览器知识的结合点?


与 150 多名订阅我的关于编程和个人成长的新闻通讯的人一起!

我发布有关科技的推文@healeycodes

文章来源:https://dev.to/healeycodes/interview-question-implement-a-progress-bar-4j7h
PREV
让我兴奋的 JavaScript 单行代码
NEXT
如何编写出色的 GitHub README