Web Audio API系列之一Web Audio初探

本文介绍Web Audio API的简介以及使用范围等基础内容。

一、特性
Audio API主要有以下特性:

  • 为简单或复杂的混音/特效提供模块化路由架构,包括多路传送以及子混合(submixes)
  • 高动态范围,内部处理使用32位单精度浮点数
  • 对于需要极高节奏精度的音乐应用,例如鼓机和音序器,精确取样低延迟播放。同时也为动态创建音效提供了可能性
  • 为envelopes、淡入/淡出、粒状效应、滤波器扫描、LFOs等提供自动化的音频参数
  • 灵活的处理音频流中的声道,允许对其进行分离与合并
  • 可以从audio或video媒体元素中处理音频源
  • 可以处理从getUserMedia函数获取的媒体流
  • 与WebRTC集成:使用MediaStreamAudioSourceNode和webrtc处理从远端接收的音频;通过MediaStreamAudioDestinationNode和webrtc发送生成或处理过的音频流到远端
  • 音频流的合成与处理可以直接使用JavaScript
  • 支持各种3D游戏和身临其境的空间化音频:平移模型(等功率,HRTF,直通);距离衰减;声锥;阻塞/闭塞;多普勒频移;源/监听器
  • 卷积引擎适用于各种线性效果,尤其是非常高品质的室内效果,例如:小/大房间、大教堂、音乐厅、洞穴、隧道、门厅、圆形剧场、一个遥远的声音通过门口、极端过滤器、倒退效果、极端梳状滤波效果(Extreme comb filter effects)
  • 动态压缩整体控制以及甜味的混合(sweetening of the mix)
  • 高效的实时时域和频率分析/音乐可视化支持
  • 用于低通,高通和其他常见滤波器的高效双二阶滤波器。
  • 波形整形效果,用于失真和其他非线性效果
  • 振荡器

二、模块化路由
模块化路由允许不同AudioNode对象之间的任意连接。每个节点可以具有输入和(或)输出。源节点没有输入有一个输出。目的节点有一个输入,没有输出,最常见的例子是音频硬件的最终目的地AudioDestinationNode。可以在源节点和目的地节点之间放置其他节点,例如过滤器。当两个对象连接在一起时,开发人员不必担心低级流格式细节。例如,如果单声道音频流连接到立体声输入,它应该恰当地混合到左和右声道。
在最简单的情况下,单个源可以直接路由到输出。所有路由在单个AudioDestinationNode的AudioContext中:
Source→Destination
下面是一个简单的例子:

var context = new AudioContext();

function playSound() {
    var source = context.createBufferSource();
    source.buffer = dogBarkingBuffer;
    source.connect(context.destination);
    source.start(0);
}

模块化路由还允许将AudioNode的输出路由到控制不同AudioNode行为的AudioParam参数。在这种情况下,节点的输出可以用作调制信号而不是输入信号。

Content Security Policy(CSP)指令

本文介绍CSP的指令,基于CSP level 3,由于文档截止发文时未定稿,可能出现内容陈旧、错误等问题。

为了减缓跨站脚本攻击,网站开发者应该包含控制脚本和插件资源的指令。他们可以这样做:

  • 包含script-src和object-src指令或
  • 一个default-src指令

开发者不应在策略中包含’unsafe-inline’或data:指令。他们都会允许通过文档中直接包含的代码进行XSS攻击。因此最好彻底避免使用这两个指令。

child-src
child-src指令管理嵌套浏览上下文(iframe、frame)和Worker执行上下文。
示例:

Content-Security-Policy: child-src https://example.com/
下面的代码会引发网络错误,因为url不匹配给出的url模式:
<iframe src="https://not-example.com"></iframe>
<script>
  var blockedWorker = new Worker("data:application/javascript,...");
</script>

connect-src
connect-src指令限制从脚本接口加载的url。
通过脚本接口加载资源的方式指:fetch、XHR、eventSource、Beacon、a标签的ping、WebSocket。
示例:

Content-Security-Policy: connect-src https://example.com/
下面代码会引发网络错误:
<a ping="https://not-example.com">...
<script>
  var xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://not-example.com/');
  xhr.send();

  var ws = new WebSocket("https://not-example.com/");

  var es = new EventSource("https://not-example.com/");

  navigator.sendBeacon("https://not-example.com/", { ... });
</script>

default-src
default-src指令是在其他指令没有定义时的默认指令。
示例:

