Web audio 空间化基础 - Web API 接口参考

Web audio 空间化基础
===============

正如大量的各种声音处理（或者其他）选择是不够的，WebAduioAPI 也包含了一些工具，可以让你模仿听众在声源周围移动时的声音差异，例如当你在 3D 游戏声源中移动时声音的平移。官方名词称为 **空间化**，这篇文章将会介绍如何实现这样一个系统的基础知识。

空间化的基础知识
--------

在 Web Audio 中，复杂的 3D 空间化是使用 `PannerNode` (en-US) 创建的，用外行人的话来说就是一个使音频出现在 3D 空间中的很酷的数学。想象一下声音从你头上飞过，爬到你身后，在你面前移动。诸如此类的事情。

它对 WebXR 和游戏非常有用。在 3D 空间中，它是实现逼真的音频效果的唯一方式。像 three.js 和 A-frame 这样的库在处理声音时就利用了它的潜力。值得注意的是，你不必在完整的 3D 空间中移动声音 - 你可以只使用 2D 平面，因此如果你计划实现一个 2D 游戏，这依然是你要寻找的节点。

**备注：** 还有一个设计用于处理创建简单的左右立体声平移效果的 `StereoPannerNode` (en-US) 。这使用起来更简单，但显然无处可用。如果你只想要一个简单的立体声平移效果，我们的 StereoPannerNode 示例（请参阅源码）应该可以为你提供所需的一切。

3D boombox 演示
-------------

为了演示 3D 空间化，我们在 Using the Web Audio API 指南中的 boombox 演示的基础上创建一个修改版本。参见 3D spatialization demo live （同时也可以看 source code）

![A simple UI with a rotated boombox and controls to move it left and right and in and out, and rotate it.](/zh-CN/docs/Web/API/Web_Audio_API/Web_audio_spatialization_basics/web-audio-spatialization.png)

音箱放置于房间中（由浏览器视区边缘定义），在本 demo 中我们可以通过提供的控件移动和旋转它。当我们移动音箱时，它产生的声音会相应的改变，当它在移动到房间的左侧或右侧时声音平移，或当它远离用户时变得安静，或旋转使得扬声器背离它们等。这是通过给 `PannerNode` 对象实例设置不同的与该运动有关的属性来实现模拟空间化。

**备注：** 如果你使用耳机或者其他某种环绕声系统连接计算机，体验会更好。

创建 audio 收听者
------------

让我们开始！ `BaseAudioContext` (en-US)（ `AudioContext` 扩展自该接口）有一个 listener 属性，返回一个 `AudioListener` 对象。这代表着场景收听者，通常是使用者（用户）。你可以定义他 (她) 们在空间中的位置和他 (她) 们面向的方向。他 (她) 们保持静止。 `pannerNode` 可以计算出声音相对于收听者位置的位置。

让我们创建我们的上下文和监听器，并设置收听者的位置来模拟一个看向（探索）我们房间的人：

```js
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
const listener = audioCtx.listener;

const posX = window.innerWidth / 2;
const posY = window.innerHeight / 2;
const posZ = 300;

listener.positionX.value = posX;
listener.positionY.value = posY;
listener.positionZ.value = posZ - 5;
```

我们可以使用 `positionX` 将收听者向左/右移动，使用 `positionY` 向上/下移动，或使用 `positionZ` 移出/入房间。在这里，我们将收听者设置在视口中间同时稍微位于音箱的前方。我们还可以设置收听者面对的方向。这些默认值工作良好：

```js
listener.forwardX.value = 0;
listener.forwardY.value = 0;
listener.forwardZ.value = -1;
listener.upX.value = 0;
listener.upY.value = 1;
listener.upZ.value = 0;
```

这些 forward 属性代表了收听者前进方向的 3D 坐标位置（例如他/她们面向的方向），而 up 属性表示了收听者头顶的 3D 坐标位置。这两种属性值可以很好的设定方位。

创建 panner 节点
------------

让我们创建 `PannerNode` (en-US)节点，这有很多与之相关的属性。让我们来一一看看：

首先我们可以设置 `panningModel` (en-US)。这是用于在 3D 空间中定位音频的空间化算法。我们可以将其设置为：

`equalpower` — 计算出默认和一般方式的平移。

`HRTF` — 这代表 'Head-related transfer function' ，在弄清楚声音的位置时，会考虑人脑（对声音的处理）。

非常聪明的东西，让我们使用 `HRTF` 模型！

```js
const pannerModel = "HRTF";
```

属性 `coneInnerAngle` (en-US) 和 `coneOuterAngle` (en-US) 指定音量发出的位置。默认情况下，两者都是 360 度。我们可以定义音箱扬声器拥有较小的锥体。内锥是总是模拟增益（音量）最大值的地方，外锥是增益开始下降的地方。

增益通过 `coneOuterGain` (en-US) 值来减少。让我们创建之后将会用于这些参数的常量：

```js
const innerCone = 60;
const outerCone = 90;
const outerGain = 0.3;
```

 下一个参数是 `distanceModel` (en-US) — 这只能设置为 `linear`, `inverse`, 或者 `exponential`。这些是不同的算法，用于在音频源远离收听者时减小音频源的音量。
 我们将使用`linear`，因为它很简单：

```js
const distanceModel = "linear";
```

我们可以设置源和收听者之间的最大距离 (`maxDistance` (en-US)) — 如果源距离此点更远，则音量将不再减小。这可能很有用，因为你可能会发现你想要模拟距离，但是音量会下降，而实际上并不是你想要的。默认情况下，它是 10,000（无单位的相对值）。我们可以像这样保持它：

```js
const maxDistance = 10000;
```

还有一个参考距离 (`refDistance` (en-US))，由距离模型使用。我们也可以将其保持为默认值 `1`：

```js
const refDistance = 1;
```

然后就是 roll-off 因子 (`rolloffFactor` (en-US)) — 描述随着 panner 远离收听者，音量减小的速度有多快。默认值为 1；让我们使其大一些以放大我们的动作。
```

const rollOff = 10;
```

 现在我们可以开始设置我们 boombox 的位置和方向。这与我们如何设置收听者很像。
 这些也是我们在使用界面上的控件时要改变的参数。

```js
const positionX = posX;
const positionY = posY;
const positionZ = posZ;

const orientationX = 0.0;
const orientationY = 0.0;
const orientationZ = -1.0;
```

 注意 z 方向的负值 - 这会将 boombox 设置为面向我们。正值会使声源背离我们。
 让我们使用相关的构造函数来创建我们的 panner 节点，并传入我们在上面设置的所有参数：

```js
const panner = new PannerNode(audioCtx, {
  panningModel: pannerModel,
  distanceModel: distanceModel,
  positionX: positionX,
  positionY: positionY,
  positionZ: positionZ,
  orientationX: orientationX,
  orientationY: orientationY,
  orientationZ: orientationZ,
  refDistance: refDistance,
  maxDistance: maxDistance,
  rolloffFactor: rollOff,
  coneInnerAngle: innerCone,
  coneOuterAngle: outerCone,
  coneOuterGain: outerGain,
});
```

移动 boombox
----------

 现在我们将在我们的“房间”中四处移动 boombox。我们已经设置了一些控件来执行此操作。我们可以左右移动，上下移动，来回移动；我们也可以旋转它。声音方向来自前面的 boombox 的扬声器，因此当我们旋转它时，我们可以改变声音的方向 - 即当音箱旋转 180 度并背向我们时，使其向后投射。
 我们需要为界面设置一些东西。首先，我们将获得我们想要移动的元素的引用，然后存储我们在设置 CSS transforms 来实际执行移动时将要更改的值的引用。
 最后，我们将设置一些边界值，以便我们的 boombox 在任何方向上都不会走得太远：

```js
const moveControls = document
  .querySelector("#move-controls")
  .querySelectorAll("button");
const boombox = document.querySelector(".boombox-body");

// the values for our css transforms
let transform = {
  xAxis: 0,
  yAxis: 0,
  zAxis: 0.8,
  rotateX: 0,
  rotateY: 0,
};

// set our bounds
const topBound = -posY;
const bottomBound = posY;
const rightBound = posX;
const leftBound = -posX;
const innerBound = 0.1;
const outerBound = 1.5;
```

 让我们创建一个函数，将我们想要移动的方向作为参数，并且修改 CSS 变换及更新我们的 panner 节点的位置和方向属性值以适当地更改声音。
 首先让我们看看左，右，上，下值，因为这些非常简单。我们将沿着这些轴移动 boombox，并更新合适的位置。

```js
function moveBoombox(direction) {
  switch (direction) {
    case "left":
      if (transform.xAxis > leftBound) {
        transform.xAxis -= 5;
        panner.positionX.value -= 0.1;
      }
      break;
    case "up":
      if (transform.yAxis > topBound) {
        transform.yAxis -= 5;
        panner.positionY.value -= 0.3;
      }
      break;
    case "right":
      if (transform.xAxis < rightBound) {
        transform.xAxis += 5;
        panner.positionX.value += 0.1;
      }
      break;
    case "down":
      if (transform.yAxis < bottomBound) {
        transform.yAxis += 5;
        panner.positionY.value += 0.3;
      }
      break;
  }
}
```

移入和移出也是类似的故事：

```js
case 'back':
    if (transform.zAxis > innerBound) {
        transform.zAxis -= 0.01;
        panner.positionZ.value += 40;
    }
break;
case 'forward':
    if (transform.zAxis < outerBound) {
        transform.zAxis += 0.01;
        panner.positionZ.value -= 40;
    }
break;
```

 然而，我们的旋转值稍为复杂，因为我们需要在周围移动声音。我们不仅需要更新两个轴值（例如，如果围绕 x 轴旋转对象，则更新该对象的 y 和 z 坐标），还需要为此进行更多的数学运算。旋转是一个圆圈，我们需要 `Math.sin` 和 `Math.cos` 来帮助我们绘制这个圆圈。
 让我们设置一个旋转速率，我们将会将它转换为弧度范围的值，以便稍后在 `Math.sin` 和 `Math.cos` 中使用，当我们在旋转我们的 boombox，需要计算出新的坐标时：

```js
// set up rotation constants
const rotationRate = 60; // bigger number equals slower sound rotation

const q = Math.PI / rotationRate; //rotation increment in radians
```

我们也可以使用它来计算旋转度，这将有助于我们即将必须创建的 CSS 变换（注意我们需要用于 CSS 变换的 x 和 y 轴）：

```js
// get degrees for css
const degreesX = (q \* 180) / Math.PI;
const degreesY = (q \* 180) / Math.PI;
```

我们以左旋转为例看一看。我们需要更改 panner x 方向和 z 方向的坐标，以围绕 y 轴移动进行左旋转：

```js
case 'rotate-left':
  transform.rotateY -= degreesY;

  // 'left' is rotation about y-axis with negative angle increment
  z = panner.orientationZ.value\*Math.cos(q) - panner.orientationX.value\*Math.sin(q);
  x = panner.orientationZ.value\*Math.sin(q) + panner.orientationX.value\*Math.cos(q);
  y = panner.orientationY.value;

  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
```

 这有点令人困惑，但我们正在做的是使用 sin 和 cos 来帮助我们计算出旋转 boombox 所需的圆周运动的坐标。
 我们可以为所有轴做到这一点。只需要选择正确的方向进行更新，以及我们是想要正增量还是负增量。

```js
case 'rotate-right':
  transform.rotateY += degreesY;
  // 'right' is rotation about y-axis with positive angle increment
  z = panner.orientationZ.value\*Math.cos(-q) - panner.orientationX.value\*Math.sin(-q);
  x = panner.orientationZ.value\*Math.sin(-q) + panner.orientationX.value\*Math.cos(-q);
  y = panner.orientationY.value;
  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
case 'rotate-up':
  transform.rotateX += degreesX;
  // 'up' is rotation about x-axis with negative angle increment
  z = panner.orientationZ.value\*Math.cos(-q) - panner.orientationY.value\*Math.sin(-q);
  y = panner.orientationZ.value\*Math.sin(-q) + panner.orientationY.value\*Math.cos(-q);
  x = panner.orientationX.value;
  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
case 'rotate-down':
  transform.rotateX -= degreesX;
  // 'down' is rotation about x-axis with positive angle increment
  z = panner.orientationZ.value\*Math.cos(q) - panner.orientationY.value\*Math.sin(q);
  y = panner.orientationZ.value\*Math.sin(q) + panner.orientationY.value\*Math.cos(q);
  x = panner.orientationX.value;
  panner.orientationX.value = x;
  panner.orientationY.value = y;
  panner.orientationZ.value = z;
break;
```

 最后一件事 - 我们需要更新 CSS 并保留鼠标事件最后一步的引用。
 这是最终的 `moveBoombox` 函数。

```js
function moveBoombox(direction, prevMove) {
  switch (direction) {
    case "left":
      if (transform.xAxis > leftBound) {
        transform.xAxis -= 5;
        panner.positionX.value -= 0.1;
      }
      break;
    case "up":
      if (transform.yAxis > topBound) {
        transform.yAxis -= 5;
        panner.positionY.value -= 0.3;
      }
      break;
    case "right":
      if (transform.xAxis < rightBound) {
        transform.xAxis += 5;
        panner.positionX.value += 0.1;
      }
      break;
    case "down":
      if (transform.yAxis < bottomBound) {
        transform.yAxis += 5;
        panner.positionY.value += 0.3;
      }
      break;
    case "back":
      if (transform.zAxis > innerBound) {
        transform.zAxis -= 0.01;
        panner.positionZ.value += 40;
      }
      break;
    case "forward":
      if (transform.zAxis < outerBound) {
        transform.zAxis += 0.01;
        panner.positionZ.value -= 40;
      }
      break;
    case "rotate-left":
      transform.rotateY -= degreesY;

      // 'left' is rotation about y-axis with negative angle increment
      z =
        panner.orientationZ.value \* Math.cos(q) -
        panner.orientationX.value \* Math.sin(q);
      x =
        panner.orientationZ.value \* Math.sin(q) +
        panner.orientationX.value \* Math.cos(q);
      y = panner.orientationY.value;

      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
    case "rotate-right":
      transform.rotateY += degreesY;
      // 'right' is rotation about y-axis with positive angle increment
      z =
        panner.orientationZ.value \* Math.cos(-q) -
        panner.orientationX.value \* Math.sin(-q);
      x =
        panner.orientationZ.value \* Math.sin(-q) +
        panner.orientationX.value \* Math.cos(-q);
      y = panner.orientationY.value;
      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
    case "rotate-up":
      transform.rotateX += degreesX;
      // 'up' is rotation about x-axis with negative angle increment
      z =
        panner.orientationZ.value \* Math.cos(-q) -
        panner.orientationY.value \* Math.sin(-q);
      y =
        panner.orientationZ.value \* Math.sin(-q) +
        panner.orientationY.value \* Math.cos(-q);
      x = panner.orientationX.value;
      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
    case "rotate-down":
      transform.rotateX -= degreesX;
      // 'down' is rotation about x-axis with positive angle increment
      z =
        panner.orientationZ.value \* Math.cos(q) -
        panner.orientationY.value \* Math.sin(q);
      y =
        panner.orientationZ.value \* Math.sin(q) +
        panner.orientationY.value \* Math.cos(q);
      x = panner.orientationX.value;
      panner.orientationX.value = x;
      panner.orientationY.value = y;
      panner.orientationZ.value = z;
      break;
  }

  boombox.style.transform =
    "translateX(" +
    transform.xAxis +
    "px) translateY(" +
    transform.yAxis +
    "px) scale(" +
    transform.zAxis +
    ") rotateY(" +
    transform.rotateY +
    "deg) rotateX(" +
    transform.rotateX +
    "deg)";

  const move = prevMove || {};
  move.frameId = requestAnimationFrame(() => moveBoombox(direction, move));
  return move;
}
```

连接我们的控件
-------

连接控制按钮相对简单 - 现在我们可以在控件上监听鼠标事件并运行此方法，并在释放鼠标时停止它：

