本文为HTML标准解读系列文章,其他文章详见这里。
你可能也遇到过,页面内的video/audio标签无法自动播放的问题:
<video src="./test.mp4" autoplay></video>
这时如果你添加muted
属性,他就能自动播放了,虽然是在没有声音的情况下:
<video src="./test.mp4" autoplay muted></video>
这其实是浏览器的防骚扰机制:只有在用户至少与页面交互过一次的情况下,浏览器才允许页面放出声音。
而声音限制只是这个防骚扰机制的其中一部分内容,一些浏览器API也受到同样的约束,只有在页面曾经被激活过的情况才能使用。还有一些API,有着更加严格的要求,必须是用户正在与页面进行积极的交互才能触发。具体是哪些API,我们会在下面提到,不过在此之前,让我们先来看看HTML的页面激活机制。
HTML页面激活机制
HTML页面可以有两种激活状态:
- 粘性激活态(Sticky activation):只要用户与页面交互过一次,页面就会一直处于这个状态,这个状态可以判断用户是否曾经与页面有过交互。
- 短暂激活态(Transient activation):用户与页面的每次交互,都会让页面处于这个状态。但这个状态是有“有效期”的,如果在交互后的一段时间内,用户没有再与页面进行交互,这个状态就会过期。具体的有效期时长由浏览器决定,比如chrome是5秒。
那么,怎么样才算用户与页面“进行了一次交互”呢??标准答案是:当页面触发了以下事件,且事件的isTrusted
属性值为true时:
-
keydown
,且不是ESC
键以及不是浏览器使用的其他快捷键; -
mousedown
; -
pointerdown
,且pointerType
属性是"mouse"; -
pointerup
,且pointerType
属性不是"mouse"; -
touchend
。
可以看出,这里的一些条件,是防止开发者通过脚本伪造真实用户行为来激活页面的。比如对于isTrusted
属性值的要求:isTrusted
是一个只读的属性,由脚本创建的事件,其值为false,只有由用户真实动作触发的事件,其值才为true。
查看页面激活状态
有两个API可以查看页面的激活状态:
-
navigator.userActivation.hasBeenActive
:查看页面是否处于粘性激活态。 -
navigator.userActivation.isActive
:查看页面是否处于短暂激活态。
于是,通过检测页面激活状态,我们可以使视频带有声音地自动播放。在以下的代码中,我使用脏检查,当用户第一次激活页面的时候,就会给页面插入一个视频并自动播放:
<script>
const t = setInterval(() => {
if (navigator.userActivation.hasBeenActive){
// 页面进入粘性激活态
const v = document.createElement('video')
v.src = './test.mp4'
v.autoplay = true
document.body.appendChild(v)
clearInterval(t)
}
}, 1000);
</script>
遗憾的是,虽然这两个API已经写进了标准,但是目前safari以及firefox并不支持这两个API。
受页面激活态限制的浏览器API
所有受到页面激活态限制的API,可以分为三个类别:
- 粘性激活态-限制API(Sticky activation-gated APIs) :页面必须处于粘性激活态才能使用的API。如:
- 音频自动播放,如上面所示。
- 移动端震动功能相关API:Navigator.vibrate()
- 短暂激活态-限制API(Transient activation-gated APIs) :页面必须处于短暂激活态才能使用的API。如:
- 当iframe的
sandbox
属性声明allow-top-navigation-by-user-activation
关键词时,尝试在iframe外的浏览上下文中导航。 - 对于
type=file
或type=color
的input元素,使用input.showPicker()
的方法。 - 剪切板Clipboard相关API。
- 短暂激活态-销毁API(Transient activation-consuming APIs) :页面必须处于短暂激活态才能使用的API,但每一次使用后都会促使页面短暂激活态过期,以此来避免重复调用API。换句话说,每一次用户交互,这类API最多只能被调用一次。
-
window.open()
方法。
HTML标准并没有对受页面激活态限制的API进行一个完整的总结,它们大部分散落在不同的web标准/草案中,所以我在这里只能列举一些常用的API。其他更多的API,也可以查看MDN这篇文档。