Content-Security-Policy: default-src 'self'
相当于下面的指令:
Content-Security-Policy: connect-src 'self';
                         font-src 'self';
                         frame-src 'self';
                         img-src 'self';
                         manifest-src 'self';
                         media-src 'self';
                         object-src 'self';
                         script-src 'self';
                         style-src 'self';
                         worker-src 'self'
以下指令:
Content-Security-Policy: default-src 'self'; script-src https://example.com
等价于:
Content-Security-Policy: connect-src 'self';
                         font-src 'self';
                         frame-src 'self';
                         img-src 'self';
                         manifest-src 'self';
                         media-src 'self';
                         object-src 'self';
                         script-src https://example.com;
                         style-src 'self';
                         worker-src 'self'

font-src
font-src指令限制字体文件资源加载的URL。
示例:

Content-Security-Policy: font-src https://example.com/
下面的代码会触发网络错误:
<style>
  @font-face {
    font-family: "Example Font";
    src: url("https://not-example.com/font");
  }
  body {
    font-family: "Example Font";
  }
</style>

frame-src
frame-src指令限制嵌套浏览上下文加载的URL。
示例:

Content-Security-Policy: frame-src https://example.com/
下面的代码会触发网络错误:
<iframe src="https://not-example.com/"></iframe>

img-src
img-src指令限制了图片资源加载的URL。
示例:

Content-Security-Policy: img-src https://example.com/
下面代码会触发网络错误:
<img src="https://not-example.com/img" />

manifest-src
manifest-src指令限制应用的manifests文件资源加载的URL。
示例:

Content-Security-Policy: manifest-src https://example.com/
下面代码会触发网络错误:
<link rel="manifest" href="https://not-example.com/manifest">

media-src
media-src指令限制了音频、视频、相关文本追踪的URL。
示例:

Content-Security-Policy: media-src https://example.com/
下面代码会触发网络错误:
<audio src="https://not-example.com/audio"></audio>
<video src="https://not-example.com/video">
    <track kind="subtitles" src="https://not-example.com/subtitles">
</video>

object-src
object-src指令限制插件内容加载的URL。
示例:

Content-Security-Policy: object-src https://example.com/
下面代码会触发网络错误:
<embed src="https://not-example.com/flash"></embed>
<object data="https://not-example.com/flash"></object>
<applet archive="https://not-example.com/flash"></applet>

如果插件的内容没有通过相关的URL(如object元素没有data属性,但是根据指定的类型加载了默认的插件),除非object-src的值是’none’必须阻止,否则都会允许。

script-src
script-src指令限制脚本执行的位置。这不仅包含直接通过script元素引用的URL还包括内联脚本块和XSLT样式表触发的脚本执行。

style-src
style-src指令限制样式应用的位置。包括link标签、@import、HTTP头加载的样式。

worker-src
worker-src指令限制Worker、SharedWorker或ServiceWorker加载的URL。
示例:

Content-Security-Policy: worker-src https://example.com/
下面代码会引发网络错误:
<script>
  var blockedWorker = new Worker("data:application/javascript,...");
  blockedWorker = new SharedWorker("https://not-example.com/");
  navigator.serviceWorker.register('https://not-example.com/sw.js');
</script>

base-uri
base-uri指令限制base元素中的URL。

plugin-types
plugin-types指令限制可被加载的插件类型。
示例:

Content-Security-Policy: plugin-types application/pdf
下面代码会引发网络错误:
<!-- No 'type' declaration(没有声明type) -->
<object data="https://example.com/flash"></object>

<!-- Non-matching 'type' declaration(声明的type和允许的type不一致) -->
<object data="https://example.com/flash" type="application/x-shockwave-flash"></object>

<!-- Non-matching resource(虽然声明的type一致,但其实返回的资源还是个flash类型,也会报错) -->
<object data="https://example.com/flash" type="application/pdf"></object>

下面的策略:
Content-Security-Policy: plugin-types application/x-shockwave-flash
会成功加载如下资源:
<!-- Matching 'type' declaration and resource -->
<object data="https://example.com/flash" type="application/x-shockwave-flash"></object>

sandbox
sandbox指令指定一个客户端需要应用于资源的HTML沙箱策略,就如同包含于带sandbox属性的iframe标签中。

disown-opener
disown-opener指令确保资源与其opener断绝关系。

form-action
form-action指令限制form表单提交的URL。

frame-ancestors
frame-ancestors指令限制可以被通过frame、iframe、object、embed、applet元素嵌入的资源的URL。