```js
// for each of our controls, move the boombox and change the position values
moveControls.forEach(function (el) {
  let moving;
  el.addEventListener(
    "mousedown",
    function () {
      let direction = this.dataset.control;
      if (moving && moving.frameId) {
        window.cancelAnimationFrame(moving.frameId);
      }
      moving = moveBoombox(direction);
    },
    false,
  );

  window.addEventListener(
    "mouseup",
    function () {
      if (moving && moving.frameId) {
        window.cancelAnimationFrame(moving.frameId);
      }
    },
    false,
  );
});
```

概述
--

希望本文能让你深入了解 Web Audio 空间化的工作原理，以及每个`PannerNode` (en-US) 属性的作用（其中有很多属性）。这些值有时难以操作，根据你的使用情况，可能需要一些时间才能使它们正确。

**备注：** 音频空间化在不同浏览器中的听起来略有不同。panner 节点在底层做了一些非常复杂的数学运算；这里有 许多测试，因此你可以跟踪不同平台上此节点的内部工作状态。

再次，你可以在 这里查看最终的演示，同时最终的源代码在这里。还有一个 Codepen 的演示。

如果你正在使用 3D 游戏和/或 WebXR，最好利用 3D 库来创建此类功能，而不是尝试从最初的规则完成所有这些操作。我们在本文中提出了自己的想法，让你了解它是如何工作的，但是通过利用别人在你之前所做的工作，你将节省大量时间。


基于 Web Audio API 实现音频可视化效果 - Web API 接口参考

基于 Web Audio API 实现音频可视化效果
==========================

网页音频接口最有趣的特性之一它就是可以获取频率、波形和其他来自声源的数据，这些数据可以被用作音频可视化。这篇文章将解释如何做到可视化，并提供了一些基础使用案例。

**备注：** 你可以在Voice-change-O-matic演示里找到本文出现的所有代码片段。

基本概念
----

要从你的音频源获取数据，你需要一个 `AnalyserNode`节点，它可以用 `AudioContext.createAnalyser()` 方法创建，比如：

```js
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var analyser = audioCtx.createAnalyser();
```

然后把这个节点（node）连接到你的声源：

```js
source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
// etc.
```

**备注：** 分析器节点 (Analyser Node) 不一定输出到另一个节点，不输出时也可以正常使用。但前提是它必须与一个声源相连（直接或者通过其他节点间接相连都可以）。

分析器节点 (Analyser Node) 将在一个特定的频率域里使用快速傅立叶变换(Fast Fourier Transform (FFT) ) 来捕获音频数据，这取决于你给 `AnalyserNode.fftSize` 属性赋的值（如果没有赋值，默认值为 2048）。

**备注：** 你也可以为 FFT 数据缩放范围指定一个最小值和最大值，使用`AnalyserNode.minDecibels` (en-US) 和`AnalyserNode.maxDecibels` (en-US)进行设置，要获得不同数据的平均常量，使用 `AnalyserNode.smoothingTimeConstant`。阅读这些页面以获得更多如何使用它们的信息。

要捕获数据，你需要使用 `AnalyserNode.getFloatFrequencyData()` 或 `AnalyserNode.getByteFrequencyData()` 方法来获取频率数据，用 `AnalyserNode.getByteTimeDomainData()` 或 `AnalyserNode.getFloatTimeDomainData()` (en-US) 来获取波形数据。

这些方法把数据复制进了一个特定的数组当中，所以你在调用它们之前要先创建一个新数组。第一个方法会产生一个 32 位浮点数组，第二个和第三个方法会产生 8 位无符号整型数组，因此一个标准的 JavaScript 数组就不能使用——你需要用一个 `Float32Array` (en-US) 或者 `Uint8Array` (en-US) 数组，具体需要哪个视情况而定。

那么让我们来看看例子，比如我们正在处理一个 2048 尺寸的 FFT。我们返回 `AnalyserNode.frequencyBinCount` 值，它是 FFT 的一半，然后调用 Uint8Array()，把 frequencyBinCount 作为它的长度参数——这代表我们将对这个尺寸的 FFT 收集多少数据点。

```js
analyser.fftSize = 2048;
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
```

要正确检索数据并把它复制到我们的数组里，就要调用我们想要的数据收集方法，把数组作为参数传递给它，例如：

```js
analyser.getByteTimeDomainData(dataArray);
```

现在我们就获取了那时的音频数据，并存到了我们的数组里，而且可以把它做成我们喜欢的可视化效果了，比如把它画在一个 HTML5 `<canvas>` 画布上。

下面让我们来看一些具体的例子。

创建一个波形/示波器
----------

要创建一个示波器视觉效果（感谢 Soledad Penadés 在 Voice-change-O-matic 中提供的源码），我们首先用下面代码框中的代码为标准设置一个 buffer：

```js
analyser.fftSize = 2048;
var bufferLength = analyser.fftSize;
var dataArray = new Uint8Array(bufferLength);
```

接下来，我们清空画布为绘制新的可视化效果做准备：

```js
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
```

现在我们来定义 `draw()` 函数：

```js
function draw() {
```

这里我们用 `requestAnimationFrame()` 来保持绘图持续更新：

```js
drawVisual = requestAnimationFrame(draw);
```

接下来我们获取时间域上的数据并将它复制到数组当中：

```js
analyser.getByteTimeDomainData(dataArray);
```

接下来把 canvas 用纯色填满作为背景：

```js
canvasCtx.fillStyle = "rgb(200, 200, 200)";
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
```

为我们要画的波形设置好线宽和线的颜色，然后开始绘制路径：

```js
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = "rgb(0, 0, 0)";

canvasCtx.beginPath();
```

用 canvas 画布的总宽度除以数组的长度（与之前定义的 FrequencyBinCount 相等）来决定要花上的每段线条的宽度，之后设置横坐标 (x) 为 0，将画笔移动到起始位置：

```js
var sliceWidth = (WIDTH \* 1.0) / bufferLength;
var x = 0;
```

接下来我们进入循环，遍历数组，通过其中的数据来确定每段线条的高度，之后改变横坐标将画笔移动到下一段线条开始的地方：

```js
for (var i = 0; i < bufferLength; i++) {
  var v = dataArray[i] / 128.0;
  var y = (v \* HEIGHT) / 2;

  if (i === 0) {
    canvasCtx.moveTo(x, y);
  } else {
    canvasCtx.lineTo(x, y);
  }

  x += sliceWidth;
}
```

最后，我们把线连到右边的中央，然后画出来：

```js
      canvasCtx.lineTo(canvas.width, canvas.height/2);
      canvasCtx.stroke();
    };
```

在这块代码最后，我们调用 `draw()` 函数来开始整个过程：

```js
draw();
```

这个演示画出了一个每秒会刷新几次并且看起来还不错的波形图：

![a black oscilloscope line, showing the waveform of an audio signal](/zh-CN/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API/wave.png)

创建一个频率条形图
---------

另一种小巧的可视化方法是创建频率条形图，在 Voice-change-O-matic 中已经有一个做好的，现在让我们来看看它是如何实现的。

首先，我们设置好解析器和空数组，之后用 clearRect() 清空画布。与之前的唯一区别是我们这次大大减小了 FFT 的大小，这样做的原因是为了使得每个频率条足够宽，让它们看着像“条”而不是“细杆”。

```js
analyser.fftSize = 256;
var bufferLength = analyser.frequencyBinCount;
console.log(bufferLength);
var dataArray = new Uint8Array(bufferLength);

canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
```

接下来我们写好 `draw()` 函数，再一次用 `requestAnimationFrame()` 设置一个循环，这样显示的数据就可以保持刷新，并且每一帧都清空一次画布。

```js
    function draw() {
      drawVisual = requestAnimationFrame(draw);

      analyser.getByteFrequencyData(dataArray);

      canvasCtx.fillStyle = 'rgb(0, 0, 0)';
      canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
```

现在我们来设置一个 `barWidth` 变量，它等于每一个条形的宽度。理论上用花布宽度除以条的个数就可以得到它，但是在这里我们还要乘以 2.5。这是因为有很多返回的频率区域中是没有声音的，我们每天听到的大多数声音也只是在一个很小的频率区域当中。在条形图中我们肯定不想看到大片的空白条，所以我们就把一些能正常显示的条形拉宽来填充这些空白区域。

我们还要设置一个条高度变量 `barHeight`，还有一个 `x` 变量来记录当前条形的位置。

```js
var barWidth = (WIDTH / bufferLength) \* 2.5;
var barHeight;
var x = 0;
```

像之前一样，我们进入循环来遍历 `dataArray` 数组中的数据。在每一次循环过程中，我们让条形的高度 `barHeight` 等于数组的数值，之后根据高度设置条形的填充色（条形越高，填充色越亮），然后在横坐标 `x` 处按照设置的宽度和高度的一半把条形画出来（我们最后决定只画高度的一半因为这样条形看起来更美观）。

需要多加解释的一点是每个条形竖直方向的位置，我们在 `HEIGHT-barHeight/2` 的位置画每一条，这是因为我想让每个条形从底部向上伸出，而不是从顶部向下（如果我们把竖直位置设置为 0 它就会这样画）。所以，我们把竖直位置设置为画布高度减去条形高度的一半，这样每个条形就会从中间向下画，直到画布最底部。

```js
      for(var i = 0; i < bufferLength; i++) {
        barHeight = dataArray[i]/2;

        canvasCtx.fillStyle = 'rgb(' + (barHeight+100) + ',50,50)';
        canvasCtx.fillRect(x,HEIGHT-barHeight/2,barWidth,barHeight);

        x += barWidth + 1;
      }
    };
```

和刚才一样，我们在最后调用 draw() 函数来开启整个可视化过程。

```js
draw();
```

这些代码会带来下面的效果：

![a series of red bars in a bar graph, showing intensity of different frequencies in an audio signal](/zh-CN/docs/Web/API/Web_Audio_API/Visualizations_with_Web_Audio_API/bar-graph.png)

**备注：** 本文中的案例展现了 `AnalyserNode.getByteFrequencyData()` 和 `AnalyserNode.getByteTimeDomainData()` 的用法。如果想要查看 `AnalyserNode.getFloatFrequencyData()` 和 `AnalyserNode.getFloatTimeDomainData()` (en-US) 的用法，请参考我们的 Voice-change-O-matic-float-data 演示（也能看到 源代码 ）——它和本文中出现的 Voice-change-O-matic 功能完全相同，唯一区别就是它使用的是浮点数作数据，而不是本文中的无符号整型数。


Web Audio API 的运用 - Web API 接口参考

Web Audio API 的运用
=================

让我们来看看 Web Audio API 入门。我们将简要介绍一些概念，然后学习一个简单的允许我们加载音轨，播放暂停，改变音量和立体声声像的音箱例子。

Web Audio API 并不会取代<audio>音频元素，倒不如说它是<audio>的补充更好，就好比如<canvas>与<img>共存的关系。你使用来实现音频的方式取决于你的使用情况。如果你只是想控制一个简单的音轨的播放，<audio>或许是一个更好更快的选择。如果你想实现更多复杂的音频处理，以及播放，Web Audio API 提供了更多的优势以及控制。

Web Audio API 的一个强大之处在于，它没有任何严格的声音呼叫控制。比如说，在同一时间它没有呼叫 32 或 64 的声音的限制。如果你的处理器性能好的话，同一时间播放 1000 多的声音不卡顿也是有可能的。这充分显示真正的进步，要知道几年前中高频的声卡仅能处理小部分的负载。

例子
--

我们的音箱看起来像这样：

![A boombox with play, pan, and volume controls](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/boombox.png)

注意带有播放按钮的复古磁带卡座，及用于改变音量和立体声声像的平移滑块。我们可以使其更复杂，但这是该阶段进行简单学习的理想选择。

查看最终 demo 代码 here on Codepen，或者在 GitHub 查看源代码 on GitHub。

浏览器支持
-----

现代浏览器的 Web Audio API 对的大多数功能都有很好的支持。API 有很多的功能，因此要获得更准确的信息，你必须检查每个参考页面底部的浏览器兼容表。

音频图
---

Web Audio API 中的所有内容都是基于音频图的概念，音频图由节点组成。

Web Audio API 在 **audio context（音频上下文）** 内处理音频，而且被设计为允许模块化路由。基本的音频操作是基于 **audio nodes** 进行的，音频节点连接起来形成一个音频路由图。你拥有输入节点，你要操作的声音源，根据设计需要被修改的节点，和输出节点（目的地），它们允许你保存或者听取这些声音。

支持拥有不同通道布局的多个的音频源，即使是在单个上下文。因为模块化设计，你可以创建具有动态效果的复杂的音频功能。

音频上下文
-----

为了能通过 Web Audio API 执行任何操作，我们需要创建音频上下文实例。这能让我们访问 API 所有的特性和功能。

```js
// for legacy browsers
const AudioContext = window.AudioContext || window.webkitAudioContext;

const audioContext = new AudioContext();
```

所以当我们这样做时会发生什么？为我们自动创建一个 `BaseAudioContext` (en-US) 并自动扩展到在线音频上下文。我们希望如此，因为我们想要播放在线声音。

**备注：** 如果你只是想处理音频数据，举个例子，缓存和流式传输而不播放它，你可能想要考虑创建一个 `OfflineAudioContext`。

加载声音
----

现在，需要通过我们创建的音频上下文播放一些声音。Web Audio API 中有几种方法可以实现这一点。让我们通过一个简单的方法开始 — 因为我们有一个音箱，我们可能想播放一首完整的歌曲。此外，为了便于访问，我们可以在在 DOM 中暴露该音轨。我们将使用 `<audio>` 元素在页面上暴露这首歌曲。

```js
<audio src="myCoolTrack.mp3" type="audio/mpeg"></audio>
```

**备注：** 如果你要加载的声音文件保留在其他域中，则需要使用 `crossorigin` 属性；查看 Cross Origin Resource Sharing (CORS) 取得更多信息。

为了使用 Web Audio API 的优秀特性，我们需要从该元素中获取源并将其传入我们创建的上下文中。幸运的是，有一个方法可以让我们做到这一点 — `AudioContext.createMediaElementSource`:

```js
// get the audio element
const audioElement = document.querySelector("audio");

// pass it into the audio context
const track = audioContext.createMediaElementSource(audioElement);
```

**备注：** 上面的 `<audio>` 元素在 DOM 中代表了一个`HTMLMediaElement` 类型的对象，拥有其自身的一组功能。这一切都将保持不变。我们只是让 Web Audio API 能够访问到声音。

控制声音
----

当在网页上播放声音时，让用户能控制它是很重要的。根据使用场景，有无数的选项可用，但这我们将提供播放/暂停声音，改变音轨音量及从左到右平移声音的功能。

通过 JavaScript 代码控制声音会受到浏览器的自动播放策略的影响 (autoplay support policies)，因此在未经用户（或白名单）许可的情况下脚本对声音的控制会被阻止。浏览器的自动播放策略通常要求显式权限或者用户与页面产生互动后，才允许脚本触发音频播放。

这些特殊的要求基本上是因为意外的声音可能会打扰到用户，令人厌烦，并且可能导致无障碍问题。你可以在文章 媒体与 Web 音频 API 自动播放指南 了解更多相关信息。

因为我们的脚本正响应用户输入（例如，点击播放按钮）进行播放音频，我们状态良好且应该没有自动播放阻止的问题。所以，让我们看看我们的播放和暂停功能。我们有一个当音频播放时变为暂停按钮的播放按钮：

```html
<button data-playing="false" role="switch" aria-checked="false">
  <span>Play/Pause</span>
</button>
```

在我们可以播放音频前我们需要将我们的音频图从音频源/输入节点连接到目的地。

我们已经通过把音频元素传入 API 生成一个输入节点。在大多数情况下，你不需要生成一个输出节点，你只需要将其他节点连接到可以为你处理这种情况的 `BaseAudioContext.destination`：

```js
track.connect(audioContext.destination);
```

可视化这些节点的一个好方法是绘制音频图形以便可视化它。这是我们当前的音频图：

![an audio graph with an audio element source connected to the default destination](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/graph1.jpg)

现在我们可以添加播放和暂停功能。

