跳过主要内容

ScrollTrigger.normalizeScroll

ScrollTrigger.normalizeScroll( normalize:Boolean | Object ) : ScrollObserver | null

强制滚动在 JavaScript 线程上执行,确保屏幕更新同步,并防止地址栏在[大多数]移动设备上显示/隐藏。

参数

  • normalize: 布尔值 | 对象

    如果true,它将强制滚动在 JavaScript 线程上执行,并防止移动浏览器地址栏显示/隐藏。如果false,它将使用原生滚动,通常由单独的线程处理,可能导致重绘稍微不同步。你也可以传入一个配置对象来调整观察者属性。例如 { type: "touch,wheel,pointer" } 会使页面可以通过鼠标/指针拖动滚动。有关选项,请参见 Observer 文档(由于回调函数被内部使用,所以大多数回调不可用)

细节

默认情况下,ScrollTrigger 利用浏览器的原生滚动行为,但使用原生滚动可能会遇到三个潜在问题:

  • 移动浏览器地址栏显示/隐藏导致视口大小变化- 你是否注意到过地址栏显示或隐藏后突然发生的偏移?这是因为当视口大小改变时,ScrollTrigger 必须重新计算开始/结束位置,在新的视口大小下这些位置很可能已经改变(因此发生了跳动)。从逻辑上讲,无法同时保持触发器位置的准确性并避免任何改变。这不是 ScrollTrigger 的 bug。你可以阻止它重新计算(见config()),但这样你的触发器位置就会不准确。
  • 多线程同步问题- 如果快速滚动,你可能会看到固定的 ScrollTrigger 出现跳转当其最初固定/解除固定时。为什么?因为现代浏览器在一个不同的线程上处理滚动,所以它可能像页面已经滚动到固定点那样重绘屏幕……然后几毫秒之后 JavaScript 线程运行并应用了固定效果,造成视觉上的跳跃。参见past the point of the pinning...and then the JavaScript thread runs a few milliseconds later and applies the pinning, causing the perceived jump. See Firefox 的解释.
  • iOS Safari 错误地报告位置数据导致抖动- 其中一些错误自 2017 年就已经存在,至今仍未修复。浏览器间歇性地错误报告滚动位置以及 event.clientX/Y,导致内容出现“抖动”。当 ScrollTrigger 向浏览器询问“当前滚动位置是什么”或者“用户的手指在屏幕上的什么位置?”,iOS Safari 频繁提供的是错误的数值。
  • 超出滚动行为- 某些浏览器如 iOS Safari 忽略overscroll-behaviorCSS,并在页面顶部或底部强制执行烦人的超出回弹效果。
  • 不同设备上的惯性滚动行为不一致- Android 和 iOS 触摸滚动的惯性差异非常大。

解决方案

当你设置ScrollTrigger.normalizeScroll(true),它会拦截原生滚动行为并改用 JavaScript 线程进行处理,结果如下:

  • 防止地址栏显示/隐藏在[多数]移动设备上保持一致的视口大小(消除因调整大小产生的偏移)。我们知道的一个例外是最新版 iOS,在手机竖屏方向时浏览器强制执行地址栏显示/隐藏(似乎无法绕过,但你仍然可以使用ScrollTrigger.config({ ignoreMobileResize: true})在这种情况下跳过刷新)。
  • 防止超出滚动和回弹滚动行为。
  • 由于滚动是在 JavaScript 线程上完成的,屏幕更新得以同步(不再因为重绘时机不准而导致固定元素跳跃)
  • ScrollTrigger绕过了 iOS 的 Bug方法是通过跳过每一个“touchmove”事件并在内部管理位置,而不是依赖浏览器的报告。只有在 iOS 设备上才会发生“touchmove”跳跃。
  • ScrollTrigger 在所有设备上统一处理触摸的惯性滚动。

这是一种混合形式的滚动劫持;技术上确实取消了原生滚动事件,但它没有强加任何假的滚动条或者不同的加速度等类似机制——它只是获取滚动增量并通过 JavaScript 应用它们,以解决上述问题。这也最大限度地减少了可访问性的副作用。

基本用法

此方法可以用作取值器或设值器

ScrollTrigger.normalizeScroll(true); // enable
ScrollTrigger.normalizeScroll(false); // disable

let normalizer = ScrollTrigger.normalizeScroll(); // gets the Observer instance that's handling normalization (if enabled, of course)

高级配置

内部,它使用了一个Observer来实现神奇效果,因此你可以传入包含以下任选属性的配置对象:

示例

ScrollTrigger.normalizeScroll({
allowNestedScroll: true,
lockAxis: false,
momentum: self => Math.min(3, self.velocityY / 1000), // dynamically control the duration of the momentum when flick-scrolling
type: "touch,wheel,pointer", // now the page will be drag-scrollable on desktop because "pointer" is in the list
});

注意事项

  • 为了避免干扰手势操作,在触摸设备上有多个触点或页面缩放比例非 1(例如捏合缩放后)时,它将放弃控制。
  • 某些移动浏览器会在你不主动滚动时隐藏滚动条,但由于 normalizeScroll() 截获了浏览器的原生滚动行为,这种情况不会发生。目前我们尚未实现任何“虚拟”的滚动条,但欢迎你自己实现;利用 ScrollTrigger 提供的数据应该相对容易做到这一点。事实上,你可以使用一个简单的 ScrollTrigger,不需要 scroller/trigger 和 start/end(因为它默认作用于整个页面)来动画化一个滚动条<div>。如果你需要帮助,请在论坛.
  • 发帖提问。论坛.

为什么 ScrollTrigger 不总是默认归一化滚动?

因为通常并不需要,我们更倾向于让浏览器尽可能以原生方式处理事务。normalizeScroll()看起来是一个重要的选择加入的设置。ScrollTrigger 被专门设计成执行滚动劫持(scroll-jacking),因为我们希望它保持“纯粹”且不具有侵入性。

iOS 浏览器 bug 是怎么回事?

以下只是我们在 Safari 中遇到的各种未解决 bug 的部分示例,毫无疑问Safari 是问题最多的浏览器(主要是在移动设备上):1 | 2 | 3 | 4 | 5。其中一些 bug 最早在2018年1月就被报告了,但至今仍未修复。我们曾多次尝试直接联系 Safari 团队,但他们没有任何回应。如果你知道联系他们的方式,请告诉我们;我们希望能与他们协作寻找解决方案。我们在排查这些问题上耗费了几百小时,normalizeScroll() 是我们针对各种浏览器 bug 和不一致性所能做出的最佳应对措施。我们欢迎建议。.

无噪 Logo
无噪文档
中文文档 · 复刻官网
查看所有 ↗