report-uri
report-uri指令用于报告非法请求,已过时,应使用report-to指令代替。

report-to
report-to指令用于发送非法的请求信息。

CSP3策略简介

Content Security Policy(CSP)定义了内容安全策略,它是一种缓解web应用受到内容注入攻击(如跨站脚本攻击XSS)的机制。它是一种由作者或服务器管理员声明的策略,用于描述web应用的客户端可以加载哪些源的资源。

一、介绍
为了减缓XSS攻击,一个web应用可以声明它只可以从指定的可信源加载脚本。这个声明允许客户端检测并阻止恶意的脚本被黑客注入到应用中。
CSP并不应作为防御内容注入漏洞的第一道防线,而是用于深度防护,它是为了减少由内容注入攻击造成的伤害。防御内容注入的第一道防线,应该由服务器来验证输入,以及对输出进行编码。
通常会对现有的web应用实现CSP策略,为了获得最大的利益,作者需要移除所有内联脚本和内联样式,例如将他们移动到外部脚本中。因为浏览器不能判断内联脚本是否是由攻击者注入的。
web应用程序通过Content-Security-Policy HTTP头来使用CSP策略。这些策略只适用于当前的资源。如果要为整个站点提供一个策略,服务器需要对每个资源进行声明。
应用示例:
开发者想要保护他们的应用免受跨站脚本攻击。他们只希望加载并执行可信的CDN上的脚本,并且他们希望确保在网页环境中不能执行插件,下面的政策可以实现:
Content-Security-Policy: script-src https://cdn.example.com/scripts/; object-src 'none'

二、第二版相对第一版本的变化
1.现在如果资源加载是重定向的结果,源表达式的路径组件会被忽略。
2.现在受保护的资源可以加载Workers由child-src而不是script-src控制。
3.Workers现在有自己的策略,从加载它们的保护资源中分离。
4.base-url控制受保护的资源可以从指定的文档基础URL加载。
5.child-url代替了frame-src,控制受保护的资源能够嵌入框架并加载Workers。(第三版又有变化)
6.form-action控制受保护的资源的表单提交。
7.frame-ancestors控制受保护资源能否嵌入其他文档。它意图取代X-Frame-Options HTTP请求头。
8.plugin-types控制受保护资源能否加载指定类型的插件。
9.特殊的内联脚本和样式可以通过随机数和哈希加入白名单。
10.CSP请求头现在跟随相关请求头发送。
11.SecurityPolicyViolationEvent事件会在违反策略时触发。
12.新增了一些违反策略的报告(通过report-uri提交以及通过SecurityPolicyViolationEvent事件)。它包括effectiveDirective、statusCode、sourceFile、lineNumber以及columnNumber。
13.现在在sandbox指令中的标志会影响Worker的实例。

三、第三版相对于第二版的变化
1.在CSP第二版中被废弃的frame-src指令,又生效了,worker-src指令添加了进来。如果两者都未出现则遵从child-src指令(如果仍没有则为default-src指令)。
2.URL匹配规则目前把不安全的模式和端口视为它们的安全变种。也就是说http://example.com:80将会同时匹配http://example.com:80和https://example.com:443
‘self’现在匹配https和wss,即使页面是http
3.从内联脚本和内联样式生成的违反报告现在报告“inline”作为阻止资源。阻止eval()的执行会报告“eval”。
4.增加了manifest-src指令。
5.report-uri指令废弃,使用新的report-to指令代替。
6.disown-opener指令确保资源不会被其他浏览器上下文控制。

四、策略的传达
服务器通过HTTP响应头向客户端传达策略。
1.Content-Security-Policy头域
Content-Security-Policy头域是传达策略的较好的机制,它的语法如下:
"Content-Security-Policy":策略字符串
例如,一个响应可能会包含以下头域:
Content-Security-Policy: script-src 'self'
服务器禁止发送多于1个叫做Content-Security-Policy的HTTP头域。
服务器可以针对不同的类型的资源发送不同的值。
在收到服务器响应至少一个Content-Security-Policy头域时,客户端必须执行其中的所有策略。

2.Content-Security-Policy-Report-Only头域
Content-Security-Policy-Report-Only头域只是监控策略而不执行,语法如下:
"Content-Security-Policy-Report-Only":策略字符串
例如,服务器操作员可能希望开发它们的安全策略迭代。操作者可以部署一个report-only策略以检测它们的表现:

Content-Security-Policy-Report-Only: script-src 'self';
                                     report-to csp-reporting-endpoint