```js
// select our play button
const playButton = document.querySelector("button");

playButton.addEventListener(
  "click",
  function () {
    // check if context is in suspended state (autoplay policy)
    if (audioContext.state === "suspended") {
      audioContext.resume();
    }

    // play or pause track depending on state
    if (this.dataset.playing === "false") {
      audioElement.play();
      this.dataset.playing = "true";
    } else if (this.dataset.playing === "true") {
      audioElement.pause();
      this.dataset.playing = "false";
    }
  },
  false,
);
```

我们也需要考虑到当音频播放完毕后做什么。我们的 `HTMLMediaElement` 一旦播放完毕会触发一个 `ended` 事件，所以我们可以监听它并运行相应代码：

```js
audioElement.addEventListener(
  "ended",
  () => {
    playButton.dataset.playing = "false";
  },
  false,
);
```

关于 Web Audio 编辑器
----------------

Firefox 有一个名为 Web Audio editor 的工具。在其上运行音频图的任何页面上，你可以打开开发者工具，使用 Web Audio 选项卡查看音频图，可查看每个节点的可用属性，并可以修改这些属性来查看会有什么效果。

![The Firefox web audio editor showing an audio graph with AudioBufferSource, IIRFilter, and AudioDestination](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/web-audio-editor.png)

**备注：** Web Audio 编辑器默认不是开启的，你需要打开 Firefox developer tools 设置，选中 Default Developer Tools 部分中的 Web Audio 复选框来显示它。

修改声音
----

让我们深入研究一些基本的修改节点以改变我们的声音。这就是 Web Audio API 真正开始派上用场的地方。首先，让我们改变音量。这可以通过 `GainNode` 实现，它表示我们的声波有多大。

使用 Web Audio API 可以通过 2 个方法创建节点。你可以使用上下文本身的工厂方法（例如， `audioContext.createGain()` ）或者通过节点的构造函数（例如， `new GainNode()` ），我们将使用工厂方法：

```js
const gainNode = audioContext.createGain();
```

现在我们需要在原先音频图基础上更新音频图，所以输入连接到增益，然后增益节点连接到目标：

```js
track.connect(gainNode).connect(audioContext.destination);
```

这会让我们的音频图看起来如下：

![an audio graph with an audio element source, connected to a gain node that modifies the audio source, and then going to the default destination](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/graph2.jpg)

默认增益为 1；这使当前音量保持不变。增益可以设置的最小值约`-3.4028235E38`，最大约`3.4028235E38`。这里我们将允许音箱增益可以设置到 2（2 倍的原音量）和降低到 0（这可以有效的静音）。

让我们给用户这样的控制 — 我们将会使用 range input ：

```js
<input type="range" id="volume" min="0" max="2" value="1" step="0.01">
```

**备注：** 范围输入 (Range Input) 是更新音频节点值非常方便的输入类型。你可以指定特定的范围值同时直接将它们作为音频参数一起使用。

所以当用户更改输入节点值时，获取此输入值并更新增益值：

```js
const volumeControl = document.querySelector("#volume");

volumeControl.addEventListener(
  "input",
  function () {
    gainNode.gain.value = this.value;
  },
  false,
);
```

**备注：** 节点对象的值（例如， `GainNode.gain` ）不是简单值；它们实际上是 `AudioParam` 类型对象 — 这些被称为参数。这也是为什么我们需要设置 `GainNode.gain` 的 `value` 属性，而不是直接设置 `gain` 的值。这使得它们更加的灵活，允许传入一系列特定的值以在例如一段时间内改变。

好的，现在用户可以更新音频的音量！如果你要增加静音功能，增益节点是可使用的完美节点。

为应用程序增加立体声平移
------------

让我们添加另一个修改阶段来练习我们刚刚学过的。

如果用户拥有立体声功能，可用 `StereoPannerNode` (en-US) 节点改变左右扬声器的平衡。

**备注：** `StereoPannerNode` 用于你只想从左到右进行立体声平移的简单情况。还有一个 `PannerNode` (en-US)，它允许对 3D 空间或声音空间化进行大量控制以创建更复杂的效果。这在游戏和 3D 应用程序中生成小鸟飞过头顶或者来自用户身后的声音。

为了使其可视化，我们将使我们的音频图如下：

![An image showing the audio graph showing an input node, two modification nodes (a gain node and a stereo panner node) and a destination node.](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/graphpan.jpg)

这次让我们使用构造函数来生成节点。当我们这样做，我们需要传入上下文及该特定节点可能采用的任何选项：

```js
const pannerOptions = { pan: 0 };
const panner = new StereoPannerNode(audioContext, pannerOptions);
```

**备注：** 目前生成节点的构造函数不是每个浏览器都支持的。旧工厂函数支持更为广泛。

这里我们的范围从 -1（最左边）和 1（最右边）。再次让我们使用范围类型的 input 来改变这个参数：

```js
<input type="range" id="panner" min="-1" max="1" value="0" step="0.01">
```

与我们之前一样，我们使用来自这个 input 的值来调整我们的 panner 的值：

```js
const pannerControl = document.querySelector("#panner");

pannerControl.addEventListener(
  "input",
  function () {
    panner.pan.value = this.value;
  },
  false,
);
```

让我们再次调整我们的音频图，将所有节点连接在一起：

```js
track.connect(gainNode).connect(panner).connect(audioContext.destination);
```

剩下要做的就是试试这个应用程序：查看 Codepen 上的最终演示。

摘要
--

好的，我们拥有一个音箱播放我们的“磁带”，我们可以调整音量和立体声声像，给我们提供了一个相当基本的工作音频图表。

这构成了开始向你的网站或 Web 应用添加音频所需的很少的几个基础知识。Web Audio API 还有很多功能，但一旦你掌握了节点的概念及将音频节点图联系在一起，我们可以继续研究更加复杂的功能。

更多例子
----

还有其他示例可以了解有关 Web Audio API 的更多信息。

Voice-change-O-matic 是一个有趣的语音操纵器和音频可视化 web 应用程序，允许你选择不同的效果和可视化。该应用程序相当初级，但它演示了同时使用多个 Web Audio API 特性（运行 Voice-change-O-matic live）。

![A UI with a sound wave being shown, and options for choosing voice effects and visualizations.](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/voice-change-o-matic.png)

另一个专门用于演示 Web Audio API 的例子是 Violent Theremin，一个允许你通过移动鼠标来改变它的音调音量的简单的应用程序。它还提供了一个迷幻的灯光秀（查看 Violent Theremin 源代码）

![A page full of rainbow colours, with two buttons labeled Clear screen and mute. ](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/violent-theremin.png)

另参阅我们的 webaudio-examples repo 以获取更多示例。

#### *注：以下为旧文档，因较完整，此处暂不删除，方便开发者查看。*

基础概念
----

**备注：** 很多的代码碎片来自于这个例子 Violent Theremin example.

Web Audio API 包含在音频上下文的处理音频操作，以及已被设计允许模块化路由。基本音频操作可通过音频节点进行，这些节点连接在一起，组成一个音频的路由表。多个音源——带有不同类型的频道配置——甚至可以被一个上下文支持。这个模块设计提供了创造带有动态效果的复杂音频功能的灵活性。

音频节点通过输入与输出进行连接，形成一个链，从一个或多个源出发，通过一个或更多的节点，最终到输出终端（你也可以不提供输出终端，换句话说，如果只是想使一些音频数据可视化）。一个简单经典的 web Audio 的工作流程如下：

1. 构建音频上下文 AudioContext 对象；
2. 在 AudioContext 对象内，构建音源，比如<audio>，oscillator，stream
3. 构建效果节点 effectNode，比如混响，双二阶滤波器，声相，压限器
4. 选择最终的音频目的地，比如说你的系统扬声器
5. 连接源到效果，效果到输出终端
### 构建 AudioContext 对象

首先，你需要构建一个 AudioContext 实例，来创建一个音频图。最简单的方法就像这样：

```js
var audioCtx = new AudioContext();
```

**备注：** 同样一个文档是可以存在多个 audioContext 对象的，但是比较浪费。

然而，提供一个版本前缀对于 webkit/Blink 浏览器是很重要的，对于 Firefox(桌面版/手机版/OS 版) 是不需要的。如下：

```js
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
```

**备注：** 当创建一个新的 conText 对象时，如果你不提示 window 对象，Safari 会无效。

### 创建 AudioSource

现在我们有了 AudioContext，可以用这个来做很多事。第一件我们需要做的事是玩音乐。音频可以来自于多样的地方：

* 通过 JavaScript 直接生成一个音频节点比如 oscillator. 一个 `OscillatorNode`是利用`AudioContext.createOscillator` (en-US) 方法来构建。
* 从原 PCM 数据构建：AudioContext 有解密被支持的音频格式的多种方法。看 `AudioContext.createBuffer()`, `AudioContext.createBufferSource()`, 以及 `AudioContext.decodeAudioData()`.
* 来自于 HTML 音频元素例如 `<video>` 或者`<audio>`: 可以看 `AudioContext.createMediaElementSource()`.
* 直接来自于 WebRTC (en-US)，`MediaStream` 例如来自于摄像头或麦克风。可以看`AudioContext.createMediaStreamSource()`.

对于这些特殊的例子，我们将会为我们的源构建一个 oscillator 来提供简单的音调，以及 gain node 来控制音频音量：

```js
var oscillator = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
```

**备注：** 为了直接播放一个音乐文件，你通常通过 XHR 来加载文件，通过 Buffer 来解码，创建 BufferSource. 看这个 例子来自于 Voice-change-O-matic.

**备注：** Scott Michaud 已经写了一个有用的库来加载和解码一个或多个音频实例，被称为 AudioSampleLoader. 这个可以帮助简化 XHR/buffering 的处理操作。

### 连接输入输出

为了通过你的扬声器来实际输出音质，你需要将它们连接起来。这个被称为节点连接方法，节点来自于很多可获得的不同节点类型。你想要连接的节点都提供了这个方法。

你的设备的默认输出结构（通常是你的设备扬声器），通过`AudioContext.destination`来允许进入。为了连接 oscillator，gain node 以及输出端，如以下运用：

```js
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
```

一个更复杂的例子，（比如 Voice-change-O-matic), 你可以链接很多你想要的节点在一起，例如：

```js
source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(biquadFilter);
biquadFilter.connect(convolver);
convolver.connect(gainNode);
gainNode.connect(audioCtx.destination);
```

这个将会创造一个如下音频节点图：

![](/zh-CN/docs/Web/API/Web_Audio_API/Using_Web_Audio_API/voice-change-o-matic-graph.png)你也可以链接多个节点到一个节点，比如说你想要混合多个音频源在一起，就让它们都通过一个效果节点，比如 gain node。

**备注：** Firefox32 以上版本已有完整的 firefox 开发者工具包括 Web Audio Editor, 一个对测试 web audio 表的 bug 非常有用的东西。

### 播放音乐及设置音调

现在 audio 节点图已经建立，我们可以设置属性值及调用音频节点的方法来调节想要的音效。在这个简单的例子，我们可以设置特殊的音调，以赫兹为单位，设置为特殊类型，以及指示音乐播放：

```js
oscillator.type = "sine"; // sine wave — other values are 'square', 'sawtooth', 'triangle' and 'custom'
oscillator.frequency.value = 2500; // value in hertz
oscillator.start();
```

在我们的 Violent Theremin 例子，设定了一个最大 gain 以及 frequency（频率）值：

```js
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;

var maxFreq = 6000;
var maxVol = 1;

var initialFreq = 3000;
var initialVol = 0.5;

// set options for the oscillator

oscillator.type = "sine"; // sine wave — other values are 'square', 'sawtooth', 'triangle' and 'custom'
oscillator.frequency.value = initialFreq; // value in hertz
oscillator.start();

gainNode.gain.value = initialVol;
```

然后我们设置了一个 frequency 的新的值，以及设置每个时间鼠标的移动，基于目前的鼠标坐标值作为 frequency 和 gain 的最大值百分比。

```js
// Mouse pointer coordinates

var CurX;
var CurY;

// Get new mouse pointer coordinates when mouse is moved
// then set new gain and pitch values

document.onmousemove = updatePage;

function updatePage(e) {
  CurX = window.Event
    ? e.pageX
    : event.clientX +
      (document.documentElement.scrollLeft
        ? document.documentElement.scrollLeft
        : document.body.scrollLeft);
  CurY = window.Event
    ? e.pageY
    : event.clientY +
      (document.documentElement.scrollTop
        ? document.documentElement.scrollTop
        : document.body.scrollTop);

  oscillator.frequency.value = (CurX / WIDTH) \* maxFreq;
  gainNode.gain.value = (CurY / HEIGHT) \* maxVol;

  canvasDraw();
}
```

### 简单的 canvas 可视化

每次鼠标的移动，canvasDraw() 方法会被调用，鼠标停留的位置会画出一个多圆圈组成的小簇，它的大小以及颜色会基于 frequency/gain 的值。

```js
function random(number1, number2) {
  var randomNo =
    number1 + (Math.floor(Math.random() \* (number2 - number1)) + 1);
  return randomNo;
}

var canvas = document.querySelector(".canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;

var canvasCtx = canvas.getContext("2d");

function canvasDraw() {
  rX = CurX;
  rY = CurY;
  rC = Math.floor((gainNode.gain.value / maxVol) \* 30);

  canvasCtx.globalAlpha = 0.2;

  for (i = 1; i <= 15; i = i + 2) {
    canvasCtx.beginPath();
    canvasCtx.fillStyle =
      "rgb(" +
      100 +
      i \* 10 +
      "," +
      Math.floor((gainNode.gain.value / maxVol) \* 255) +
      "," +
      Math.floor((oscillator.frequency.value / maxFreq) \* 255) +
      ")";
    canvasCtx.arc(
      rX + random(0, 50),
      rY + random(0, 50),
      rC / 2 + i,
      (Math.PI / 180) \* 0,
      (Math.PI / 180) \* 360,
      false,
    );
    canvasCtx.fill();
    canvasCtx.closePath();
  }
}
```

### theremin 的静音

当静音按钮点击，以下方法会被调用，disconnect 方法，将切断 gain node 与 destination 节点的链接，有效阻止了节点图的链接，所以没有声音会被产生。再次点击效果相反。

```js
var mute = document.querySelector(".mute");

mute.onclick = function () {
  if (mute.id == "") {
    gainNode.disconnect(audioCtx.destination);
    mute.id = "activated";
    mute.innerHTML = "Unmute";
  } else {
    gainNode.connect(audioCtx.destination);
    mute.id = "";
    mute.innerHTML = "Mute";
  }
};
```

其他节点选择
------

这里有许多通过 Web Audio API 来构建的节点，一个好消息就是，总体来说，正如我们所见，他们用同一种方法工作：构建节点，连接到图表的另一个节点，然后处理节点属性以及方法来作用于你想要的音源。

我们并不希望通过所有可获得的效果等；你可以在`Web_Audio_API`不同的参考接口找到如何使用每一个的详述。我们现在来浏览下不同的设置。

### Wave shaper 节点

利用 `AudioContext.createWaveShaper` 方法，你可以构建一个 wave shaper node:

```js
var distortion = audioCtx.createWaveShaper();
```

这个对象一定会数学化的定义 wave shape，一个被应用于基础声音波来创造扭曲的效果。这些波并不好被计算，最好的开始方法是搜索 web 算法。比如，我们可以从 Stack Overflow 找到：

```js
function makeDistortionCurve(amount) {
  var k = typeof amount === "number" ? amount : 50,
    n_samples = 44100,
    curve = new Float32Array(n_samples),
    deg = Math.PI / 180,
    i = 0,
    x;
  for (; i < n_samples; ++i) {
    x = (i \* 2) / n_samples - 1;
    curve[i] = ((3 + k) \* x \* 20 \* deg) / (Math.PI + k \* Math.abs(x));
  }
  return curve;
}
```

在 Voice-change-O-matic 的演示中，我们连接到 audio 图表上的 ditortion 节点，当需要的时候可以运用：

```js
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(biquadFilter);

...

distortion.curve = makeDistortionCurve(400);
```

