在 Chrome 浏览器中使用以下代码:
document.body.addEventListener("touchmove", (e) => {
console.log(e);
})
会在控制台看到一个警告信息:
[Violation] Added non-passive event listener to a scroll-blocking 'touchmove' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952
解决办法就是传递第三个参数, 显式设置 passive
的值. 大多数情况下, 是直接在 addEventListener
函数的第三个参数传递 { passive: true }
.
原因
产生这个警告的原因是, 默认情况下在 touchstart
和 第一个touchmove
事件的回调函数中可以通过调用 event.preventDefault()
来阻止页面触摸滚动(Chrome 56 之前版本), 为了支持这种事件行为, 默认情况下, 页面的触摸滚动就要等待第一个 touchmove
事件结束后才可以滚动, 这会导致严重的滚动性能问题.
注意在 Chrome 56 版本之前, passive
默认为 false
. 为了用户体验, 在之后版本中默认 true
.
为了解决该问题, addEventListenner
第三个参数 引入了 passive
选项:
- 如果设置了
passive: true
, 则页面触摸滚动就不需要等待第一个touchmove
事件结束, 也意味着event.preventDefault()
方法不会起作用. - 如果设置了
passive: false
, 不管是否需要调用e.preventDefault()
来阻止页面滚动,都需要等到第一个touchmove
函数执行完毕,页面才会做出反应, 也就可以在touchmove
事件回调函数内调用event.preventDefault()
来阻止页面滚动.
注意官方不建议使用 { passtive: false }
+ event.preventDefault()
的方式阻止滚动, 如果实际需求中需要阻止触摸滚动, 使用CSS样式 touch-action: none
或者 touch-action: pan-y pinch-zoom
(水平滚动) 来阻止触摸滚动:
/* 禁止滚动和缩放 */
element {
touch-action: none;
}
/* 只允许垂直滚动 */
element {
touch-action: pan-y;
}
/* 只允许水平滚动 */
element {
touch-action: pan-x;
}
/* 启用垂直滚动和缩放 */
element {
touch-action: pan-y pinch-zoom;
}
参考资料
Making touch scrolling fast by default
We know that scrolling responsiveness is critical to the user's engagement with a website on mobile, yet touch event listeners often cause serious scrolling performance problems. Chrome has been addressing this by allowing touch event listeners to be passive (passing the {passive: true}
option to addEventListener()
) and shipping the pointer events API. These are great features to drive new content into models that don't block scrolling, but developers sometimes find them hard to understand and adopt.
我们知道,在移动设备上,滚动响应对用户与网站的互动至关重要,然而触摸事件监听器常常导致严重的滚动性能问题。Chrome 通过允许触摸事件监听器变为被动(将 {passive: true}
选项传递给 addEventListener()
)并实现指针事件 API 来解决这个问题。这些功能对于引导新内容到不阻塞滚动的模型来说非常有用,但开发者有时会觉得它们难以理解和采纳。
We believe the web should be fast by default without developers needing to understand arcane details of browser behavior. In Chrome 56 we are defaulting touch listeners to passive by default in cases where that most often matches the developer's intention. We believe that by doing this we can greatly improve the user's experience whilst having minimal negative impact on sites. 我们相信,网络应该在默认情况下就很快,而无需开发者了解浏览器行为的奥秘。在 Chrome 56 中,我们将触摸监听器默认为被动的,在这种情况下,它通常与开发者的意图相匹配。我们相信,通过这样做,我们可以在对网站的负面影响最小的情况下,极大地提高用户的体验。
In rare cases this change can result in unintended scrolling. This is usually easily addressed by applying a touch-action: none
style to the element where scrolling shouldn't occur. Read on for details, how to know if you are impacted, and what you can do about it.
在极少数情况下,这种变化可能导致意外的滚动。通常通过将 touch-action: none
样式应用于不应发生滚动的元素,可以很容易地解决这个问题。请继续阅读了解详细信息,了解您是否受到影响,以及您可以采取哪些措施。
Background: Cancelable Events slow your page down
背景: 可取消事件会让你的页面变慢
这里的 Cancelable Events
是指使用 event.preventDefault()
方法阻止 touch 事件阻止页面滚动.
If you call preventDefault()
in the touchstart
or first touchmove
events then you will prevent scrolling. The problem is that most often listeners will not call preventDefault()
, but the browser needs to wait for the event to finish to be sure of that. Developer-defined "passive event listeners" solve this. When you add a touch event with a {passive: true} object as the third parameter in your event handler then you are telling the browser that the touchstart listener will not call preventDefault() and the browser can safely perform the scroll without blocking on the listener. For example:
如果您在 touchstart
或第一个 touchmove
事件中调用 preventDefault()
,那么您将阻止滚动。问题在于,大多数情况下,监听器(事件回调函数)不会调用 preventDefault()
,但浏览器需要等待事件结束才能确保这一点。开发者定义的 passive event listeners 解决了这个问题。当您在事件处理程序中将 {passive: true}
对象作为第三个参数添加触摸事件时,您就告诉浏览器 touchstart
监听器不会调用 preventDefault()
,浏览器可以在不阻塞监听器的情况下安全地执行滚动。例如:
window.addEventListener("touchstart", func, { passive: true });
The Intervention
Our main motivation is to reduce the time it takes to update the display after the user touches the screen. To understand the usage of touchstart
and touchmove
we added metrics to determine how frequently scroll blocking behavior occurred.
我们的主要动机是减少用户触摸屏幕后更新显示所需的时间。为了理解 touchstart
和 touchmove
的使用情况,我们添加了指标来确定滚动阻塞行为发生的频率。
We looked at the percentage of cancelable touch events that were sent to a root target (window
, document
, or body
) and determined that about 80% of these listeners are conceptually passive but were not registered as such. Given the scale of this problem we noticed a great opportunity to improve scrolling without any developer action by making these events automatically "passive".
我们查看了发送到根节点(window
、document
或body
)的可取消触摸事件的百分比,并确定了大约 80% 的这些监听器在概念上是被动的,但并未作为此类进行注册。鉴于这个问题的规模,我们发现了一个很好的机会来改进滚动,而无需任何开发者操作,那就是通过自动将这些事件变为“被动”。
This drove us to define our intervention as: if the target of a touchstart
or touchmove
listener is the window
, document
or body
we default passive
to true
. This means that code like:
这使我们将干预定义为:如果 touchstart
或 touchmove
监听器的目标是window
、document
或body
,我们将passive
属性默认设置为 true
。这意味着像这样的代码:
window.addEventListener("touchstart", func);
becomes equivalent to: 等价于:
window.addEventListener("touchstart", func, {passive: true} );
Now calls to preventDefault()
inside the listener will be ignored.
现在在事件监听器中调用 preventDefault()
将会被忽略.
Breakage and Guidance
In the vast majority of cases, no breakage will be observed. But when breakage does occur, the most common symptom is that scrolling happens when you don't want it. In rare cases, developers may also notice unexpected click events (when preventDefault()
was missing from a touchend
listener).
在绝大多数情况下,该默认行为的变化(指Chrome 56 从 {passive: false}
变成了 {passive: true}
导致 touchmove
事件监听器中的 event.preventDefault()
不再起作用)不会出现问题。但当问题确实发生时,最常见的症状是在您不希望滚动时发生滚动。在极少数情况下,开发者可能还会注意到意外的点击事件(当 touchend
监听器中缺少 preventDefault()
) 时)。
In Chrome 56 and later, DevTools will log a warning when you call preventDefault()
in an event where the intervention is active.
在 Chrome 56 及更高版本中,当您在启用干预的事件中调用 preventDefault()
时,DevTools 将打印一个警告。
touch-passive.html:19 Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
Your application can determine whether it may be hitting this in the wild by checking if calling preventDefault
had any effect via the defaultPrevented
property.
您的应用程序可以通过检查调用 preventDefault
是否对 defaultPrevented
属性产生了任何影响来判断它是否在实际场景中遇到了这个问题。
We've found that a large majority of impacted pages are fixed relatively easily by applying the touch-action
CSS property whenever possible. If you wish to prevent all browser scrolling and zooming within an element apply touch-action: none
to it. If you have a horizontal carousel consider applying touch-action: pan-y pinch-zoom
to it so that the user can still scroll vertically and zoom as normal. Applying touch-action
correctly is already necessary on browsers such as desktop Edge that support Pointer Events and not Touch Events. For mobile Safari and older mobile browsers that don't support touch-action
your touch listeners must continue calling preventDefault
even when it will be ignored by Chrome.
我们发现,绝大多数受影响的页面通过尽可能应用 touch-action
CSS 属性可以相对容易地修复。如果您希望在元素内阻止所有浏览器滚动和缩放,请将 touch-action: none
应用于它。如果您有一个水平轮播,请考虑将 touch-action: pan-y pinch-zoom
应用于它,以便用户仍然可以正常地垂直滚动和缩放。在支持指针事件而不是触摸事件的浏览器(如桌面 Edge)上,正确应用 touch-action
已经是必要的。对于不支持 touch-action
的移动 Safari 和较旧的移动浏览器,即使 Chrome 会忽略它,您的触摸监听器仍然必须在调用 preventDefault
时继续调用。
In more complex cases it may be necessary to also rely on one of the following: 在更复杂的情况下,可能还需要依赖以下方法之一:
If your touchstart
listener calls preventDefault()
, ensure preventDefault()
is also called from associated touchend listeners to continue suppressing the generation of click events and other default tap behavior.
如果您的 touchstart
监听器调用了 preventDefault()
,请确保 preventDefault()
也从关联的 touchend
监听器中调用,以继续阻止 click
事件和其他默认点击行为的产生。
Last (and discouraged) pass {passive: false}
to addEventListener()
to override the default behavior. Be aware you will have to feature detect if the User Agent supports EventListenerOptions
.
最后(并不鼓励),将 {passive: false}
传递给 addEventListener()
以覆盖默认行为。请注意,您需要检测用户代理是否支持 EventListenerOptions
功能。
let passiveSupported = false;
try {
const options = Object.defineProperty({}, "passive", {
get: function() {
passiveSupported = true;
}
});
window.addEventListener("test", options, options);
window.removeEventListener("test", options, options);
} catch (err) {
passiveSupported = false;
}
const eventOptions = passiveSupported ? { passive: false } : false;
window.addEventListener("touchstart", yourFunction, eventOptions);
上述代码首先通过尝试设置并检测 passive
属性来检测浏览器是否支持 EventListenerOptions
。如果支持,passiveSupported
变量将设置为 true
。然后,根据 passiveSupported
变量的值,为 addEventListener()
创建适当的事件选项对象,其中将 passive
设置为 false
。这样可以覆盖默认行为,但请注意,这种方法并不鼓励使用,因为它可能会影响页面滚动性能。
Conclusion
In Chrome 56 scrolling starts substantially faster on many websites. This is the only impact that most developers will notice as a result of this change. In some cases developers may notice unintended scrolling. 在 Chrome 56 中,许多网站的滚动速度明显加快。这是大多数开发者会注意到的此次更改带来的唯一影响。在某些情况下,开发者可能会注意到意外的滚动。
Although it's still necessary to do so for mobile Safari, websites should not rely on calling preventDefault()
inside of touchstart
and touchmove
listeners as this is no longer guaranteed to be honored in Chrome. Developers should apply the touch-action
CSS property on elements where scrolling and zooming should be disabled to notify the browser before any touch events occur. To suppress the default behavior of a tap (such as the generation of a click event), call preventDefault()
inside of a touchend listener.
尽管仍然需要为移动 Safari 这样做,但网站不应依赖在 touchstart
和 touchmove
监听器中调用 preventDefault()
,因为在 Chrome 中不再保证会遵守该操作。开发者应在需要禁用滚动和缩放的元素上应用 touch-action
CSS 属性,以在任何触摸事件发生之前通知浏览器。要抑制点击行为的默认行为(例如 click
事件的生成),请在 touchend
监听器中调用 preventDefault()
。
Updated on Tuesday, July 31, 2018