在他们的网站中,违反策略的客户端会发送违反报告到report-to中指定的URL上,但是仍然允许违反策略的资源加载。一旦确认策略配置的正确,他们就可以使用Content-Security-Policy头域强制执行。
服务器禁止发送多于1个叫做Content-Security-Policy-Report-Only的HTTP头域。
服务器可以针对不同的类型的资源发送不同的值。
在收到服务器响应至少一个Content-Security-Policy-Report-Only头域时,客户端必须监控其中的所有策略。
Content-Security-Policy-Report-Only头在meta元素中不支持。

3.HTML meta元素
服务器可以通过一个或多个meta元素支持策略。例如:

<meta http-equiv="Content-Security-Policy" content="script-src 'self'">

4.CSP HTTP请求头
CSP头域声明了一个特殊的请求,它是一个策略的主题,它的值定义为下面的ABNF语法:
"CSP:" csp-header-value
csp-header-value = *WSP “active” *WSP
如果用户代理在监控或执行一个包含指令值为源列表的策略,那么用户代理必须与不匹配受保护的资源的来源请求一同发送一个叫做CSP的头域,它的值必须是active。
用户代理可以选择发送这个头,仅当请求的资源类型是活动的策略。也就是说,给定的策略img-src example.com,用户代理会同图片请求一同发送CSP:active头,可以在其他类型的资源中不添加这个头。

5.执行多个策略
上文说道,当有多条策略出现时,每一条必须根据相关类型被执行或报告:

Content-Security-Policy: default-src 'self' http://example.com http://example.net;
                         connect-src 'none';
Content-Security-Policy: connect-src http://example.com/;
                         script-src http://example.com/

连接到example.com是否允许?回答是不允许。执行多个策略指的是通过所有的策略。即使第二条策略会允许连接,但第一条策略包含connect-src ‘none’,所以它会阻止连接。向策略列表添加额外的策略只会更进一步的限制保护资源的能力。
进一步证明,考虑在这个页面的一个脚本。第一个策略会通过default-src指令锁定脚本资源到自身、http://example.com和http://example.net来源。第二条,只会允许脚本来源于http://example.com/。脚本只会在两个策略同时符合时加载,这个例子中来源只有是http://example.com/时符合。

五、策略语法
CSP使用分号“;”分割多个策略。每个策略由下面的语法构成:
WSP表示空白字符,VCHAR表示可打印字符

policy-token    = [ directive-token *( ";" [ directive-token ] ) ]
directive-token = *WSP [ directive-name [ WSP directive-value ] ]
directive-name  = 1*( ALPHA / DIGIT / "-" )
directive-value = *( WSP /  )

解析策略步骤:
1.让directives集合为空的集合
2.遍历每一个非空的token,返回以分号为分隔符对policy进行严格的拆分:
I.跳过空白字符
II.收集非空格的其他字符序列,它们是指令名称
III.如果在token中仍然有字符保留,跳过开头的一个字符(一定是一个空格)
IV.token剩下的字符(如果由)就是指令的值
V.如果指令集合已经包含一个大小写不敏感的匹配指令名,忽略这个指令,继续下一个token
VI.添加指令到指令集合中,名为指令名,值为指令值
3.返回指令集合

具体指令的用法会在后面的文章中说明。

Referrer策略与防盗链

Referrer策略用于控制浏览器在何种情况下发送referrer信息。该策略可以保护用户的隐私,但也可以使得目前绝大多数站点的防盗链机制失效。

一、介绍
从一个文档发出的请求,以及从文档导航到其他页面,都会有一个Referer头。出于以下原因,我们有时会希望浏览器不发送referer头:
1.隐私
一个社交网站会有每个用户的简介页面,用户会在他们的个人主页中添加一些链接。社交网站可能不希望泄露用户的个人主页URL给被链接的网站(因为个人主页URL可能会泄露其主人的身份信息)。
一些社交网站可能想通知其他网站该链接是从社交网站发起的,但不想泄露包含用户信息的链接(例如,微博中的链接希望告诉对方该链接是在微博中连接过来的,但不希望告诉对方从谁的微博连接过来)。

2.安全
一个网站应用使用https和基于URL的会话标识。应用也许希望链接其他站点的https资源但不想泄露位于URL中的用户会话标识符。
或者,应用可以使用一些url自身的能力(如重置密码的链接、通过链接直接登录账号等)。控制referrer有助于阻止这些有特殊能力的url泄露referrer头。
注意,有其他方式避免这些url的泄露,控制referrer并不足以控制所有的泄露情况。