### Biquad filter

biquad filter 拥有很多可选择的方法，通过 `AudioContext.createBiquadFilter` 方法来构建：

```js
var biquadFilter = audioCtx.createBiquadFilter();
```

在 Voice-change-o-matic 的演示中，运用的制定选项是“lowshelf”过滤器，它提供了低音的基本增幅方法：

```js
biquadFilter.type = "lowshelf";
biquadFilter.frequency.value = 1000;
biquadFilter.gain.value = 25;
```

我们在这里详述了过滤器的类型，频率值，增幅值。在 lowshelf 过滤器情况，所有的指定频率拥有 25 分贝的增幅值。

Web Audio API 的其他
-----------------

Web Audio API 可以做不仅仅音频可视化及专业化（如 panning）的事情。我们将会在之后的文章涉及其他的更多内容。


AudioBuffer.getChannelData() - Web API 接口参考

AudioBuffer.getChannelData()
============================

`AudioBuffer` 接口的 getChannelData() 方法返回一`Float32Array` (en-US) ，其中包含与通道关联的 PCM 数据，通道参数定义 (0 表示第一个通道)。

语法
--

```js
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);
var nowBuffering = myArrayBuffer.getChannelData(channel);
```

### 参数

channel

channel 属性是要获取特定通道数据的索引。0 代表第一个通道。如果索引值大于或等于`AudioBuffer.numberOfChannels`, 会抛出一个索引大小异常（`INDEX_SIZE_ERR` ）的错误。

### 返回值

`Float32Array` (en-US).

例子
--

在下例中，我们创建一个 2 秒钟的缓冲区，用白噪声填充它，然后通过`AudioBufferSourceNode`来播放它。评论应该会清楚的解释发生的事情。你也可以实时运行代码，或者查看源代码。

```js
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var button = document.querySelector("button");
var pre = document.querySelector("pre");
var myScript = document.querySelector("script");

pre.innerHTML = myScript.innerHTML;

// Stereo
var channels = 2;
// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
var frameCount = audioCtx.sampleRate \* 2.0;

var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);

button.onclick = function () {
  // Fill the buffer with white noise;
  //just random values between -1.0 and 1.0
  for (var channel = 0; channel < channels; channel++) {
    // This gives us the actual ArrayBuffer that contains the data
    var nowBuffering = myArrayBuffer.getChannelData(channel);
    for (var i = 0; i < frameCount; i++) {
      // Math.random() is in [0; 1.0]
      // audio needs to be in [-1.0; 1.0]
      nowBuffering[i] = Math.random() \* 2 - 1;
    }
  }

  // Get an AudioBufferSourceNode.
  // This is the AudioNode to use when we want to play an AudioBuffer
  var source = audioCtx.createBufferSource();
  // set the buffer in the AudioBufferSourceNode
  source.buffer = myArrayBuffer;
  // connect the AudioBufferSourceNode to the
  // destination so we can hear the sound
  source.connect(audioCtx.destination);
  // start the source playing
  source.start();
};
```

Specification
-------------

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-getchanneldata |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Using the Web Audio API


AudioBuffer() - Web API 接口参考

AudioBuffer()
=============

Web Audio API 的 **`AudioBuffer`** 构造函数将创建一个新的 `AudioBuffer` 对象。

语法
--
```

var audioBuffer = new AudioBuffer([options]);
var audioBuffer = new AudioBuffer(context[, options]);
```

### 参数

*继承参数自 `AudioNodeOptions` 字典*.

*context*
已弃用

一个 `AudioContext` 对象。(这个参数已经被标准移除，详细信息请参阅浏览器兼容性部分.)

*options* 可选

Options are as follows:

* `length`: buffer 中采样帧的长度。
* `numberOfChannels`: buffer 的通道数。默认值为 1.
* `sampleRate`: buffer 的采样率 (Hz). 默认值为构造此对象时使用的 `context` 的采样率。

### 返回值

一个新的 `AudioBuffer` 对象实例。

规范
--

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-audiobuffer |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


AudioBuffer.numberOfChannels - Web API 接口参考

AudioBuffer.numberOfChannels
============================

`AudioBuffer`接口的`numberOfChannels`属性返回一个整数，该整数表示由缓冲区中存储的 PCM 数据描述的离散音频通道的数量

语法
--

```js
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);
myArrayBuffer.numberOfChannels;
```

### 返回值

一个整数。

示例
--

```js
// Stereo
var channels = 2;

// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
var frameCount = audioCtx.sampleRate \* 2.0;
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);

button.onclick = function () {
  // Fill the buffer with white noise;
  // just random values between -1.0 and 1.0
  for (var channel = 0; channel < channels; channel++) {
    // This gives us the actual ArrayBuffer that contains the data
    var nowBuffering = myArrayBuffer.getChannelData(channel);
    for (var i = 0; i < frameCount; i++) {
      // Math.random() is in [0; 1.0]
      // audio needs to be in [-1.0; 1.0]
      nowBuffering[i] = Math.random() \* 2 - 1;
    }
  }

  console.log(myArrayBuffer.numberOfChannels);
};
```

规范
--

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-numberofchannels |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* Using the Web Audio API


AudioBuffer.length - Web API 接口参考

AudioBuffer.length
==================

The `length` property of the `AudioBuffer` interface returns an integer representing the length, in sample-frames, of the PCM data stored in the buffer.

`AudioBuffer` 的 length 属性接口返回整数，该整数代表采样帧中，存贮在缓冲区中的 PCM 数据的长度

语法
--

```js
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);
myArrayBuffer.length;
```

### 值

浮点数

例子
--

```js
// Stereo
var channels = 2;

// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
var frameCount = audioCtx.sampleRate \* 2.0;
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);

button.onclick = function () {
  // Fill the buffer with white noise;
  // just random values between -1.0 and 1.0
  for (var channel = 0; channel < channels; channel++) {
    // This gives us the actual ArrayBuffer that contains the data
    var nowBuffering = myArrayBuffer.getChannelData(channel);
    for (var i = 0; i < frameCount; i++) {
      // Math.random() is in [0; 1.0]
      // audio needs to be in [-1.0; 1.0]
      nowBuffering[i] = Math.random() \* 2 - 1;
    }
  }

  console.log(myArrayBuffer.length);
};
```

规范
--

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-length |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Using the Web Audio API


AudioBuffer.sampleRate - Web API 接口参考

AudioBuffer.sampleRate
======================

`AudioBuffer` 接口的 `sampleRate` 属性返回一个以浮点数表示的采样率。该采样率是存储在缓冲区的 PCM 数据每秒钟的采样。

语法
--

```js
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);
myArrayBuffer.sampleRate;
```

### 返回值

一个浮点数，表示缓冲区数据的当前采样率。

示例
--

```js
// Stereo
var channels = 2;

// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
var frameCount = audioCtx.sampleRate \* 2.0;
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);

button.onclick = function () {
  // Fill the buffer with white noise;
  // just random values between -1.0 and 1.0
  for (var channel = 0; channel < channels; channel++) {
    // This gives us the actual ArrayBuffer that contains the data
    var nowBuffering = myArrayBuffer.getChannelData(channel);
    for (var i = 0; i < frameCount; i++) {
      // Math.random() is in [0; 1.0]
      // audio needs to be in [-1.0; 1.0]
      nowBuffering[i] = Math.random() \* 2 - 1;
    }
  }

  console.log(myArrayBuffer.sampleRate);
};
```

规范
--

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-samplerate |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* Using the Web Audio API


AudioBuffer.duration - Web API 接口参考

AudioBuffer.duration
====================

`AudioBuffer`接口的 duration 属性返回一个双精度数，表示缓冲区中存储的 PCM 数据的持续时间（以秒为单位）

语法
--

```js
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);
myArrayBuffer.duration;
```

### 值

双精度值

例子
--

```js
// Stereo
var channels = 2;

// Create an empty two second stereo buffer at the
// sample rate of the AudioContext
var frameCount = audioCtx.sampleRate \* 2.0;
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);

button.onclick = function () {
  // Fill the buffer with white noise;
  // just random values between -1.0 and 1.0
  for (var channel = 0; channel < channels; channel++) {
    // This gives us the actual ArrayBuffer that contains the data
    var nowBuffering = myArrayBuffer.getChannelData(channel);
    for (var i = 0; i < frameCount; i++) {
      // Math.random() is in [0; 1.0]
      // audio needs to be in [-1.0; 1.0]
      nowBuffering[i] = Math.random() \* 2 - 1;
    }
  }

  console.log(myArrayBuffer.duration);
};
```

规范
--

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-duration |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Using the Web Audio API


AudioBuffer.copyFromChannel() - Web API 接口参考

AudioBuffer.copyFromChannel()
=============================

`AudioBuffer`接口的 copyFromChannel 方法将样本从 AudioBuffer 的指定通道复制到目标数组中

语法
--
```

myArrayBuffer.copyFromChannel(destination,channelNumber,startInChannel);
```

### 参数

*destination*

将通道数据复制到的`Float32Array` (en-US)

*channelNumber*

当前 AudioBuffer 的通道号，用于复制通道数据。键入 channelNumber 大于或等于`AudioBuffer.numberOfChannels`，将会抛出`INDEX_SIZE_ERR` 的错误

*startInChannel* 可选

用于复制数据的可选偏移量。假如*startInChannel 比*`AudioBuffer.length`大，将会抛出`INDEX_SIZE_ERR` 的错误

例子
--

```js
var myArrayBuffer = audioCtx.createBuffer(2, frameCount, audioCtx.sampleRate);
var anotherArray = new Float32Array();
myArrayBuffer.copyFromChannel(anotherArray, 1, 0);
```

规范
--

| Specification |
| --- |
| Web Audio API # dom-audiobuffer-copyfromchannel |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Using the Web Audio API


ProgressEvent.lengthComputable - Web API 接口参考

ProgressEvent.lengthComputable
==============================

**`ProgressEvent.lengthComputable`** 只读属性是一个布尔`Boolean` (en-US) 标志，表示`ProgressEvent` 所关联的资源是否具有可以计算的长度。否则，`ProgressEvent.total` 属性将是一个无意义的值。

语法
--
```

flag = ProgressEvent.lengthComputable
```

规范
--

| Specification |
| --- |
| XMLHttpRequest Standard # dom-progressevent-lengthcomputable |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The `ProgressEvent` interface it belongs to.


ProgressEvent.loaded - Web API 接口参考

ProgressEvent.loaded
====================

**`ProgressEvent.loaded`** 只读属性是一个整数，表示底层的进程已经执行的工作量。可以使用该属性和 `ProgressEvent.total` 计算完成工作的比率。当使用 HTTP 下载一个资源，其值以字节（而不是位）为单位，且仅表示内容（body）部分，不包含标头和其他开销。

值
-

一个数值。

规范
--

| Specification |
| --- |
| XMLHttpRequest Standard # dom-progressevent-loaded |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 它属于 `ProgressEvent` 接口。


ProgressEvent() - Web API 接口参考

ProgressEvent()
===============

**`ProgressEvent()`** 构造函数返回一个新的 `ProgressEvent` 对象，表示当前一个漫长处理过程的完成进度。

语法
--

```js
new ProgressEvent(type)
new ProgressEvent(type, options)
```

### 参数

`type`

带有事件名称的字符串。它是区分大小写的，浏览器将其设置为 `loadstart`、`progress`、`abort`、`error`、`load`、`timeout` 或 `loadend`。

`options` 可选

一个对象，除了 `Event()` 中定义的属性，它还具有以下属性：

`lengthComputable` 可选

一个布尔值，表示底层的进程将要完成的总工作量和已经完成的工作量是否是可计算的。换句话说，它说明进度是否可衡量。它的默认值是 `false`。

`loaded` 可选

一个数值，表示底层的进程已经完成的工作量。可以使用该属性和 `ProgressEvent.total` 计算完成工作的比例。当使用 HTTP 下载一个资源时，其仅表示内容本身（body）的部分，不包含头和其他开销。它默认值为 `0`。

`total` 可选

一个数值，表示底层的进程正在进行的工作的总工作量。当使用 HTTP 下载一个资源时，其仅表示内容本身（body）的部分，不包含头和其他开销。它默认值为 `0`。

### 返回值

一个新的 `ProgressEvent` 对象。

规范
--

| Specification |
| --- |
| XMLHttpRequest Standard # dom-progressevent-progressevent |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 它属于 `ProgressEvent` 接口。


ProgressEvent.total - Web API 接口参考

ProgressEvent.total
===================

**`ProgressEvent.total`** 只读属性是一个无符号 64 位整数值，表明正在处理或者传输的数据的总大小。在 HTTP 传输的情况下，这是消息体的大小（`Content-Length`）并且不包含标头和其他的开销。

如果事件的 `lengthComputable` 属性是 `false`，这个值是没有意义的并且应该被忽略。

值
-

一个整数。

规范
--

| Specification |
| --- |
| XMLHttpRequest Standard # dom-progressevent-total |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 它属于 `ProgressEvent` 接口。


EventTarget.removeEventListener() - Web API 接口参考

EventTarget.removeEventListener()
=================================

`EventTarget` 的 **`removeEventListener()`** 方法可以删除使用 `EventTarget.addEventListener()` 方法添加的事件。可以使用事件类型，事件侦听器函数本身，以及可能影响匹配过程的各种可选择的选项的组合来标识要删除的事件侦听器。参见下文的匹配要删除的事件监听器。

调用 `removeEventListener()` 时，若传入的参数不能用于确定当前注册过的任何一个事件监听器，该函数不会起任何作用。

如果一个 `EventTarget` 上的事件监听器在另一监听器处理该事件时被移除，那么它将不能被事件触发。不过，它可以被重新绑定。

**警告：** 如果同一个事件监听器分别为“事件捕获（`capture` 为 `true`）”和“事件冒泡（`capture` 为 `false`）”注册了一次，这两个版本的监听器需要分别移除。移除捕获监听器不会影响非捕获版本的相同监听器，反之亦然。

还有一种移除事件监听器的方法：可以向 `addEventListener()` 传入一个 `AbortSignal`，稍后再调用拥有该事件的控制器上的 `abort()` 方法即可。

语法
--

```js
removeEventListener(type, listener);
removeEventListener(type, listener, options);
removeEventListener(type, listener, useCapture);
```

### 参数

`type`

一个字符串，表示需要移除的事件类型。

`listener`

需要从目标事件移除的事件监听器函数。

`options` 可选

一个指定事件侦听器特征的可选对象。可选项有：

* `capture`: 一个布尔值，指定需要移除的事件监听器函数是否为捕获监听器。如果未指定此参数，默认值为 `false`。

`useCapture` 可选

一个布尔值，指定需要移除的事件监听器函数是否为捕获监听器。如果未指定此参数，默认值为 `false`。

### 返回值

无（`undefined`）。

### 匹配要删除的事件监听器

假设你之前通过 `addEventListener()` 添加了一个事件监听器，你总会在某些情况下需要将其移除。很明显，你需要提供相同的 `type` 和 `listener` 参数给 `removeEventListener()`，但是 `options` 或者 `useCapture` 参数呢？

当使用 `addEventListener()` 时，如果 `options` 参数不同，那么你可以在相同的 `type` 上多次添加相同的监听，唯一需要 `removeEventListener()` 检测的是 `capture`/`useCapture` 标志。这个标志必须与 `removeEventListener()` 的对应标志匹配，但是其他的值不需要。

举个例子，思考一下下面的 `addEventListener()`：

```js
element.addEventListener("mousedown", handleMouseDown, true);
```

现在思考下下面两个 `removeEventListener()`:

```js
element.removeEventListener("mousedown", handleMouseDown, false); // 失败
element.removeEventListener("mousedown", handleMouseDown, true); // 成功
```

第一个调用失败是因为 `useCapture` 没有匹配。第二个调用成功，是因为 `useCapture` 匹配相同。

现在再思考下这个：

```js
element.addEventListener("mousedown", handleMouseDown, { passive: true });
```

这里，我们在 `options` 对象里将 `passive` 设成 `true`，其他 `options` 配置都是默认值 `false`。

