← 📜 Scroll · 滚动怎么动、长什么样、装得下多少 / scrollTo() 返回 Promise:等程序滚动停下再动手 待审核 4 / 9
scrollTo() Promise · interrupted

scrollTo() 返回 Promise:等程序滚动停下再动手

scroll-behavior 让程序滚动变平滑,却一直缺一个信号:这段平滑滚动什么时候到位了?过去只能监听 scrollend 事件、或轮询 scrollY 硬等。Chrome / Edge 150 起,scrollTo() /scrollBy() /scroll()(含 Element 版)返回一个 Promise,滚动停下时兑现——于是「滚到位之后再淡入某块内容」这类衔接动画,可以直接 await 出来。

1 · 返回的是什么

Promise 兑现值是一个对象,只有一个字段 interrupted:

  • { interrupted: false }:这段滚动顺利到位
  • { interrupted: true }:滚动被打断——通常是上一段程序滚动还没停,又发起了新的一段。

behavior: 'smooth' 才有意义:平滑滚动要动画若干帧,Promise 等到最后一帧才兑现;instant(或默认 auto 落到瞬时)则近乎立即兑现。

bar.classList.add('fade-out');
const { interrupted } = await container.scrollTo({ top: 0, behavior: 'smooth' });
if (!interrupted) bar.classList.add('fade-in');

2 · 动手:滚到位之后再淡入工具条

点按钮让容器平滑滚到顶 / 底。触发的瞬间工具条淡出,await 到滚动停下后再淡入——衔接干净,不用猜滚动要多久。想看打断:一段平滑滚动还在跑时,立刻点另一个方向,第一段的 Promise 会以 interrupted: true 兑现。

关掉 behavior: 'smooth' 开关再点:滚动瞬时到位,Promise 几乎立刻兑现,淡出 / 淡入连在一起几乎看不出停顿——这也印证了 Promise 兑现的时机跟随实际滚动时长

3 · 用之前先特性检测

Firefox / Safari 目前还不返回 Promise,旧版 Chromium 也不返回——那里 scrollTo() 的返回值是 undefined,直接 await 它虽不报错,却拿不到 interrupted,读 result.interrupted 还会抛错。所以要先探测:

function supportsScrollPromises(el) {
  const r = el.scrollTo({ top: el.scrollTop, behavior: 'instant' });
  return r instanceof Promise;
}

上面的 demo 已按此探测,并在标题栏标出你当前浏览器是否原生返回 Promise。

4 · 和 scrollend 事件的区别

scrollend 事件也报告「滚动停了」,但它是容器级的——用户拖动、滚轮、程序滚动结束都触发,分不清是哪一次、更认不出打断。Promise 则绑定这一次调用:谁发起的滚动,谁拿到自己的兑现值,还附带 interrupted。要「这一次程序滚动结束后接着做事」,Promise 更贴切;要「只要滚动停下就刷新某状态」,事件更合适。

container.scrollTo({ top: 0, behavior: 'smooth' }).then(({ interrupted }) => {
});

container.addEventListener('scrollend', () => {
});

5 · 边界与细节

  • Chrome / Edge / Opera(Chromium)150+ 起支持,规范仍标实验性;Firefox / Safari 尚未支持。
  • 返回值形状固定为 { interrupted },不是任意值;别把它当普通返回值解构别的字段。
  • 只有程序触发的滚动会返回 Promise;用户拖动 / 滚轮不经过 scrollTo(),与它无关。
  • instant / 瞬时滚动也返回 Promise,只是近乎立即兑现,interrupted 一般为 false
  • 降级时不必阻塞体验:检测不到就退回 scrollend 事件或直接不等——滚动本身照常发生。