3.引用
基于HTTPS的博客可能希望连接到一个HTTP上的博客并收到引用链接。

二、referrer策略
Referrer策略包含以下值:

  1. 空字符串
  2. no-referrer
  3. no-referrer-when-downgrade
  4. same-origin
  5. origin
  6. strict-origin
  7. origin-when-cross-origin
  8. strict-origin-when-cross-origin
  9. unsafe-url

下面会详细讲解每种Referrer策略。
1.no-referrer
最简单的策略是“no-referrer”,表示所有的请求都不带referrer。
http://www.lyz810.com/demo/referrer/index.php?referrer=no-referrer

2.no-referrer-when-downgrade
主要针对于受TLS保护的URL(如https),简单的说就是https的页面中,当连接的资源也是https的,则发送完整的referrer,如果连接的资源是http的,就不发送referrer
https://www.lyz810.com/demo/referrer/index.php?referrer=no-referrer-when-downgrade
此例中,音乐可以播放,因为他是http的,不发送referrer,iframe可以显示referrer,因为是https协议。
这个是在没有特别指定referrer策略时,浏览器的默认行为。

3.same-origin
对于同源的链接,会发送referrer,其他的不会。同源意味着域名需要相同,example.com和not.example.com是非同源的。
http://www.lyz810.com/demo/referrer/index.php?referrer=same-origin
上面的例子中可以看到,音乐无法播放了(因为是他站资源),而iframe嵌套的同源页面仍然可以读到referrer。

4.origin
这个策略对于任何资源来说只发送源的信息,不发送完整的url。
http://www.lyz810.com/demo/referrer/index.php?referrer=origin
此例中,音乐无法播放,因为它发送了referrer,iframe中显示的referrer只包含源的信息,不包含完整的url。

5.strict-origin(浏览器可能不支持)
这个策略类似于origin和no-referrer-when-downgrade的合体,如果一个https页面中链接到http的页面或资源,则不会发送referrer。http页面链接以及https链接到https都只发送来源页面的源信息。
https://www.lyz810.com/demo/referrer/index.php?referrer=strict-origin
此例中,音乐正常播放,因为是http的资源,不发送referrer,而iframe中只有源信息。

6.origin-when-cross-origin
该策略在同源的链接中发送完整的URL,其他情况仅发送源信息。相同的域名,http和https协议被认为是非同源的。
http://www.lyz810.com/demo/referrer/index.php?referrer=origin-when-cross-origin
此例中,音乐不能播放,发送源信息,iframe显示完整url。

7.strict-origin-when-cross-origin(浏览器可能不支持)
对于同源请求,发送完整的URL;对于同为https的,只发送源信息;对于http页面只发送源信息;https页面中的http请求不发送referrer。

8.unsafe-url
这个主要是解决https页面中的http资源不发referrer的问题,它会使在https页面中http资源发送完整的referrer。
https://www.lyz810.com/demo/referrer/index.php?referrer=unsafe-url
此例中,音乐不能播放,虽然页面是https,资源是http,但unsafe-url使得浏览器仍发送referrer。

9.空字符串
空字符串表示没有referrer策略,默认为no-referrer-when-downgrade。

三、用法
Referrer策略可以通过以下方法声明:
1.通过http请求头中的Referrer-Policy字段
2.通过meta标签,name为referrer
3.通过<a>、<area>、<img>、<iframe>、<link>元素的referrerpolicy属性。
4.通过<a>、<area><link>元素的rel=noreferrer属性
5.通过隐式继承

四、用法举例
1.http请求头
Referrer-Policy: no-referrer
2.meta标签
<meta name=”referrer” content=”no-referrer” />
3.referrerpolicy属性
<a href=“http://example.com” referrerpolicy=“origin”>
4.rel=noreferrer属性

五、注意事项
Referrer策略还有其他历史遗留的值:
1.never等价于no-referrer
2.default等价于no-referrer-when-downgrade
3.always等价于unsafe-url
4.不建议使用上面三个值,建议使用后面的新值

六、兼容性
IE:不支持(IE高版本中隐式支持default,https页面拉取的http资源不会加referrer)
Edge:仅支持较早版本的值(never、always、origin、default)
Firefox:36+
Chrome:21+
Safari:7.1+(仅支持较早版本的4个值)
Opera:15+
iOS Safari:8+(仅支持较早版本的4个值)