现在我们看下下面的 `removeEventListener()`。当配置 `capture` 或 `useCapture` 为 `true` 时，移除事件失败；其他所有都是成功的。这说明只有 `capture` 配置影响 `removeEventListener()`。

```js
element.removeEventListener("mousedown", handleMouseDown, { passive: true }); // 成功
element.removeEventListener("mousedown", handleMouseDown, { capture: false }); // 成功
element.removeEventListener("mousedown", handleMouseDown, { capture: true }); // 失败
element.removeEventListener("mousedown", handleMouseDown, { passive: false }); // 成功
element.removeEventListener("mousedown", handleMouseDown, false); // 成功
element.removeEventListener("mousedown", handleMouseDown, true); // 失败
```

值得注意的是，一些浏览器版本在这方面会有些不一致。除非你有特别的理由，使用与调用 `addEventListener()` 时配置的参数去调用 `removeEventListener()` 是明智的。

示例
--

以下例子展示了添加与删除监听事件：

```js
const body = document.querySelector("body");
const clickTarget = document.getElementById("click-target");
const mouseOverTarget = document.getElementById("mouse-over-target");

let toggle = false;
function makeBackgroundYellow() {
  body.style.backgroundColor = toggle ? "white" : "yellow";

  toggle = !toggle;
}

clickTarget.addEventListener("click", makeBackgroundYellow, false);

mouseOverTarget.addEventListener("mouseover", () => {
  clickTarget.removeEventListener("click", makeBackgroundYellow, false);
});
```

规范
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-eventtarget-removeeventlistener② |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `EventTarget.addEventListener()`


EventTarget() - Web API 接口参考

EventTarget()
=============

**`EventTarget()`** 构造方法将会创建一个新的 `EventTarget` 对象实例。

**备注：** 需要显式调用这一构造函数的情况极为少见。在大多数情况下，这一函数是在继承了 `EventTarget` 类的构造函数中被 `super` 所调用的。

语法
--

```js
new EventTarget();
```

### 参数

无。

### 返回值

一个 `EventTarget` 实例。

示例
--

```js
class MyEventTarget extends EventTarget {
  constructor(mySecret) {
    super();
    this._secret = mySecret;
  }

  get secret() {
    return this._secret;
  }
}

let myEventTarget = new MyEventTarget(5);
let value = myEventTarget.secret; // === 5
myEventTarget.addEventListener("foo", (e) => {
  myEventTarget._secret = e.detail;
});

let event = new CustomEvent("foo", { detail: 7 });
myEventTarget.dispatchEvent(event);
let newValue = myEventTarget.secret; // === 7
```

规范
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-eventtarget-eventtarget① |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `EventTarget`


EventTarget.dispatchEvent() - Web API 接口参考

EventTarget.dispatchEvent()
===========================

`EventTarget` 的 **`dispatchEvent()`** 方法会向一个指定的事件目标派发一个 `Event`，并以合适的顺序（同步地）调用所有受影响的 `EventListener`。标准事件处理规则（包括事件捕获和可选的冒泡过程）同样适用于通过手动使用 `dispatchEvent()` 方法派发的事件。

调用 `dispatchEvent()` 是*触发一个事件*的最后一步。被触发的事件应事先通过 `Event()` 构造函数创建并初始化完毕。

**备注：** 在调用此方法时，`Event.target` 属性默认为当前的 `EventTarget`。

和经由浏览器触发，并通过事件循环异步调用事件处理程序的“原生”事件不同，`dispatchEvent()` 会*同步*调用事件处理函数。在 `dispatchEvent()` 返回之前，所有监听该事件的事件处理程序将在代码继续前执行并返回。

语法
--

```js
dispatchEvent(event)
```

### 参数

`event`

被派发的 `Event`。其 `Event.target` 属性为当前的 `EventTarget`。

### 返回值

* 当 `event` 可被取消（`cancelable` 值为 `true`），且 `event` 中至少有一个事件处理程序调用了 `Event.preventDefault()` 方法时，返回 `false`。否则，返回 `true`。
### 异常

`InvalidStateError` `DomException`

若事件未在初始化时指定类型（`type` 属性值），则抛出此异常。

**警告：** 由事件处理程序抛出的异常视为未被捕获的异常。事件处理程序运行在一个嵌套的调用栈中；在其处理完毕前将阻塞 `caller`，但异常却不会冒泡至 `caller`。

示例
--

请参考创建和触发事件。

规范
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-eventtarget-dispatchevent③ |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* Event 对象


EventTarget.addEventListener() - Web API 接口参考

EventTarget.addEventListener()
==============================

**EventTarget.addEventListener()** 方法将指定的监听器注册到 `EventTarget` 上，当该对象触发指定的事件时，指定的回调函数就会被执行。事件目标可以是一个文档上的元素 `Element`、`Document` 和 `Window`，也可以是任何支持事件的对象（比如 `XMLHttpRequest`）。

**备注：** *推荐*使用 `addEventListener()` 来注册一个事件监听器，理由如下：

* 它允许为一个事件添加多个监听器。特别是对库、JavaScript 模块和其他需要兼容第三方库/插件的代码来说，这一功能很有用。
* 相比于 `onXYZ` 属性绑定来说，它提供了一种更精细的手段来控制 `listener` 的触发阶段。（即可以选择捕获或者冒泡）。
* 它对任何事件都有效，而不仅仅是 HTML 或 SVG 元素。

`addEventListener()` 的工作原理是将实现 `EventListener` 的函数或对象添加到调用它的 `EventTarget` 上的指定事件类型的事件侦听器列表中。如果要绑定的函数或对象已经被添加到列表中，该函数或对象不会被再次添加。

**备注：** 如果先前向事件侦听器列表中添加过一个匿名函数，并且在之后的代码中调用 `addEventListener` 来添加一个功能完全相同的匿名函数，那么之后的这个匿名函数*也会*被添加到列表中。

实际上，即使使用*完全相同*的代码来定义一个匿名函数，这两个函数仍然存在区别，**在循环中也是如此**。在使用该方法的情况下，匿名函数的重复定义会带来许多麻烦，详见下文中的内存问题一节。

当一个 `EventListener` 在 `EventTarget` 正在处理事件的时候被注册到 `EventTarget` 上，它不会被立即触发，但可能在事件流后面的事件触发阶段被触发，例如可能在捕获阶段添加，然后在冒泡阶段被触发。

语法
--

```js
addEventListener(type, listener);
addEventListener(type, listener, options);
addEventListener(type, listener, useCapture);
```

### 参数

`type`

表示监听事件类型的大小写敏感的字符串。

`listener`

当所监听的事件类型触发时，会接收到一个事件通知（实现了 `Event` 接口的对象）对象。`listener` 必须是一个实现了 `EventListener` 接口的对象，或者是一个函数。有关回调本身的详细信息，请参阅事件监听回调

`options` 可选

一个指定有关 `listener` 属性的可选参数对象。可用的选项如下：

`capture` 可选

一个布尔值，表示 `listener` 会在该类型的事件捕获阶段传播到该 `EventTarget` 时触发。

`once` 可选

一个布尔值，表示 `listener` 在添加之后最多只调用一次。如果为 `true`，`listener` 会在其被调用之后自动移除。

`passive` 可选

一个布尔值，设置为 `true` 时，表示 `listener` 永远不会调用 `preventDefault()`。如果 listener 仍然调用了这个函数，客户端将会忽略它并抛出一个控制台警告。查看使用 passive 改善滚屏性能以了解更多。

`signal` 可选

`AbortSignal`，该 `AbortSignal` 的 `abort()` 方法被调用时，监听器会被移除。

`useCapture` 可选

一个布尔值，表示在 DOM 树中注册了 `listener` 的元素，是否要先于它下面的 `EventTarget` 调用该 `listener`。当 useCapture（设为 true）时，沿着 DOM 树向上冒泡的事件不会触发 listener。当一个元素嵌套了另一个元素，并且两个元素都对同一事件注册了一个处理函数时，所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 DOM Level 3 事件及 JavaScript 事件顺序文档。如果没有指定，`useCapture` 默认为 `false`。

**备注：** 对于事件目标上的事件监听器来说，事件会处于“目标阶段”，而不是冒泡阶段或者捕获阶段。捕获阶段的事件监听器会在任何非捕获阶段的事件监听器之前被调用。

`wantsUntrusted` 可选
非标准

如果为 `true`，则事件处理程序会接收网页自定义的事件。此参数只适用于 Gecko（chrome 的默认值为 `false`，其他常规网页的默认值为 `true`），主要用于附加组件的代码和浏览器本身。

### 返回值

无（`undefined`）。

用法说明
----

### 事件监听回调

事件监听器可以被指定为回调函数或一个对象（其 `handleEvent()` 方法用作回调函数）。

回调函数本身具有与 `handleEvent()` 方法相同的参数和返回值；也就是说，回调接受一个参数：一个基于 `Event` 的对象，描述已发生的事件，并且它不返回任何内容。

例如，一个可同时处理 `fullscreenchange` 和 `fullscreenerror` 事件的函数如下：

```js
function eventHandler(event) {
  if (event.type === "fullscreenchange") {
    /\* 处理 fullscreenchange 事件 \*/
  } else {
    /\* 处理 fullscreenerror 事件 \*/
  }
}
```

### option 支持的安全检测

在旧版本的 DOM 的规定中，`addEventListener()` 的第三个参数是一个布尔值，表示是否在捕获阶段调用事件处理程序。随着时间的推移，很明显需要更多的选项。与其在方法之中添加更多参数（传递可选值将会变得异常复杂），倒不如把第三个参数改为一个包含了各种属性的对象，这些属性的值用来被配置删除事件侦听器的过程。

因为旧版本的浏览器（以及一些相对不算古老的）仍然假定第三个参数是布尔值，你需要编写一些代码来有效地处理这种情况。你可以对每一个你感兴趣的 options 值进行特性检测。

如果你想检测 `passive` 值，可以参考下面这个例子：

```js
let passiveSupported = false;

try {
  const options = {
    get passive() {
      // 该函数会在浏览器尝试访问 passive 值时被调用。
      passiveSupported = true;
      return false;
    },
  };

  window.addEventListener("test", null, options);
  window.removeEventListener("test", null, options);
} catch (err) {
  passiveSupported = false;
}
```

这段代码为 `passive` 属性创建了一个带有 getter 函数的 `options` 对象；getter 设定了一个标识 `passiveSupported`，被调用后就会把其设为 `true`。那意味着如果浏览器检查 `options` 对象上的 `passive` 值时，`passiveSupported` 将会被设置为 `true`；否则它将保持 `false`。然后我们调用 `addEventListener()` 去设置一个指定这些选项的空事件处理器，这样如果浏览器将第三个参数认定为对象的话，这些选项值就会被检查。我们再调用 `removeEventListener()` 来移除事件处理器（注意：未被调用的事件处理器上的 `handleEvent()` 会被忽略）。

你可以利用这个方法检查 options 之中任一个值。只需使用与上面类似的代码，为选项设定一个 getter。

然后，当你想实际创建一个是否支持 options 的事件侦听器时，你可以这样做：

```js
someElement.addEventListener(
  "mouseup",
  handleMouseUp,
  passiveSupported ? { passive: true } : false,
);
```

我们在 `someElement` 这里添加了一个 `mouseup`。对于第三个参数，如果 `passiveSupported` 是 `true`，我们传递了一个 `passive` 值为 `true` 的 `options` 对象；如果相反的话，我们知道要传递一个布尔值，于是就传递 `false` 作为 `useCapture` 的参数。

如果你愿意，你可以用一个类似 Modernizr 或 Detect It 的第三方库来帮助你做这项测试。

你可以在 Web Incubator Community Group 里关于`EventListenerOptions` 的文章中了解更多。

示例
--

### 添加一个简单的监听器

这个例子用来展示如何使用 `addEventListener()` 监听鼠标点击一个元素的事件。

```html
<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>
```

```js
// 改变 t2 内容的函数
function modifyText() {
  const t2 = document.getElementById("t2");
  const isNodeThree = t2.firstChild.nodeValue === "three";
  t2.firstChild.nodeValue = isNodeThree ? "two" : "three";
}

// 为 table 添加事件监听器
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);
```

在这个例子中，`modifyText()` 是一个 `click` 事件的监听器，通过使用 `addEventListenter()` 注册到 table 对象上。在表格中任何位置单击都会触发事件并执行 `modifyText()`。

#### 结果

### 添加一个可被移除的监听器

这个例子用来展示如何使用 `addEventListenter()` 添加一个可被 `AbortSignal` 移除的侦听器。

#### HTML

```html
<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>
```

#### JavaScript

```js
// 为 table 添加可被移除的事件监听器
const controller = new AbortController();
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, { signal: controller.signal });

// 改变 t2 内容的函数
function modifyText() {
  const t2 = document.getElementById("t2");
  if (t2.firstChild.nodeValue === "three") {
    t2.firstChild.nodeValue = "two";
  } else {
    t2.firstChild.nodeValue = "three";
    controller.abort(); // 当值变为 "three" 后，移除监听器
  }
}
```

在这个例子中，我们修改了上一个例子的代码。在第二行的内容变为 three 时，我们调用了传入 `addEventListener()` 的 `AbortController` 中的 `abort()` 方法。如此，无论如何点击表格，第二行的内容都不会再发生改变，因为表格中的点击事件监听器已被移除。

#### 结果

### 带有匿名函数的监听器

现在我们来看看如何使用匿名函数来为事件监听器进行传参。

#### HTML

```html
<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>
```

#### JavaScript

```js
// 改变 t2 内容的函数
function modifyText(new\_text) {
  const t2 = document.getElementById("t2");
  t2.firstChild.nodeValue = new_text;
}

// 用匿名函数为 table 添加事件监听器
const el = document.getElementById("outside");
el.addEventListener(
  "click",
  function () {
    modifyText("four");
  },
  false,
);
```

请注意，侦听器是一个匿名函数，它封装了代码，然后代码可以将参数发送到 `modifyText()` 函数，该函数负责实际响应事件。

#### 结果

### 带有箭头函数的监听器

这个例子用来展示如何通过箭头函数来实现一个监听器。

#### HTML

```html
<table id="outside">
  <tr>
    <td id="t1">one</td>
  </tr>
  <tr>
    <td id="t2">two</td>
  </tr>
</table>
```

#### JavaScript

```js
// 改变 t2 内容的函数
function modifyText(new\_text) {
  var t2 = document.getElementById("t2");
  t2.firstChild.nodeValue = new_text;
}

// 用箭头函数为 table 添加事件监听器
const el = document.getElementById("outside");
el.addEventListener(
  "click",
  () => {
    modifyText("four");
  },
  false,
);
```

#### 结果

请注意尽管匿名函数和箭头函数有些类似，但是他们绑定不同的 `this` 对象。匿名函数（和所有传统的 Javascript 函数）创建他们独有的 `this` 对象，而箭头函数则继承绑定他所在函数的 `this` 对象。

这意味着在使用箭头函数时，原函数中可用的变量和常量在事件处理器中同样可用。

### options 用法示例

#### HTML

```html
<div class="outer">
  outer, once & none-once
  <div class="middle" target="\_blank">
    middle, capture & none-capture
    <a class="inner1" href="https://www.mozilla.org" target="\_blank">
      inner1, passive & preventDefault(which is not allowed)
    </a>
    <a class="inner2" href="https://developer.mozilla.org/" target="\_blank">
      inner2, none-passive & preventDefault(not open new page)
    </a>
  </div>
</div>
```

#### CSS

```css
.outer,
.middle,
.inner1,
.inner2 {
  display: block;
  width: 520px;
  padding: 15px;
  margin: 15px;
  text-decoration: none;
}
.outer {
  border: 1px solid red;
  color: red;
}
.middle {
  border: 1px solid green;
  color: green;
  width: 460px;
}
.inner1,
.inner2 {
  border: 1px solid purple;
  color: purple;
  width: 400px;
}
```

#### JavaScript