七、关于防盗链
目前大部分网站采用的是判断referrer是否是当前域名或指定白名单域名下的url。而没有referrer的请求都会放行。
referrer策略普及后,单从referrer判断防盗链的方法会失效,所以需要考虑其他的技术手段实现防盗链机制。

HTML5中input元素的支持类型

目前input元素定义了22种不同的type,它们在不同浏览器中的表现并不一致。本文主要用于在不同设备及浏览器中测试它们的区别。

不同类型的input在不同浏览器中表现的方式有所不同,尤其在移动浏览器中(有些类型会调用不同的键盘类型),下面所有的示例建议在不同的浏览器中测试。

一、文本类型(默认值)-text
这个没有什么好说的,text类型是input的默认类型,当input设置为一个不支持的类型或没有设置类型时,浏览器会将type设置为text

二、电子邮件类型-email
在提交表单时,会验证输入的是否符合电子邮件格式要求,但这个验证比较不准,必要时还需要通过代码进行验证

三、url类型-url
提交表单时会验证是否符合url

四、数字类型-number
只能输入数字,一般浏览器会出现上下三角按钮来控制数值的增减(这个可以通过css控制),也可以直接输入数字。number类型的input支持step属性表示一次增加或减少多少,min为最小值,max为最大值。如果用户手动输入,在输入时是可以突破这些界限的。

五、范围类型-range
在浏览器中多为一个滑杆(类似于调节音量的滑杆),同样支持min、max、step属性。

六、日期类型-date
可以选择年月日。

七、月份类型-month
可以选择年和月。

八、周类型-week
可以选择周和年。

九、时间类型-time
可以选择小时和分钟。

十、日期时间类型-datetime-local
可以选择年月日和时间(本地时间)。

十一、隐藏类型-hidden
不在页面上显示,用于传递一些不需要用户输入的值。

十二、搜索类型-search
这个与普通的text并没有什么区别。

十三、颜色类型-color
用于选取颜色,会调出调色板。

十四、电话类型-tel
用于输入电话号码,一般移动浏览器会自动调用数字键盘。

十五、密码类型-password
用于输入密码,显示为实心圆点或星号等。

十六、复选框-checkbox
可以同时选择多个值,可以通过点击取消。

十七、单选框-radio
只能选择一个值,再次单击也不能取消,只能通过单击相同name的其他radio来取消当前的radio。

十八、文件类型-file
用于选择文件。

十九、表单提交-submit
用于提交表单,与button外观一致。

二十、图片类型-image
在表单中显示图片。

二十一、重置类型-reset
重置表单的所有内容。

二十二、按钮类型-button
一个单纯的按钮,没有默认的事件。

页面可见状态API使用方法

Page Visibility API用于获取当前页面的可见性状态,在音频、视频播放、游戏等场景中十分有用。

本文Demo请见:https://www.lyz810.com/demo/pageVisibility/
一、应用场景

页面可见性状态一般可以用于以下场景:

  1. 音频或视频(尤其是视频)播放时,当页面不可见自动暂停播放,页面恢复后再继续播放
  2. 一些定时执行的操作(如微博获取最新的微博条数,该动作每分钟获取一次),没有必要在用户不可见的时候去获取数据
  3. 网页游戏在用户切换页面后,游戏自动暂停,用户体验更好
  4. 统计用户停留时长,这种方式比目前常用的两次页面打开时间之差得到用户停留时长更准确

二、document.visibilityState属性值

document.visibilityState有四个值(后两个是可选属性,浏览器可以不实现):

  1. hidden
  2. visible
  3. prerender
  4. unloaded

以下情况,document.visibilityState的值为hidden:

  • 浏览器最小化
  • 浏览器没有最小化,但是页面是非活动的标签页(即切换到其他标签),浏览器被其他窗口覆盖值不会受影响
  • 操作系统锁屏

在页面可见时,值为visible

在页面的父级页面不可见时,值可以为prerender(该属性为可选的)

在文档卸载时,值应为unloaded(该属性为可选的)

三、document.hidden属性

document.hidden为布尔型,当页面不可见(hidden)时为true,可见(visible)时为false

四、visibilitychange事件

document中的visibilitychange用于监听可见状态改变的事件,用法如下:

document.addEventListener('visibilitychange', function(){
  console.log(document.visibilityState);
}, false);

五、兼容性

IE:10+
FireFox:18+(10~17需要加moz前缀)
Chrome:33+(14~32需要加webkit前缀)
Safari:6.1+
Opera:12.1以及20+(15~19需要加webkit前缀,12.1不需要加前缀)

Beacon API介绍

Beacon API可以向指定服务器发送少量的数据以实现统计等目的。

一、应用场景

主要用于记录并发送用户行为、发送错误日志等。
之前记录用户行为通常是通过发送一个GET请求(一般是一个带所有统计信息参数的图片请求),使统计服务器获取到用户行为数据并记录。
这种方式有以下几个弊端:
1.当页面关闭时,发送的统计请求可能不会成功,导致统计数据丢失
2.通常为了解决关闭页面的统计数据丢失问题,会发起一个同步的请求,这样会阻塞页面的卸载
3.GET请求通常有长度限制,可携带的数据量有限,并且一般只能携带文本信息
而使用Beacon API后,上述问题都能够解决:
Beacon是以非阻塞POST的方式发送数据,支持多种数据类型:ArrayBufferView, Blob, DOMString, 或者 FormData 类型的数据
关闭页面不会中断Beacon的发送,也不会阻塞页面的卸载,用户体验更好

二、API用法

navigator.sendBeacon(url, data);
url 参数表明 data 将要被发送到的网络地址。(不存在跨域问题,但https的页面中不能向http协议的统计服务器发送数据,会报安全错误)
data 参数是将要发送的 ArrayBufferView, Blob, DOMString, 或者 FormData 类型的数据。
sendBeacon() 方法返回 true 如果用户代理能够成功地将要发送的数据排入队列,否则返回 false。
示例:

document.getElementById('container').addEventListener('click', function(e){
   if(e.target.tagName === 'A'){
      var formData = new FormData();
      formData.append('type', 'click');
      formData.append('elementType', 'link');
      formData.append('href', e.target.href);
      formData.append('t', Date.now());
      navigator.sendBeacon('http://tongji.example.com', formData);
   }
}, false);

window.addEventListener('unload', function(){
    var data = JSON.stringify({
        type: 'unload',
        t: Date.now()
      });
    navigator.sendBeacon('http://tongji.example.com', data);
}, false);

上面示例中,为container下的所有A标签的点击事件进行了统计,当点击链接时,会发送统计信息到统计服务器tongji.example.com上,并以form表单的形式发送。
在页面卸载时,也会将相关信息以json字符串的方式发送给统计服务器
注意此例为了演示不同的发送方式采用了两种不同的数据类型,实际使用时建议同一使用一种数据类型便于统计处理。

三、兼容性

IE:不支持
Edge:14+
Firefox:31+
Chrome:39+
Opera:26+

四、注意事项

该API没有响应回调方法,所以建议服务器返回204状态码
该API同样可以应用于一些只需要发送数据而不需要关心返回结果的场景(如CSRF攻击中使用此API构造POST请求,要比模拟表单提交成本低很多)

getBattery API用法详解

本文介绍getBattery API的使用方法及注意事项。getBattery API是获取电源管理信息的API。

本文内容可以在https://www.lyz810.com/demo/battery/中查看相关Demo。
一、概述
navigator.getBattery是一个用于获取电源管理信息的API。它可以获取电源的连接状态、电池电量水平、剩余放电时间、剩余充电时间等于电源管理相关的参数。

二、兼容性
navigator.getBattery基本只能在Chrome及Firefox浏览器上使用,其他浏览器基本上都没戏。
Firefox:43+
Chrome:38+
Opera:25+

三、基本用法

  navigator.getBattery().then(function(batteryInfo){
    console.log(batteryInfo);
  });

navigator.getBattery返回一个Promise对象,通过then的第一个function,可以获取到电池状态信息。
其中batteryInfo对象如下:
batteryInfo.charging:电源状态,true表示电源接通(但是否充电不一定),false表示电源没有接通。
batteryInfo.chargingTime:充电时长,目前测试没有发现此数值除Infinity外还有其他值,可能与系统等因素有关。
batteryInfo.dischargingTime:剩余时长,在不接电源时此属性会有一个非Infinity的值,表示电池剩余时间的秒数。
batteryInfo.level:电池电量水平,范围0~1表示0%~100%。
batteryInfo.onchargingchange:当切换电源状态时触发,如插入电源和拔出电源。
batteryInfo.onchargingtimechange:目前未发现会触发事件,因为chargingTime总是Infinity。
batteryInfo.ondischargingtimechange:电池剩余时间改变时触发该事件。
batteryInfo.onlevelchange:在电池电量水平改变时触发。