```js
const outer = document.querySelector(".outer");
const middle = document.querySelector(".middle");
const inner1 = document.querySelector(".inner1");
const inner2 = document.querySelector(".inner2");

const capture = {
  capture: true,
};
const noneCapture = {
  capture: false,
};
const once = {
  once: true,
};
const noneOnce = {
  once: false,
};
const passive = {
  passive: true,
};
const nonePassive = {
  passive: false,
};

outer.addEventListener("click", onceHandler, once);
outer.addEventListener("click", noneOnceHandler, noneOnce);
middle.addEventListener("click", captureHandler, capture);
middle.addEventListener("click", noneCaptureHandler, noneCapture);
inner1.addEventListener("click", passiveHandler, passive);
inner2.addEventListener("click", nonePassiveHandler, nonePassive);

function onceHandler(event) {
  alert("outer, once");
}
function noneOnceHandler(event) {
  alert("outer, none-once, default");
}
function captureHandler(event) {
  //event.stopImmediatePropagation();
  alert("middle, capture");
}
function noneCaptureHandler(event) {
  alert("middle, none-capture, default");
}
function passiveHandler(event) {
  // 在 passive 事件调用中不支持 preventDefault。
  event.preventDefault();
  alert("inner1, passive, open new page");
}
function nonePassiveHandler(event) {
  event.preventDefault();
  //event.stopPropagation();
  alert("inner2, none-passive, default, not open new page");
}
```

#### 结果

分别点击 outer、middle 和 inner 以查看选项的工作方式。

在使用 `options` 对象中具体的值前，最好确保用户的浏览器支持它，因为这些是历史上并非所有浏览器都支持的附加功能。你可以查看 option 支持的安全检测以了解更多

### 拥有多个 option 的监听器

你可以在 `options` 参数中设置多个 option。在以下示例中，我们将设置两个 option：

* `passive`，以确保 `preventDefault()` 不会被调用
* `once`，以确保事件处理函数只会被调用一次。

#### HTML

```html
<button id="example-button">You have not clicked this button.</button>
<button id="reset-button">Click this button to reset the first button.</button>
```

#### JavaScript

```js
const buttonToBeClicked = document.getElementById("example-button");

const resetButton = document.getElementById("reset-button");

// 示例按钮的初始文本
const initialText = buttonToBeClicked.textContent;

// 点击示例按钮后显示的文本
const clickedText = "You have clicked this button.";

// 我们将事件监听器函数单独提出，以防止重复添加监听函数
function eventListener() {
  buttonToBeClicked.textContent = clickedText;
}

function addListener() {
  buttonToBeClicked.addEventListener("click", eventListener, {
    passive: true,
    once: true,
  });
}

// 当点击重置按钮后，示例按钮将被重置，并可以再次更新它自身的状态
resetButton.addEventListener("click", () => {
  buttonToBeClicked.textContent = initialText;
  addListener();
});

addListener();
```

#### 结果

备注
--

### 处理过程中 `this` 的值的问题

通常来说 `this` 的值是触发事件的元素的引用，这种特性在多个相似的元素使用同一个通用事件监听器时非常让人满意。

当使用 `addEventListener()` 为一个元素注册事件的时候，事件处理器里的 `this` 值是该元素的引用。其与传递给句柄的 event 参数的 `currentTarget` 属性的值一样。

```js
my_element.addEventListener("click", function (e) {
  console.log(this.className); // 输出 my\_element 的 className
  console.log(e.currentTarget === this); // 输出 `true`
});
```

需要注意的是，箭头函数没有它自己的 `this` 上下文。

```js
my_element.addEventListener("click", (e) => {
  console.log(this.className); // 警告：`this` 并不指向 `my\_element`
  console.log(e.currentTarget === this); // 输出 `false`
});
```

如果一个事件的属性（例如 `onclick`）是在 HTML 代码中指定的，则这个属性中的 JavaScript 语句实际上会被包裹在一个处理函数中，在这个处理函数中使用 `this` 的效果和使用 `addEventListener()` 来绑定事件的效果是一样的；`this` 的出现代表了元素的引用。

```html
<table id="my\_table" onclick="console.log(this.id);">
  <!-- `this` 指向 table 元素；输出 'my\_table' -->
  …
</table>
```

注意到在一个函数里 `this` 调用的效果和标准规则里面是一样的。请看下面一个例子：

```html
<script>
 function logID() {
 console.log(this.id);
 }
</script>
<table id="my\_table" onclick="logID();">
  <!-- 被调用时，`this` 指向全局（window）对象 -->
  …
</table>
```

这时，`logID()` 中的 `this` 的值会变成全局（`Window`）对象的引用（在严格模式中为 `undefined`）。

#### 使用 bind() 指定 `this` 的值

`Function.prototype.bind()` 方法可以让你为所有后续的调用建立一个固定的 `this` 上下文，以此解决因不同位置的函数调用带来的 `this` 指向不明晰的问题。然而，你仍然需要保持一份对监听器的引用，以便你可以在后续的代码中将其移除。

下面是一个使用/不使用 `bind()` 的例子：

```js
const Something = function (element) {
  // |this| is a newly created object
  this.name = "Something Good";
  this.onclick1 = function (event) {
    console.log(this.name); // undefined, as |this| is the element
  };

  this.onclick2 = function (event) {
    console.log(this.name); // 'Something Good', as |this| is bound to newly created object
  };

  // bind causes a fixed `this` context to be assigned to onclick2
  this.onclick2 = this.onclick2.bind(this);

  element.addEventListener("click", this.onclick1, false);
  element.addEventListener("click", this.onclick2, false); // Trick
};
const s = new Something(document.body);
```

另一种方法是使用 `handleEvent()` 这个特殊函数来捕获任何事件：

```js
const Something = function (element) {
  // |this| is a newly created object
  this.name = "Something Good";
  this.handleEvent = function (event) {
    console.log(this.name); // 'Something Good', as this is bound to newly created object
    switch (event.type) {
      case "click":
        // some code here…
        break;
      case "dblclick":
        // some code here…
        break;
    }
  };

  // Note that the listeners in this case are |this|, not this.handleEvent
  element.addEventListener("click", this, false);
  element.addEventListener("dblclick", this, false);

  // You can properly remove the listeners
  element.removeEventListener("click", this, false);
  element.removeEventListener("dblclick", this, false);
};
const s = new Something(document.body);
```

还有一种控制 *this* 指向的方法，是给 `EventListener` 传递一个函数，来调用想要访问的对应作用域对象：

```js
class SomeClass {
  constructor() {
    this.name = "Something Good";
  }

  register() {
    const that = this;
    window.addEventListener("keydown", (e) => {
      that.someMethod(e);
    });
  }

  someMethod(e) {
    console.log(this.name);
    switch (e.keyCode) {
      case 5:
        // some code here…
        break;
      case 6:
        // some code here…
        break;
    }
  }
}

const myObject = new SomeClass();
myObject.register();
```

### 在使用监听器时进行数据交换

事件监听器看起来就像一个“孤岛”，要向它传入数据非常困难，更别说是要在它被调用后获取任何数据了。事件监听器仅仅接受事件对象这一个自动传入的参数，并且它的返回值还会被忽略。那怎样才能与事件监听器交换数据呢？其实有很多种方法可以做到。

#### 使用 `this` 传入数据

上文提到，你可以使用 `Function.prototype.bind()` 来以 `this` 作为媒介向监听器传值。

```js
const myButton = document.getElementById("my-button-id");
const someString = "Data";

myButton.addEventListener(
  "click",
  function () {
    console.log(this); // 预期输出：'Data'
  }.bind(someString),
);
```

这个方法适合在你无需关心监听器是在哪个 HTML 元素上触发的时候使用，好处是这样传值就好像是你在使用参数列表向监听器传值一样。

#### 巧用变量作用域传值

当外部作用域包含一个（使用 `const`、`let` 的）变量声明时，所有该作用域内声明的函数都可以访问该变量（查看此链接以了解外部/内部函数，以及此链接以了解变量作用域）。因此，最简单的传入数据的方法就是使监听器所在的作用域可以访问到你所声明的变量。

```js
const myButton = document.getElementById("my-button-id");
let someString = "Data";

myButton.addEventListener("click", () => {
  console.log(someString); // 预期输出：'Data'

  someString = "Data Again";
});

console.log(someString); // 预期输出：'Data'（不可能是 'Data Again'）
```

**备注：** 虽然内部作用域可以访问到外部 `const` 和 `let` 所声明的变量，但你在事件监听器内对变量所做的任何修改都不能在外部作用域内体现。为什么呢？因为在事件监听器被执行时，其外部作用域已经执行完毕了。

#### 使用对象进行数据交换

与 JavaScript 中的大部分函数不同，一个对象只要有被引用计数，它就会一直存在于内存之中。并且，对象可以拥有自己的属性，属性又可以被引用形式传递，使得它有潜力成为在作用域间传值的候选。让我们来试试。

**备注：** 实际上，在 JavaScript 中，函数也是对象的一种。（因此，函数也可以有自己的对象，甚至可以在其执行结束后依旧存在于内存中，只要将其分配到一个永久存在于内存中的变量即可。）

因为只需一个变量引用就可以使对象的属性将数据存储在内存之中，你可以使用它与事件监听器进行数据交换，并且更改后的数据会被保留。请看下面这个例子：

```js
const myButton = document.getElementById("my-button-id");
const someObject = { aProperty: "Data" };

myButton.addEventListener("click", () => {
  console.log(someObject.aProperty); // 预期输出：'Data'

  someObject.aProperty = "Data Again"; // 更改值
});

setInterval(() => {
  if (someObject.aProperty === "Data Again") {
    console.log("Data Again: True");
    someObject.aProperty = "Data"; // 将值重置以等待下次事件调用
  }
}, 5000);
```

在这一例子中，虽然事件监听器和定时函数的作用域都在 `someObject.aProperty` 的值变更前执行完毕，但 `someObject` 仍被事件监听器和定时函数所*引用*而存在于内存中，这两个函数依然能访问到同样的数据（也就是说，当其中一方改变了数据，另一方可以对这一改变做出响应）。

**备注：** 对象的是以引用方式存储于变量中的，也即变量中存储的只是对象的内存地址而已。在其他方面上，这也意味着“存储”这个对象的变量可以对其他引用（或“存储”）同一个对象的变量产生影响。当两个变量引用同一对象时（比如 `let a = b = {aProperty: 'Yeah'};`），仅对其中一个变量进行更改仍然会影响到另一个变量。

**备注：** 基于对象存储于变量中的“引用”逻辑，你可以在一个函数执行完毕后返回一个对象以使其“保活”（使其保留在内存中且不丢失数据）。

### 内存问题

```js
const els = document.getElementsByTagName("\*");

// 例一
for (let i = 0; i < els.length; i++) {
  els[i].addEventListener(
    "click",
    (e) => {
      /\* 处理点击事件 \*/
    },
    false,
  );
}

// 例二
function processEvent(e) {
  /\* 处理同样的点击事件 \*/
}

for (let i = 0; i < els.length; i++) {
  els[i].addEventListener("click", processEvent, false);
}
```

在上面的第一个例子中，一个新的（匿名）函数在每次循环中被创建一次。在第二个例子中，与之前的匿名函数功能相同的函数被用作事件监听器，但后者所带来的内存开销要更小一点，因为函数只被声明过一次。此外，在第一个例子中，我们不能调用 `removeEventListener()`，因为我们没有保留任何对匿名函数的引用（在例子的情况中，是没有保存对循环中创建的多个匿名函数的引用）。而在第二个例子中，`processEvent` 是一个可被引用的函数，因此可以调用 `myElement.removeEventListener("click", processEvent, false)`。

实际上，真正影响内存的并不是没有保持函数引用，而是没有保持*静态*的函数引用。

### 使用 passive 改善滚屏性能

将 `passive` 设为 `true` 可以启用性能优化，并可大幅改善应用性能，正如下面这个例子：

```js
/\* 检测浏览器是否支持该特性 \*/
let passiveIfSupported = false;

try {
  window.addEventListener(
    "test",
    null,
    Object.defineProperty({}, "passive", {
      get() {
        passiveIfSupported = { passive: true };
      },
    }),
  );
} catch (err) {}

window.addEventListener(
  "scroll",
  (event) => {
    /\* do something \*/
    // 不能使用 event.preventDefault();
  },
  passiveIfSupported,
);
```

根据规范，`addEventListener()` 的 `passive` 默认值始终为 `false`。然而，这引入了触摸事件和滚轮事件的事件监听器在浏览器尝试滚动页面时阻塞浏览器主线程的可能性——这可能会大大降低浏览器处理页面滚动时的性能。

为了避免这一问题，大部分浏览器（Safari 和 Internet Explorer 除外）将文档级节点 `Window`、`Document` 和 `Document.body` 上的 `wheel`、`mousewheel`、`touchstart` 和 `touchmove` 事件的 `passive` 默认值更改为 `true`。如此，事件监听器便不能取消事件，也不会在用户滚动页面时阻止页面呈现。

**备注：** 若你想要了解哪些浏览器（或特定浏览器的哪一个版本）具体实现了上述功能，请参见下方的浏览器兼容性表。

因此，当你想要覆盖这一行为并确认 `passive` 在所有浏览器中都被设为 `false`，你必须显式地将其设为 `false`，而不是依赖浏览器的默认设置。

不过，你无需担心基本 `scroll` 事件的 `passive` 值。因为该事件不能被取消，事件监听器也就无法阻止页面的渲染。

### 旧版本浏览器

在不支持 `addEventListener()` 的 `options` 参数的旧浏览器上，尝试使用它会阻止使用 `useCapture` 参数而不正确使用特征检测。

规范
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-eventtarget-addeventlistener③ |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `EventTarget.removeEventListener()`
* 创建和触发自定义事件
* More details on the use of `this` in event handlers


File.name - Web API 接口参考

File.name
=========

返回由 `File` 对象表示的文件的名称。由于安全原因，该属性并不包含文件路径。

值
-

一个字符串，包含不带路径的文件名，例如“My Resume.rtf”。

示例
--

### HTML

```html
<input type="file" id="filepicker" multiple />
<div>
  <p>选定文件列表：</p>
  <ul id="output"></ul>
</div>
```

### JavaScript

```js
const output = document.getElementById("output");
const filepicker = document.getElementById("filepicker");

filepicker.addEventListener("change", (event) => {
  const files = event.target.files;
  output.textContent = "";

  for (const file of files) {
    const li = document.createElement("li");
    li.textContent = file.name;
    output.appendChild(li);
  }
});
```

### 结果

规范
--

| Specification |
| --- |
| File API # dfn-name |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 在 web 应用程序中使用文件


File.lastModifiedDate - Web API 接口参考

File.lastModifiedDate
=====================

概述
--

返回当前文件的最后修改日期，如果无法获取到文件的最后修改日期，则使用当前日期来替代。

示例
--

```js
// fileInput 是一个 HTMLInputElement 元素：<input type="file" multiple id="myfileinput">
var fileInput = document.getElementById("myfileinput");

// files 是一个 FileList 对象 (类似于 NodeList 对象)
var files = fileInput.files;

for (var i = 0; i < files.length; i++) {
  alert(
    files[i].name + " has a last modified date of " + files[i].lastModifiedDate,
  );
}
```


File.File() - Web API 接口参考

File.File()
===========

**`File()`** 构造器创建新的 `File` 对象实例。

语法
--
```

var myFile = new File(bits, name[, options]);
```

### 参数

*bits*

一个包含`ArrayBuffer`，`ArrayBufferView`，`Blob`，或者 `DOMString` 对象的 `Array` — 或者任何这些对象的组合。这是 UTF-8 编码的文件内容。

*name*

`USVString`，表示文件名称，或者文件路径。

*options* 可选

选项对象，包含文件的可选属性。可用的选项如下：

* `type`: `DOMString`，表示将要放到文件中的内容的 MIME 类型。默认值为 `""` 。
* `lastModified`: 数值，表示文件最后修改时间的 Unix 时间戳（毫秒）。默认值为 `Date.now()`。

示例
--

```js
var file = new File(["foo"], "foo.txt", {
  type: "text/plain",
});
```