四、注意事项
Firefox中电量水平的精度为0.1,会对电量进行四舍五入,如电量为95%时,Firefox电量显示为100%(1),当电量为94%时,Firefox电量显示为90%(0.9)
Chrome中电量水平的精度为0.01,能够精确的获取1%的电量差距
电池剩余时间估算可能与系统的电池剩余时间不同

HTML5实现多文件上传(支持文件拖拽及上传进度与速度)

本文介绍通过HTML5新增的多个API实现多文件(拖拽)上传,支持上传进度及上传速度显示。

本文示例可以在https://www.lyz810.com/demo/fileUpload/上查看。

一、input的multiple属性
input的file类型中新增了multiple属性,用于选择多个文件:
<input type="file" multiple="multiple" />

二、input的accept属性
accept属性用于限制选择文件的类型,例如accept=”image/*”则只能选择图片,其值为文件的MIME类型,可以由逗号分割多个值,如accept=”image/*,text/plain”。MIME类型可以参考http://www.w3school.com.cn/media/media_mimeref.asp

注意:此处只是限制打开文件选择框时默认展现的文件类型,用户可以通过更改选择文件类型,选择其他类型的文件,所以应该对文件类型进行校验。

三、progress元素
progress元素为HTML5新增元素,用于显示进度条,默认样式如下: 你的浏览器不支持此标签

元素的max属性为进度条满时的值
给元素设置value属性,即可控制进度条的进度,如设置进度为50%(value=50,max=100): 你的浏览器不支持此标签

四、拖拽事件
本例中使用的拖拽事件有以下几个:
1.ondragover:当文件出于元素上方时触发
2.ondragout:当文件脱离元素时触发
3.ondrop:当文件在元素上,并且鼠标释放时触发

五、通过Ajax上传文件
本例中,多个文件是通过多个请求上传的。这样上传的好处是,对于每一个文件来说,都可以根据上传结果来判断是否需要重传。
也可以通过一个请求同时上传多个文件,但如果上传过程中出错,那么重传的成本要更高,具体使用哪种方式可以根据自己的实际需要进行选择。
本例中,若服务器返回5xx状态码则认为上传失败,会重新上传失败的文件(如果失败3次则不再尝试重传)。
代码片段如下(截取核心代码,完整代码请进入Demo页面查看):

function uploadFile(index){
  var formData = new FormData();
    formData.append('file', files[index]);
    var xhr = new XMLHttpRequest();
    xhr.open('POST', './backend.php', true);
    xhr.onreadystatechange = function(){
      if(xhr.readyState === xhr.DONE){
        if(xhr.status >= 200 && xhr.status < 300){ 
          failCount = 0;
          if(files[++index]){ 
            uploadFile(index);
          } 
        } else if(xhr.status >= 500 && xhr.status < 600){ 
           failCount++;
           if(failCount > 3){
             failCount = 0;
             if(files[++index]){
               uploadFile(index);
             }
           }else{
            setTimeout(function(){
              uploadFile(index);
            }, 1000);
          }
        }
      }
    };
    xhr.send(formData);
}

六、上传进度与上传速率查看
XMLHttpRequest对象的upload.onprogress事件可以提供上传进度相关的数据
它的参数中包含了已上传字节数loaded、总字节数total、时间戳timeStamp
通过上传字节数/总字节数可以得出上传进度的百分比
通过两次onprogress事件timeStamp之差可以得出两次的时间差(毫秒),通过两次的loaded值可以得出该时间段内上传的字节数
多个文件上传时,可以算出一个文件占整体的百分比,当前上传的文件索引可以得出已经上传的文件个数,用已上传的文件个数乘上每个文件的占比,就可以得到当前文件上传起始时的整体百分比,加上当前文件上传的百分比*每个文件占用的百分比即可得出整体进度的百分比。
这样就可以大致的算出上传的速率:

var timeStamp = 0,
  loaded = 0,
  start = Date.now();
xhr.upload.onprogress = function(e){
  var curProgress = (e.loaded * 100 / e.total).toFixed(2),
    //index:当前文件索引,files:所有要上传的文件
    totalProgress = index / files.length + curProgress / files.length * .01,
    timeInterval = 0,
    rate = 0;
  if(timeStamp > 0){
    timeInterval = e.timeStamp - timeStamp;
  }else{
    timeInterval = Date.now() - start;
  }
  rate = (e.loaded - loaded) / 1024 / timeInterval * 1000;
  timeStamp = e.timeStamp;
  loaded = e.loaded;
};