规范
--

| Specification |
| --- |
| File API # file-constructor |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

另见
--

* `FileReader`
* `Blob`


File.lastModified - Web API 接口参考

File.lastModified
=================

只读属性 **`File.lastModified`** 返回所引用文件最后修改日期，为自 1970 年 1 月 1 日 0:00 以来的毫秒数。没有已知的最后修改时间则会返回当前时间。

语法
--

```js
var time = instanceOfFile.lastModified;
```

值
-

自 1970 年 1 月 1 日 0:00 以来的毫秒数。

实例
--

### 从 INPUT 标签读取文件

```html
<input type="file" multiple id="fileInput" />
```

```js
const fileInput = document.getElementById("fileInput");
fileInput.addEventListener("change", function (event) {
  // files is a FileList object (simliar to NodeList)
  const files = event.target.files;

  for (let i = 0; i < files.length; i++) {
    const date = new Date(files[i].lastModified);
    alert(files[i].name + " has a last modified date of " + date);
  }
});
```

结果：

### 动态创建文件

如果文件是动态创建的，可以在构造函数`new File()` 中提供最后修改时间。如果未提供则会继承文件对象被创建时的`Date.now()` 。

```js
var fileWithDate = new File([], "file.bin", {
  lastModified: new Date(2017, 1, 1),
});
console.log(fileWithDate.lastModified); //returns 1485903600000

var fileWithoutDate = new File([], "file.bin");
console.log(fileWithoutDate.lastModified); //returns current time
```

规范
--

| Specification |
| --- |
| File API # dfn-lastModified |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `File`


File.webkitRelativePath - Web API 接口参考

File.webkitRelativePath
=======================

**非标准:** 该特性是非标准的，请尽量不要在生产环境中使用它！

**`File.webkitRelativePath`** 是只读属性，包含 `USVString`，它规定了文件的路径，相对于用户在 `<input>` 元素中选择的目录，这个元素设置了 `webkitdirectory` 属性。

语法
--
```

 relativePath = File.webkitRelativePath
```

### 值

`USVString` 包含文件路径，相对于用户所选的祖先目录。

示例
--

这个示例展示了目录选择器，它让用户选择一个或多个目录。`change` 事件触发时，所选目录包含的所有文件的列表，会生成并展示，带有所选目录的层次结构。

### HTML

```html
<input type="file" id="filepicker" name="fileList" webkitdirectory multiple />
<ul id="listing"></ul>
```

### JavaScript

```js
document.getElementById("filepicker").addEventListener(
  "change",
  function (event) {
    let output = document.getElementById("listing");
    let files = event.target.files;

    for (let i = 0; i < files.length; i++) {
      let item = document.createElement("li");
      item.innerHTML = files[i].webkitRelativePath;
      output.appendChild(item);
    }
  },
  false,
);
```

### 结果

特别提醒：假设文件路径是 C:\f1\f2\f3\file.txt, 用户选择的是 f1 文件夹，则 Chrome、Firefox、Edge 都能正确返回 f2/f3/file.txt 值。而国产的 QQ 浏览器、360 浏览器、UC 浏览器、搜狗浏览器都只能返回 f3/file.txt。也就是国产浏览器调用 webkitRelativePath 返回的结果都不会是你希望得到的路径，请注意使用时的细微差距。

规范
--

| Specification |
| --- |
| File and Directory Entries API # dom-file-webkitrelativepath |

这个 API 没有官方的 W3C 或者 WHATWG 规范。

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

另见
--

* 文件和目录条目 API
* `HTMLInputElement.webkitEntries` (en-US)
* `HTMLInputElement.webkitdirectory`


PerformanceEntry.entryType - Web API 接口参考

PerformanceEntry.entryType
==========================

The **`entryType`** 返回一个代表 performance metric 类型的`DOMString` , 例如被 performance.mark("begin") 所创建的 entry 的 entryType 就是 "`mark`". 此属性只读。

语法
--
```

var type = entry.entryType;
```

### 返回值

返回值取决于 `PerformanceEntry` 对象的 subtype，entryType 的取值会影响`PerformanceEntry.name` 属性，具体如下表所示。

| Value | Subtype | Type of name property | Description of name property |
| --- | --- | --- | --- |
| `frame`, `navigation` | `PerformanceFrameTiming`, `PerformanceNavigationTiming` | `URL` | The document's address. |
| `resource` | `PerformanceResourceTiming` | `URL` | The resolved URL of the requested resource. This value doesn't change even if the request is redirected. |
| `mark` | `PerformanceMark` (en-US) | `DOMString` | The name used when the mark was created by calling `performance.mark()`. |
| `measure` | `PerformanceMeasure` (en-US) | `DOMString` | name used when the measure was created by calling `performance.measure()`. |
| `paint` | `PerformancePaintTiming` | `DOMString` | Either `'first-paint'` or `'first-contentful-paint'`. |

范例
--

下面的代码说明了 `entryType` 属性的用法。

```js
function run\_PerformanceEntry() {
  // check for feature support before continuing
  if (performance.mark === undefined) {
    console.log("performance.mark not supported");
    return;
  }

  // Create a performance entry named "begin" via the mark() method
  performance.mark("begin");

  // Check the entryType of all the "begin" entries
  var entriesNamedBegin = performance.getEntriesByName("begin");
  //entriesNamedBegin
  // Array [ PerformanceMark ]
  //entriesNamedBegin[0]
  // PerformanceMark { name: "begin", entryType: "mark", startTime: 94661370.14, duration: 0 }
  //entriesNamedBegin[0].entryType
  // "mark"
  //entriesNamedBegin[0].name
  // "begin"

  for (var i = 0; i < entriesNamedBegin.length; i++) {
    var typeOfEntry = entriesNamedBegin[i].entryType;
    console.log("Entry is type: " + typeOfEntry);
  }
}
```

Specifications
--------------

| Specification |
| --- |
| Performance Timeline # dom-performanceentry-entrytype |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


PerformanceEntry.name - Web API 接口参考

PerformanceEntry.name
=====================

**`name`** 是 `PerformanceEntry` 接口的属性，此属性的返回值是 `PerformanceEntry.entryType` 的返回值的一个补充，例如 entry.entryType="navigation",entry.name="document". 这是一个只读属性。

Syntax
------
```

var name = entry.name;
```

### 返回值

返回值取决于`PerformanceEntry` 对象的 subtype 和`PerformanceEntry.entryType`的值，如下表所示。

| Value | Subtype | entryType values | Description |
| --- | --- | --- | --- |
| `URL` | `PerformanceFrameTiming`, `PerformanceNavigationTiming` | `frame`, `navigation` | The document's address. |
| `URL` | `PerformanceResourceTiming` | `resource` | The resolved URL of the requested resource. This value doesn't change even if the request is redirected. |
| `DOMString` | `PerformanceMark` (en-US) | `mark` | The name used when the mark was created by calling `performance.mark()`. |
| `DOMString` | `PerformanceMeasure` (en-US) | `measure` | name used when the measure was created by calling `performance.measure()`. |
| `DOMString` | `PerformancePaintTiming` | `paint` | Either `'first-paint'` or `'first-contentful-paint'`. |

用例
--

下面的例子是 `name` 属性的用法。

```js
function run\_PerformanceEntry() {
  log("PerformanceEntry support ...");

  if (performance.mark === undefined) {
    log("... performance.mark Not supported");
    return;
  }

  // Create some performance entries via the mark() method
  performance.mark("Begin");
  do\_work(50000);
  performance.mark("End");

  // Use getEntries() to iterate through the each entry
  var p = performance.getEntries();
  for (var i = 0; i < p.length; i++) {
    log("Entry[" + i + "]");
    check\_PerformanceEntry(p[i]);
  }
}
//
//例如上面 p 中一个 entry p[0] = {
// "name": "document",
// "entryType": "navigation",
// "startTime": 0,
// "duration": 3611.26,
// "initiatorType": "navigation",
// "nextHopProtocol": "http/1.1",
// "workerStart": 0,
// "redirectStart": 0,
// "redirectEnd": 0,
// "fetchStart": 0.32,
// "domainLookupStart": 17.64,
// "domainLookupEnd": 17.78,
// "connectStart": 17.86,
// "connectEnd": 18.1,
// "secureConnectionStart": 0,
// "requestStart": 18.3,
// "responseStart": 294.06,
// "responseEnd": 1610.3600000000001,
// "transferSize": 97683,
// "encodedBodySize": 97112,
// "decodedBodySize": 97112,
// "unloadEventStart": 1614.8372840721554,
// "unloadEventEnd": 1619.1600105887128,
// "domInteractive": 3110.767514889843,
// "domContentLoadedEventStart": 3125.859851800787,
// "domContentLoadedEventEnd": 3438.5779820633365,
// "domComplete": 3609.999662153349,
// "loadEventStart": 3610.017623620869,
// "loadEventEnd": 3611.2672285754975,
// "type": "reload",
// "redirectCount": 0
//}

//下面的函数 check\_PerformanceEntry 的参数 obj 就是上面的 p[0]
//
function check\_PerformanceEntry(obj) {
  var properties = ["name", "entryType", "startTime", "duration"];
  var methods = ["toJSON"];

  for (var i = 0; i < properties.length; i++) {
    // check each property
    var supported = properties[i] in obj;
    if (supported) log("..." + properties[i] + " = " + obj[properties[i]]);
    else log("..." + properties[i] + " = Not supported");
  }
  for (var i = 0; i < methods.length; i++) {
    // check each method
    var supported = typeof obj[methods[i]] == "function";
    if (supported) {
      var js = obj[methods[i]]();
      log("..." + methods[i] + "() = " + JSON.stringify(js));
    } else {
      log("..." + methods[i] + " = Not supported");
    }
  }
}
```

Specifications
--------------

| Specification |
| --- |
| Performance Timeline # dom-performanceentry-name |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


DOMImplementation.createDocumentType() - Web API 接口参考

DOMImplementation.createDocumentType()
======================================

**`DOMImplementation.createDocumentType()`** 方法返回一个 `DocumentType` 对象，它可以在文档创建时用在 `DOMImplementation.createDocument` ，或者通过`Node.insertBefore()` 或 `Node.replaceChild()` 等方法放在文档中。

语法
--
```

var doctype = document.implementation.createDocumentType(qualifiedNameStr, publicId, systemId);
```

### 参数

`qualifiedNameStr`

`DOMString` 类型的值，包含一个合规的名称，如 `svg:svg`。

`publicId`

`DOMString` 类型的值，包含 `PUBLIC` 标识符。

`systemId`

`DOMString` 类型的值，包含 `SYSTEM` 标识符。

示例
--

```js
var dt = document.implementation.createDocumentType(
  "svg:svg",
  "-//W3C//DTD SVG 1.1//EN",
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd",
);
var d = document.implementation.createDocument(
  "http://www.w3.org/2000/svg",
  "svg:svg",
  dt,
);
alert(d.doctype.publicId); // -//W3C//DTD SVG 1.1//EN
```

规范
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-domimplementation-createdocumenttype① |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 该方法所属的接口 `DOMImplementation`


DOMImplementation.createDocument() - Web API 接口参考

DOMImplementation.createDocument()
==================================

**`DOMImplementation.createDocument()`方法创建并返回一个** `XMLDocument`对象。

语法
--
```

doc = document.implementation.createDocument(namespaceURI, qualifiedNameStr, documentType);
```

### 参数

*namespaceURI*

被创建的`DOMString` 文档的 namespace URI 是 namespace URI ,如果文档不属于任何 namespace URI 就为`null`.

*qualifiedNameStr*

`DOMString` 是否包含要创建文档的限定名称，即可选的前缀和冒号，以及本地的根元素。

*documentType* 可选

文档的 `DocumentType` 默认为 null.

例子
--

```js
var doc = document.implementation.createDocument(
  "http://www.w3.org/1999/xhtml",
  "html",
  null,
);
var body = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
body.setAttribute("id", "abc");
doc.documentElement.appendChild(body);
alert(doc.getElementById("abc")); // [object HTMLBodyElement]
```

规范
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-domimplementation-createdocument② |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 归属它的 `DOMImplementation` 接口。


DOMImplementation.createHTMLDocument - Web API 接口参考

DOMImplementation.createHTMLDocument
====================================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

### 概述

该方法 (属于`document.implementation`) 用来创建一个新的 HTML 文档。

### 语法
```

var doc = document.implementation.createHTMLDocument(title);
```

* `doc` 是新建的 HTML 文档。
* `title` 是 doc 中的 title 标签中的文本。
### 例子

下面的例子演示如何创建了一个新的 HTML 文档，并把它插入到当前文档的一个`<iframe>`中。

查看在线演示

例子中的 HTML 代码如下：

```html
<body>
  <p>
    Click <a href="javascript:makeDocument()">here</a> to create a new document
    and insert it below.
  </p>
  <iframe id="theFrame" src="about:blank" />
</body>
```

例子中用 JavaScript 实现的`makeDocument()`方法如下：

```js
function makeDocument() {
  var frame = document.getElementById("theFrame");

  var doc = document.implementation.createHTMLDocument("New Document");
  var p = doc.createElement("p");
  p.innerHTML = "This is a new paragraph.";

  try {
    doc.body.appendChild(p);
  } catch (e) {
    console.log(e);
  }

  // 将新建的 HTML 文档放到 iframe 中。

  var destDocument = frame.contentDocument;
  var srcNode = doc.documentElement;
  var newNode = destDocument.importNode(srcNode, true);

  destDocument.replaceChild(newNode, destDocument.documentElement);
}
```

代码 4-12 行创建了一个新的 HTML 文档，并在里面插入一些内容。第 4 行 `createHTMLDocument()`构造了一个标题为"New Document"的 HTML 文档。5-6 行创建了一个段落元素并在里面插入了一些内容，8-12 行将新建的段落元素插入到 HTML 文档中。

16 行获取了 iframe 的`contentDocument` 属性。这是我们将要插入新建的 HTML 文档的地方。下面的两行将新建的 HTML 文档插入到了 iframe 的根元素中。这样，我们用 20 行代码实现了用一个新建的 HTML 文档替换 iframe 中原有文档的目的。

### 备注

新生成的 HTML 文档有如下的初始结构：

```html
<!doctype html>
<html>
  <head>
    <title>title</title>
  </head>
  <body></body>
</html>
```

译者注：
```

alert(document.implementation.createHTMLDocument("myTitle").documentElement.outerHTML)

//<html><head><title>myTitle</title></head><body></body></html>
```

### 规范

* `createHTMLDocument` specification


Selection.rangeCount - Web API 接口参考

Selection.rangeCount
====================

**`Selection.rangeCount`** 是一个返回选区（selection）中范围（range）数量的只读属性。

在网页使用者点击一个加载完毕的新打开的页面之前，`rangeCount` 的值是 `0`。在使用者点击页面之后，`rangeCount` 的值变为 `1`，即使并没有可视的选区。

使用者一般情况下在一次只能选择一个 range，所以通常情况下 `rangeCount` 属性的值总为 `1`。脚本可以使选区包含多个范围。

Gecko 浏览器允许跨表格单元格获得多个选区。Firefox allows to select multiple ranges in the document by using Ctrl+click (unless the click within an element with display: table-cell).

示例
--

下面这个例子会每隔一秒显示一次 `rangeCount` 的值。在浏览器中选择文本，然后查看其值的变化。

### HTML

```html
<table>
  <tr>
    <td>a.1</td>
    <td>a.2</td>
  </tr>
  <tr>
    <td>b.1</td>
    <td>b.2</td>
  </tr>
  <tr>
    <td>c.1</td>
    <td>c.2</td>
  </tr>
</table>
```

### JavaScript

```js
setInterval(() => {
  console.log(window.getSelection().rangeCount);
}, 1000);
```

### 结果

打开控制台以查看选区中范围的数量。在 Gecko 浏览器中，你可以通过按住 `Ctrl`（或在 MacOS 上为 `Cmd`）同时拖动鼠标来选择表格单元格中的多个范围。

规范
--

| Specification |
| --- |
| Selection API # dom-selection-rangecount |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 属于 `Selection` 接口。


Selection.isCollapsed - Web API 接口参考

Selection.isCollapsed
=====================

### 概述

返回一个布尔值用于描述选区的起始点和终止点是否位于一个位置（即是否框选了，译者注）。

### 用法
```

sel.isCollapsed
```

### 注意

即使是一个被折叠了的选区，其 rangeCount 的结果也可能大于 0。sel.getRangeAt(0) 会在选区被折叠的情况下返回一个值。


Selection.anchorOffset - Web API 接口参考

Selection.anchorOffset
======================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

只读属性，返回选区的锚节点（ `Selection.anchorNode`）起点偏移量的数字。返回值从零开始计数，如果选区从锚节点（`Selection.anchorNode`）的第一个字符开始，返回值为 0。

语法
--
```

number = sel.anchorOffset
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-anchoroffset |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关内容
----

* `Selection`, the interface it belongs to.


Selection.addRange() - Web API 接口参考

Selection.addRange()
====================

概述
--

向选区（`Selection`）中添加一个区域（`Range`）。

语法
--
```

sel.addRange(range)
```

### 参数

*`range`*

一个区域（`Range`）对象将被增加到选区（`Selection`）当中。

例子
--

```js
/\* 在一个 HTML 文档中选中所有加粗的文本。 \*/

var strongs = document.getElementsByTagName("strong");
var s = window.getSelection();

if (s.rangeCount > 0) s.removeAllRanges();

for (var i = 0; i < strongs.length; i++) {
  var range = document.createRange();
  range.selectNode(strongs[i]);
  s.addRange(range);
}
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-addrange |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`，此方法所属接口。


Selection.getRangeAt() - Web API 接口参考

Selection.getRangeAt()
======================

**概述**
返回一个包含当前选区内容的区域对象。

语法
--
```

range = sel.getRangeAt(index)
```

### 参数

*`range`*

将返回 range 对象。

*`index`*

该参数指定需要被处理的子集编号（从零开始计数）。如果该数值被错误的赋予了大于或等于 rangeCount (en-US) 结果的数字，将会产生错误。

例子
--

```js
let ranges = [];

sel = window.getSelection();

for (var i = 0; i < sel.rangeCount; i++) {
  ranges[i] = sel.getRangeAt(i);
}
/\* 在 ranges 数组的每一个元素都是一个 range 对象，
 \* 对象的内容是当前选区中的一个。 \*/
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-getrangeat |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* Tree Selection (for the `getRangeAt()` method on the `nsITreeSelection` interface)


Selection.selectAllChildren() - Web API 接口参考

Selection.selectAllChildren()
=============================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

Selection.selectAllChildren() 把指定元素的所有子元素设为选中区域，并取消之前的选中区域。

用法
--
```

sel.selectAllChildren(parentNode)
```

### 参数

*`parentNode`*

所有 parentNode 元素的子元素会被设为选中区域，parentNode 本身除外。

举例
--
```

footer = document.getElementById("footer");
window.getSelection().selectAllChildren(footer);
/* Everything inside the footer is now selected *
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-selectallchildren |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`, 此方法所属接口。


Selection.containsNode() - Web API 接口参考

Selection.containsNode()
========================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.containsNode()`** 判断指定的节点是否包含在 Selection 中 (是否被选中).

语法
--
```

sel.containsNode(aNode,aPartlyContained)
```

### 参数

*`aNode`*

用于判断是否包含在 Selection 中的那个节点

*`aPartlyContained`*

 当此参数为`true 时`, 当`selection 包含节点 aNode 的一部分或全部时，containsNode() 返回 true`.
 当此参数为`false 时`, 只有当 selection 完全包含节点 aNode 时，`containsNode()` 才返回 true.

例子
--
```

 /* 检查 body 中是否有节点被选中 */
 console.log(window.getSelection().containsNode(document.body, true));
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-containsnode |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参考
--

* `Selection`, Selection 接口规范。


Selection.type - Web API 接口参考

Selection.type
==============

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

\*\*`type`\*\*是 `Selection` 接口的只读属性，其返回的是`DOMString`即描述当前选择的类型。

语法
--
```

value = sel.type
```

### Value

`DOMString` 描述的是当前选择的类型。可能的值为：

* `None`: 当前没有选择。
* `Caret`: 选区已折叠（即 光标在字符之间，并未处于选中状态）。
* `Range`: 选择的是一个范围。
例子
--

在下面的示例中，回调函数将在每次进行新的选择时触发。 `console.log(selection.type)` 将会输出 `Caret` 或者 `Range` ，其输出值取决于插入标记是放置在文本中的单个点还是已选择范围。
```

var selection;

document.onselectionchange = function() {
  console.log('New selection made');
  selection = document.getSelection();
  console.log(selection.type);
};
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-type |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参考
--

* `Selection`


Selection.extend() - Web API 接口参考

Selection.extend()
==================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.extend()`** 方法移动选中区的焦点到指定的点。选中区的锚点不会移动。选中区将从锚点开始到新的焦点，不管方向。

语法
--
```

sel.extend(node, offset)
```

### 参数

*node*

焦点会被移至此节点内。

*offset* 可选

焦点会被移至 `node` 内的偏移位置。如果没有指定，使用 `0` 作为默认值。

规范
--

| Specification |
| --- |
| Selection API # dom-selection-extend |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`, 此方法所属接口。


Selection.anchorNode - Web API 接口参考

Selection.anchorNode
====================

概述
--

**`Selection.anchorNode`** 只读属性返回选区开始位置所属的节点（`Node`）。

用法
--
```

sel.anchorNode
```

注意
--

用户可能从左至右进行框选（沿着文字顺序）或者从右至左框选（沿着文字顺序的反方向）。锚点位于用户开始选择的位置。可以通过按住 Shift 和方向键来得知锚点所在的位置。选区的锚点是不会移动的，但是选区的焦点、选区其他的结束位置（多个选区，译者注）可以移动。

规范
--

| Specification |
| --- |
| Selection API # dom-selection-anchornode |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`, 此属性所属接口。


Selection.removeRange() - Web API 接口参考

Selection.removeRange()
=======================

### 概述

将一个区域从选区中移除。

### 用法
```

sel.removeRange(range)
```

### 参数

*`range`*

Range 对象将从选区当中移除。

### Examples
```

/* 通过设计一段 js 代码，我们可以获得多个区域，
 * 这段代码可以移除除了第一个区域之外的所有区域。
 *（此代码在 Chrome 中不生效，因为 Chrome 当中只能
 * 选择一个选区，哎我为什么要在 Mozilla 的网站上
 * 说这个？译者注）*/

s = window.getSelection();
if(s.rangeCount > 1) {
 for(var i = 1; i < s.rangeCount; i++) {
  s.removeRange(s.getRangeAt(i));
 }
}
```


Selection.removeAllRanges() - Web API 接口参考

Selection.removeAllRanges()
===========================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.removeAllRanges()`** 方法会从当前 `selection` 对象中移除所有的 range 对象，取消所有的选择只 留下`anchorNode` 和`focusNode`属性并将其设置为 null。

语法
--
```

sel.removeAllRanges();
```

### 参数

*无。*

规范
--

| Specification |
| --- |
| Selection API # dom-selection-removeallranges |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`，此方法所属接口。


Selection.toString() - Web API 接口参考

Selection.toString()
====================

**`Selection.toString()`** 方法返回 selection 对象当前代表的字符串。例如，当前选中的文本。

语法
--

```js
toString()
```

### 参数

无。

### 返回值

selection 对象代表的字符串。

描述
--

此方法返回当前被选中的文本。

在 JavaScript 中，当 selection 对象作为字符串类型传递给函数时，此方法会被自动调用：

```js
alert(window.getSelection()); // 被调用时
alert(window.getSelection().toString()); // 真实情况
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-stringifier |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参考
--

* 属于 `Selection` 接口。


Selection.collapseToStart() - Web API 接口参考

Selection.collapseToStart()
===========================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.collapseToStart()`** 方法的作用是取消当前选区，并把光标定位在原选区的最开始处，如果此时光标所处的位置是可编辑的，且它获得了焦点，则光标会在原地闪烁。

语法
--
```

sel.collapseToStart()
```

### 参数

*无。*

规范
--

| Specification |
| --- |
| Selection API # dom-selection-collapsetostart |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参见
--

* 此方法所属的接口，`Selection`。


Selection.focusOffset - Web API 接口参考

Selection.focusOffset
=====================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

只读属性 **`Selection.focusOffset`** 返回选区终点（鼠标松开瞬间所记录的那个点）在焦点（`Selection.focusNode`）中的偏移量。返回值从零开始计数，如果选区（`Selection`）在焦点（`Selection.focusNode`）的第一个字符前结束，返回值为 0。

语法
--
```

offset = sel.focusOffset
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-focusoffset |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`, 这个属性所属的接口。


Selection.collapse() - Web API 接口参考

Selection.collapse()
====================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.collapse()`** 方法可以收起当前选区到一个点。文档不会发生改变。如果选区的内容是可编辑的并且焦点落在上面，则光标会在该处闪烁。

语法
--
```

sel.collapse(parentNode, offset);
```

### 参数

*`parentNode`*

光标落在的目标节点。

*`offset`* 可选

落在节点的偏移量。

例子
--

```js
/\* 将光标收起到文档 body 的开头 \*/
var body = document.getElementsByTagName("body")[0];
window.getSelection().collapse(body, 0);
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-collapse |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关连接
----

* `Selection`, 本方法所属的接口。


Selection.collapseToEnd() - Web API 接口参考

Selection.collapseToEnd()
=========================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.collapseToEnd()`** 方法的作用是取消当前选区，并把光标定位在原选区的最末尾处，如果此时光标所处的位置是可编辑的，且它获得了焦点，则光标会在原地闪烁。

语法
--
```

sel.collapseToEnd()
```

### 参数

无

规范
--

| Specification |
| --- |
| Selection API # dom-selection-collapsetoend |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`


Selection.setBaseAndExtent() - Web API 接口参考

Selection.setBaseAndExtent()
============================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`setBaseAndExtent()`** 方法是 `Selection` 接口用来选中并设置在两个特定的 DOM 节点中文本选中的范围，并且选中的任何内容都位于两个节点之间。

语法
--
```

sel.setBaseAndExtent(anchorNode,anchorOffset,focusNode,focusOffset)
```

### 参数

*`anchorNode`*

锚节点 - 选中内容的开始节点

*`anchorOffset`*

选中范围内起点位置在锚节点下第几个子节点的位置。例如，如果是值为 0 的话，整个节点都是被选中的。如果值为 1 的话，那么至少整个节点至少有一个子节点被选中。以此类推。

*`focusNode`*

焦点节点 - 选中内容的结尾节点

`focusOffset`

选中范围内结束位置在焦点节点下第几个子节点的位置。例如，如果是值为 0 的话，整个节点都是被选中的。如果值为 1 的话，那么至少整个节点至少有一个子节点被选中。以此类推。

**备注：** 如果源代码中`焦点节点`出现在`锚节点`之前的话，这两个将在参数中互换位置，也就是锚节点变为了焦点节点、焦点节点变为了锚节点。另外，这些参数的用法会颠倒 — 插入符是放置在文本的开头而不是结尾，这对于任何可能遵循这规则的键盘命令都是很重要的。例如，`Shift` + `➡` 会使选中状态范围的从开始缩小，而不是在结尾增加。

### 返回值

Void.

### 说明

如果`锚偏移值`超过了`锚节点`内部的子节点个数，或则如果`焦点偏移值`超过了`焦点节点`内部的子节点个数，这个`IndexSizeError` 选中会被丢弃。

示例
--

一个例子，我们有两个包含多个 span 的段落，每一个 span 包含一个单词。然后第一个段落作为`锚节`点并且第二个作为`焦点节点`.我们还有一个额外的段落插入在两个节点之间。

然后，这里有两个允许你去设置锚节点和焦点节点的表单输入框 — 它们都有一个默认值为 0.

这里还有一个按钮用来点击调用运行包含`setBaseAndExtent()`方法的函数，最后输出选中内容到 HTML 的最底部。

```html
<h1>setBaseAndExtent example</h1>
<div>
  <p class="one">
    <span>Fish</span><span>Dog</span><span>Cat</span><span>Bird</span>
  </p>
  <p>MIDDLE</p>
  <p class="two">
    <span>Car</span><span>Bike</span><span>Boat</span><span>Plane</span>
  </p>
</div>

<div>
  <p>
    <label for="aOffset">Anchor offset</label>
    <input id="aOffset" name="aOffset" type="number" value="0" />
  </p>
  <p>
    <label for="fOffset">Focus offset</label>
    <input id="fOffset" name="fOffset" type="number" value="0" />
  </p>
  <p><button>Capture selection</button></p>
</div>

<p><strong>Output</strong>: <span class="output"></span></p>
```

JavaScript 像这样：

```js
var one = document.querySelector(".one");
var two = document.querySelector(".two");

var aOffset = document.getElementById("aOffset");
var fOffset = document.getElementById("fOffset");

var button = document.querySelector("button");

var output = document.querySelector(".output");

var selection;

button.onclick = function () {
  try {
    selection = document.getSelection();
    selection.setBaseAndExtent(one, aOffset.value, two, fOffset.value);
    var text = selection.toString();
    output.textContent = text;
  } catch (e) {
    output.textContent = e.message;
  }
};
```

在下面在线运行实例，设置不同的偏移值去观察它怎么去影响选中内容的。

**备注：** 实例在这里 example on GitHub (see it live also.)

规范
--

| Specification |
| --- |
| Selection API # dom-selection-setbaseandextent |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`


Selection.modify() - Web API 接口参考

Selection.modify()
==================

**非标准:** 该特性是非标准的，请尽量不要在生产环境中使用它！

**`Selection.modify()`** 方法可以通过简单的文本命令来改变当前选区或光标位置。

语法
--
```

sel.modify(alter, direction, granularity)
```

### 参数

*alter*

改变类型。传入 `"move"` 来移动光标位置，或者 `"extend"` 来扩展当前选区。

*direction*

调整选区的方向。你可以传入 `"forward"` 或 `"backward"` 来根据选区内容的语言书写方向来调整。或者使用 `"left"` 或 `"right"` 来指明一个明确的调整方向。

*granularity*

调整的距离颗粒度。可选值有 `"character"`、`"word"`、`"sentence"`、`"line"`、`"paragraph"`、`"lineboundary"`、`"sentenceboundary"`、`"paragraphboundary"`、`"documentboundary"`。

**备注：** Gecko **不支持** `"sentence"`, `"paragraph"`, `"sentenceboundary"`, `"paragraphboundary"` 和 `"documentboundary"`. Webkit 和 Blink 支持。

**备注：** 从 Gecko 5.0 开始，不管是不是浏览器的默认行为，`"word"` 颗粒度不再包括单词后面的空格。这让这个行为变得更加稳定，这也和之前的 Webkit 保持一致，然而不幸的是他们最近修改了这个默认行为。

例子
--

使当前选区往语言书写方向扩大一个单词（word）的选择范围
```

var selection = window.getSelection();
selection.modify("extend", "forward", "word");
```

规范
--

*无。*

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

相关链接
----

* `Selection`，本方法所属的接口。


Selection.focusNode - Web API 接口参考

Selection.focusNode
===================

**实验性:** **这是一项实验性技术**
在将其用于生产之前，请仔细检查浏览器兼容性表格。

**`Selection.focusNode`** 是只读的，返回所选内容的结束位置部分所属的节点。

用户可能会从左到右 (按文字方向) 或从右到左 (按文字相反方向) 进行选择。focusNode 就是用户选择时最后停下来所处的元素节点。当你同时按下 shift 键和任何一个方向键来改变选择时，你就能看到：选择的结束位置在移动，但是选择的锚点 - 起始位置不会改变。

语法
--
```

node = sel.focusNode
```

规范
--

| Specification |
| --- |
| Selection API # dom-selection-focusnode |

浏览器兼容性
------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

参考
--

* `Selection`, Selection 接口规范。