Простые анимации - Интерфейсы веб API

Простые анимации
================

* « Предыдущая статья
* Следующая статья »

Поскольку для управления элементами `<canvas>` используется JavaScript, не составляет труда сделать (интерактивные) анимации. В этой главе мы рассмотрим, как делаются некоторые базовые анимации.

Вероятно, самым большим ограничением является то, что когда фигура нарисована, её уже нельзя двигать. Чтобы изобразить движение нам нужно перерисовать фигуру и всё, что было нарисовано до неё. Перерисовка сложных кадров занимает много времени, и производительность сильно зависит от скорости компьютера, на котором она выполняется.

Основные шаги анимации
----------------------

Ниже перечислены необходимые шаги для того, чтобы нарисовать кадр:

1. **Очистить canvas**
 Если фигура, которую вы собираетесь нарисовать, не занимает всю площадь canvas (как фон, например), то всё что было нарисовано ранее необходимо стереть. Проще всего это сделать при помощи метода `clearRect()`.
2. **Сохранить изначальное состояние canvas**
 Если вы изменяете любые настройки (такие как стили, трансформации и т.п.), которые затрагивают состояние canvas и вы хотите убедиться, что оригинальное состояние используется каждый раз, когда был отрисован кадр, то вам следует сохранить это оригинальное состояние.
3. **Нарисовать анимированные фигуры**
 Шаг на котором вы собственно отрисовываете кадр.
4. **Восстановить состояние canvas**
 Если вы сохраняли состояние, восстановите его, прежде чем отрисовывать новый кадр.
Управление анимацией
--------------------

Фигуры отрисовываются на canvas либо напрямую — при помощи методов canvas, либо с помощью сторонних функций. В нормальной ситуации результат станет виден на canvas после окончания выполнения скрипта. К примеру, цикл for использовать для анимации нельзя.

Это значит, нужен способ выполнения функций отрисовки через интервалы времени. Есть два способа для управления такой анимацией.

### Запланированные обновления

Первый — это функции `window.setInterval()` (en-US), `window.setTimeout()` (en-US), и `window.requestAnimationFrame()`, которые могут быть использованы для вызова некоторой функции, через заданный промежуток времени.

`setInterval(function, delay)` (en-US)

Начинает периодически исполнять функцию `function` каждые `delay` миллисекунд.

`setTimeout(function, delay)`

Запускает выполнение указанной функции `function` через `delay` миллисекунд.

`requestAnimationFrame(callback)`

Сообщает браузеру, что вы хотите выполнить анимацию, и запрашивает, чтобы браузер вызвал указанную функцию `callback` для обновления анимации перед следующей перерисовкой.

Если вы не планируете никакого взаимодействия с пользователем, вы можете использовать функцию `setInterval()` , которая многократно выполняет, предоставленный ей код. Если же вы планируете создать игру, в которой контроль анимации осуществляется мышью или клавиатурой, то необходимо использовать `setTimeout()`. Установив `EventListener` (en-US), вы можете перехватываете любые действия пользователя и запустить соответствующие функции анимации.

**Примечание:** В примерах ниже мы будем использовать функцию `window.requestAnimationFrame()` для контроля анимации. Функция `requestAnimationFrame` является более эффективной для создания анимации, так как новая итерация вызывается, когда система готова к отрисовке нового кадра. Количество вызовов в секунду примерно равно 60 и уменьшается, когда вкладка неактивна. Для более подробного изучения цикла анимации, особенно для игр, прочитайте статью Анатомия видеоигр В Зоне разработке игр.

Анимированная солнечная система
-------------------------------

В этом примере анимируется небольшая модель солнечной системы.

```js
var sun = new Image();
var moon = new Image();
var earth = new Image();
function init() {
  sun.src = "canvas\_sun.png";
  moon.src = "canvas\_moon.png";
  earth.src = "canvas\_earth.png";
  window.requestAnimationFrame(draw);
}

function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");

  ctx.globalCompositeOperation = "destination-over";
  ctx.clearRect(0, 0, 300, 300); // clear canvas

  ctx.fillStyle = "rgba(0,0,0,0.4)";
  ctx.strokeStyle = "rgba(0,153,255,0.4)";
  ctx.save();
  ctx.translate(150, 150);

  // Earth
  var time = new Date();
  ctx.rotate(
    ((2 \* Math.PI) / 60) \* time.getSeconds() +
      ((2 \* Math.PI) / 60000) \* time.getMilliseconds(),
  );
  ctx.translate(105, 0);
  ctx.fillRect(0, -12, 50, 24); // Shadow
  ctx.drawImage(earth, -12, -12);

  // Moon
  ctx.save();
  ctx.rotate(
    ((2 \* Math.PI) / 6) \* time.getSeconds() +
      ((2 \* Math.PI) / 6000) \* time.getMilliseconds(),
  );
  ctx.translate(0, 28.5);
  ctx.drawImage(moon, -3.5, -3.5);
  ctx.restore();

  ctx.restore();

  ctx.beginPath();
  ctx.arc(150, 150, 105, 0, Math.PI \* 2, false); // Earth orbit
  ctx.stroke();

  ctx.drawImage(sun, 0, 0, 300, 300);

  window.requestAnimationFrame(draw);
}

init();
```

```
<canvas id="canvas" width="300" height="300"></canvas>
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

Анимированные часы
------------------

В этом примере создаются анимированные часы, показывающие правильное время.

```js
function clock() {
  var now = new Date();
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.save();
  ctx.clearRect(0, 0, 150, 150);
  ctx.translate(75, 75);
  ctx.scale(0.4, 0.4);
  ctx.rotate(-Math.PI / 2);
  ctx.strokeStyle = "black";
  ctx.fillStyle = "white";
  ctx.lineWidth = 8;
  ctx.lineCap = "round";

  // Hour marks
  ctx.save();
  for (var i = 0; i < 12; i++) {
    ctx.beginPath();
    ctx.rotate(Math.PI / 6);
    ctx.moveTo(100, 0);
    ctx.lineTo(120, 0);
    ctx.stroke();
  }
  ctx.restore();

  // Minute marks
  ctx.save();
  ctx.lineWidth = 5;
  for (i = 0; i < 60; i++) {
    if (i % 5 != 0) {
      ctx.beginPath();
      ctx.moveTo(117, 0);
      ctx.lineTo(120, 0);
      ctx.stroke();
    }
    ctx.rotate(Math.PI / 30);
  }
  ctx.restore();

  var sec = now.getSeconds();
  var min = now.getMinutes();
  var hr = now.getHours();
  hr = hr >= 12 ? hr - 12 : hr;

  ctx.fillStyle = "black";

  // write Hours
  ctx.save();
  ctx.rotate(
    hr \* (Math.PI / 6) + (Math.PI / 360) \* min + (Math.PI / 21600) \* sec,
  );
  ctx.lineWidth = 14;
  ctx.beginPath();
  ctx.moveTo(-20, 0);
  ctx.lineTo(80, 0);
  ctx.stroke();
  ctx.restore();

  // write Minutes
  ctx.save();
  ctx.rotate((Math.PI / 30) \* min + (Math.PI / 1800) \* sec);
  ctx.lineWidth = 10;
  ctx.beginPath();
  ctx.moveTo(-28, 0);
  ctx.lineTo(112, 0);
  ctx.stroke();
  ctx.restore();

  // Write seconds
  ctx.save();
  ctx.rotate((sec \* Math.PI) / 30);
  ctx.strokeStyle = "#D40000";
  ctx.fillStyle = "#D40000";
  ctx.lineWidth = 6;
  ctx.beginPath();
  ctx.moveTo(-30, 0);
  ctx.lineTo(83, 0);
  ctx.stroke();
  ctx.beginPath();
  ctx.arc(0, 0, 10, 0, Math.PI \* 2, true);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(95, 0, 10, 0, Math.PI \* 2, true);
  ctx.stroke();
  ctx.fillStyle = "rgba(0,0,0,0)";
  ctx.arc(0, 0, 3, 0, Math.PI \* 2, true);
  ctx.fill();
  ctx.restore();

  ctx.beginPath();
  ctx.lineWidth = 14;
  ctx.strokeStyle = "#325FA2";
  ctx.arc(0, 0, 142, 0, Math.PI \* 2, true);
  ctx.stroke();

  ctx.restore();

  window.requestAnimationFrame(clock);
}

window.requestAnimationFrame(clock);
```

```
<canvas id="canvas" width="150" height="150"></canvas>
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

Зацикленная панорама
--------------------

В этом примере панорама прокручивается слева направо. Мы используем фото национального парка Йосемити взятое из Википедии, но вы можете использовать любое изображение, большее элемента canvas.

```js
var img = new Image();

// User Variables - customize these to change the image being scrolled, its
// direction, and the speed.

img.src = "capitan\_meadows,\_yosemite\_national\_park.jpg";
var CanvasXSize = 800;
var CanvasYSize = 200;
var speed = 30; //lower is faster
var scale = 1.05;
var y = -4.5; //vertical offset

// Main program

var dx = 0.75;
var imgW;
var imgH;
var x = 0;
var clearX;
var clearY;
var ctx;

img.onload = function () {
  imgW = img.width \* scale;
  imgH = img.height \* scale;
  if (imgW > CanvasXSize) {
    x = CanvasXSize - imgW;
  } // image larger than canvas
  if (imgW > CanvasXSize) {
    clearX = imgW;
  } // image larger than canvas
  else {
    clearX = CanvasXSize;
  }
  if (imgH > CanvasYSize) {
    clearY = imgH;
  } // image larger than canvas
  else {
    clearY = CanvasYSize;
  }
  //Get Canvas Element
  ctx = document.getElementById("canvas").getContext("2d");
  //Set Refresh Rate
  return setInterval(draw, speed);
};

function draw() {
  //Clear Canvas
  ctx.clearRect(0, 0, clearX, clearY);
  //If image is <= Canvas Size
  if (imgW <= CanvasXSize) {
    //reset, start from beginning
    if (x > CanvasXSize) {
      x = 0;
    }
    //draw aditional image
    if (x > CanvasXSize - imgW) {
      ctx.drawImage(img, x - CanvasXSize + 1, y, imgW, imgH);
    }
  }
  //If image is > Canvas Size
  else {
    //reset, start from beginning
    if (x > CanvasXSize) {
      x = CanvasXSize - imgW;
    }
    //draw aditional image
    if (x > CanvasXSize - imgW) {
      ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);
    }
  }
  //draw image
  ctx.drawImage(img, x, y, imgW, imgH);
  //amount to move
  x += dx;
}
```

Заметьте, что ширина и высота должны совпадать со значениями `CanvasXZSize` и `CanvasYSize`.

```html
<canvas id="canvas" width="800" height="200"></canvas>
```

Другие примеры
--------------

Advanced animations

Мы рассмотрим некоторые продвинутые методы анимации и физику в следующей главе.

* « Предыдущая статья
* Следующая статья »


Рисование фигур с помощью canvas - Интерфейсы веб API

Рисование фигур с помощью canvas
================================

* « Предыдущая статья
* Следующая статья »

Теперь, установив наше окружение canvas, мы можем погрузиться в детали того, как рисовать в canvas. К концу этой статьи, вы научитесь рисовать прямоугольники, треугольники, линии, дуги и кривые, при условии что вы хорошо знакомы с основными геометрическими фигурами. Работа с путями весьма важна, когда рисуете объекты на canvas и мы увидим как это может быть сделано.

Сетка
-----

![](/ru/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_default_grid.png)Перед тем, как мы начнём рисовать, нам нужно поговорить о сетке canvas или **координатной плоскости**. Наш HTML каркас из предыдущей страницы включал в себя элемент canvas 150 пикселей в ширину и 150 пикселей в высоту. Справа можно увидеть этот canvas с сеткой, накладываемой по умолчанию. Обычно 1 единица на сетке соответствует 1 пикселю на canvas. Начало координат этой сетки расположено *в верхнем левом углу* в координате `(0,0 )`. Все элементы размещены относительно этого начала. Таким образом, положение верхнего левого угла синего квадрата составляет `х` пикселей слева и `у` пикселей сверху, на координате `(х, у)`. Позже в этом уроке мы увидим, как можно перевести начало координат в другое место, вращать сетку и даже масштабировать её, но сейчас мы будем придерживаться настроек сетки по умолчанию.

Рисование прямоугольников
-------------------------

В отличие от SVG, `<canvas>` поддерживает только одну примитивную фигуру: прямоугольник. Все другие фигуры должны быть созданы комбинацией одного или большего количества контуров (paths), набором точек, соединённых в линии. К счастью в ассортименте рисования контуров у нас есть функции, которые делают возможным составление очень сложных фигур.

Сначала рассмотрим прямоугольник. Ниже представлены три функции рисования прямоугольников в canvas:

`fillRect(x, y, width, height)`

Рисование заполненного прямоугольника.

`strokeRect(x, y, width, height)`

Рисование прямоугольного контура.

`clearRect(x, y, width, height)`

Очистка прямоугольной области, делая содержимое совершенно прозрачным.

Каждая из приведённых функций принимает несколько параметров:

* *x*, *y* устанавливают положение верхнего левого угла прямоугольника в canvas (относительно начала координат);
* `width` (ширина) и `height` (высота) определяют размеры прямоугольника.

Ниже приведена функция draw(), использующая эти три функции.

### Пример создания прямоугольных фигур
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}
```

Этот пример изображён ниже.

| Screenshot | Live sample |
| --- | --- |
|  |  |

Функция fillRect() рисует большой чёрный квадрат со стороной 100 px. Функция clearRect() вырезает квадрат 60х60 из центра, а функция strokeRect() создаёт прямоугольный контур 50х50 пикселей внутри очищенного квадрата.

На следующей странице мы рассмотрим две альтернативы методу clearRect(), и также увидим, как можно изменять цвет и стиль контура отображаемых фигур.

В отличие от функций создания контуров, которые будут рассмотрены в следующем разделе, все три функции создания прямоугольника сразу же отображаются на canvas.

Рисование контуров (path)
-------------------------

Остальные примитивные фигуры создаются *контурами*. Контур - это набор точек, которые, соединяясь в отрезки линий, могут образовывать различные фигуры, изогнутые или нет, разной ширины и разного цвета. Контур (или субконтур) может быть закрытым.

Создание фигур используя контуры происходит в несколько важных шагов:

1. Сначала вы создаёте контур.
2. Затем, используя команды рисования, рисуете контур.
3. Потом закрываете контур.
4. Созданный контур вы можете обвести или залить для его отображения.

Здесь приведены функции, которые можно использовать в описанных шагах:

`beginPath()`

Создаёт новый контур. После создания используется в дальнейшем командами рисования при построении контуров.

Path методы

Методы для установки различных контуров объекта.

`closePath()`

Закрывает контур, так что будущие команды рисования вновь направлены контекст.

`stroke()`

Рисует фигуру с внешней обводкой.

`fill()`

Рисует фигуру с заливкой внутренней области.

Первый шаг создания контура заключается в вызове функции **`beginPath()`**. Внутри содержатся контуры в виде набора суб-контуров (линии, дуги и др.), которые вместе образуют форму фигуры. Каждый вызов этого метода очищает набор, и мы можем начинать рисовать новые фигуры.

**Примечание:** если текущий контур пуст (например, как после вызова `beginPath()` или на вновь созданном canvas), первой командой построения контура всегда является функция **`moveTo()`**. Поэтому мы всегда можем установить начальную позицию рисования контура после перезагрузки.

Вторым шагом является вызов методов, определяемых видом контура, который нужно нарисовать. Их мы рассмотрим позднее.

Третий и необязательный шаг - это вызов **`closePath()`**. Этот метод пытается закрыть фигуру, рисуя прямую линию из текущей точки в начальную. Если фигура была уже закрыта или является просто точкой, то функция ничего не делает.

**Примечание:** Когда вы вызываете **`fill()`**, то каждая открытая фигура закрывается автоматически, так что вы можете не использовать `closePath()`. Это обстоятельство не имеет место в случае вызова `stroke()`.

### Рисование треугольника

Например, код для рисования треугольника будет выглядеть как-то так:
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="100" height="100"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(75, 50);
    ctx.lineTo(100, 75);
    ctx.lineTo(100, 25);
    ctx.fill();
  }
}
```

Результат выглядит так:

| Screenshot | Live sample |
| --- | --- |
|  |  |

### Передвижение пера

Одна очень полезная функция, которая ничего не рисует, но связана по смыслу с вышеописанными функциями - это **`moveTo()`**. Вы можете представить это как отрыв (подъем) пера от бумаги и его перемещение в другое место.

`moveTo(x, y)`

Перемещает перо в точку с координатами x и y.

При инициализации canvas или при вызове `beginPath()`, вы захотите использовать функцию `moveTo()` для перемещения в точку начала рисования. Можно использовать `moveTo()` и для рисования несвязанного(незакрытого) контура. Посмотрите на смайлик ниже.

Вы можете проверить это сами, используя участок кода ниже. Просто вставьте в функцию `draw()`, рассмотренную ранее.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, Math.PI \* 2, true); // Внешняя окружность
    ctx.moveTo(110, 75);
    ctx.arc(75, 75, 35, 0, Math.PI, false); // рот (по часовой стрелке)
    ctx.moveTo(65, 65);
    ctx.arc(60, 65, 5, 0, Math.PI \* 2, true); // Левый глаз
    ctx.moveTo(95, 65);
    ctx.arc(90, 65, 5, 0, Math.PI \* 2, true); // Правый глаз
    ctx.stroke();
  }
}
```

Результат этого ниже:

| Screenshot | Live sample |
| --- | --- |
|  |  |

Если вы захотите увидеть соединение линии, то можете удалить вызов `moveTo()`.

**Примечание:** Подробнее о функции `arc()`,посмотрите Дуги .

### Линии

Для рисования прямых линий используйте метод `lineTo()`.

`lineTo(x, y)`

Рисует линию с текущей позиции до позиции, определённой `x и y`.

Этот метод принимает два аргумента `x и y`, которые являются координатами конечной точки линии. Начальная точка зависит от ранее нарисованных путей, причём конечная точка предыдущего пути является начальной точкой следующего и т. д. Начальная точка также может быть изменена с помощью метода `moveTo()`.

Пример ниже рисует два треугольника, один закрашенный и другой обведён контуром.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Filled triangle
    ctx.beginPath();
    ctx.moveTo(25, 25);
    ctx.lineTo(105, 25);
    ctx.lineTo(25, 105);
    ctx.fill();

    // Stroked triangle
    ctx.beginPath();
    ctx.moveTo(125, 125);
    ctx.lineTo(125, 45);
    ctx.lineTo(45, 125);
    ctx.closePath();
    ctx.stroke();
  }
}
```

Отрисовка начинается с вызова `beginPath()`, чтобы начать рисовать путь новой фигуры. Затем мы используем метод `moveTo()`, чтобы переместить начальную точку в нужное положение. Ниже рисуются две линии, которые образуют две стороны треугольника.

| Screenshot | Live sample |
| --- | --- |
|  |  |

Вы заметите разницу между закрашенным и обведённым контуром треугольниками. Это, как упоминалось выше, из-за того, что фигуры автоматически закрываются, когда путь заполнен (т. е. закрашен), но не тогда, когда он очерчен (т. е. обведён контуром). Если бы мы не учли `closePath()` для очерченного треугольника, тогда только две линии были бы нарисованы, а не весь треугольник.

### Дуги

Для рисования дуг и окружностей, используем методы arc() и arcTo().

`arc(x, y, radius, startAngle, endAngle, anticlockwise)`

Рисуем дугу с центром в точке `(x,y)` радиусом `radius`, начиная с угла `startAngle` и заканчивая в `endAngle` в направлении против часовой стрелки `anticlockwise` (по умолчанию по ходу движения часовой стрелки).

`arcTo(x1, y1, x2, y2, radius)`

Рисуем дугу с заданными контрольными точками и радиусом, соединяя эти точки прямой линией.

Рассмотрим детальнее метод *arc()*, который имеет пять параметров: *`x`* и *`y`* — это координаты центра окружности, в которой должна быть нарисована дуга. *`radius`* — не требует пояснений. Углы `startAngle` и `endAngle` определяют начальную и конечную точки дуги в радианах вдоль кривой окружности. Отсчёт происходит от оси x. Параметр `anticlockwise` — логическое значение, которое, если `true`, то рисование дуги совершается против хода часовой стрелки; иначе рисование происходит по ходу часовой стрелки.

**Примечание:** Углы в функции arc() измеряют в радианах, не в градусах. Для перевода градусов в радианы вы можете использовать JavaScript-выражение: `radians = (Math.PI/180)*degrees`.

Следующий пример немного сложнее, чем мы рассматривали ранее. Здесь нарисованы 12 различных дуг с разными углами и заливками.

Два `for` цикла размещают дуги по столбцам и строкам. Для каждой дуги, мы начинаем новый контур, вызывая *`beginPath()`*. В этом коде каждый параметр дуги для большей ясности задан в виде переменной, но вам не обязательно делать так в реальных проектах.

Координаты `x` и `y` должны быть достаточно ясны. `radius` and `startAngle` — фиксированы. `endAngle` начинается со 180 градусов (полуокружность) в первой колонке и, увеличиваясь с шагом 90 градусов, достигает кульминации полноценной окружностью в последнем столбце.

Установка параметра `clockwise` определяет результат; в первой и третьей строках рисование дуг происходит по часовой стрелке, а во второй и четвёртой - против часовой стрелки. Благодаря if-условию верхняя половина дуг образуется с контуром, (обводкой), а нижняя половина дуг - с заливкой.

**Примечание:** Этот пример требует немного большего холста (canvas), чем другие на этой странице: 150 x 200 pixels.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="200"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.beginPath();
        var x = 25 + j \* 50; // x coordinate
        var y = 25 + i \* 50; // y coordinate
        var radius = 20; // Arc radius
        var startAngle = 0; // Starting point on circle
        var endAngle = Math.PI + (Math.PI \* j) / 2; // End point on circle
        var anticlockwise = i % 2 == 0 ? false : true; // clockwise or anticlockwise

        ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

        if (i > 1) {
          ctx.fill();
        } else {
          ctx.stroke();
        }
      }
    }
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

### Безье и квадратичные кривые

Следующим типом доступных контуров являются кривые Безье, и к тому же доступны в кубическом и квадратичном вариантах. Обычно они используются при рисовании сложных составных фигур.

`quadraticCurveTo(cp1x, cp1y, x, y)` (en-US)

Рисуется квадратичная кривая Безье с текущей позиции пера в конечную точку с координатами `x` и `y`, используя контрольную точку с координатами `cp1x` и `cp1y`.

`bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)`

Рисуется кубическая кривая Безье с текущей позиции пера в конечную точку с координатами `x` и `y`, используя две контрольные точки с координатами (`cp1x`, `cp1y`) и (cp2x, cp2y).

![](/ru/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes/canvas_curves.png)Различие между ними можно увидеть на рисунке, изображённом справа. Квадратичная кривая Безье имеет стартовую и конечную точки (синие точки) и всего одну контрольную точку (красная точка), в то время как кубическая кривая Безье использует две контрольные точки.

Параметры `x` и `y` в этих двух методах являются координатами конечной точки. `cp1x` и `cp1y` — координаты первой контрольной точки, а `cp2x` и `cp2y` — координаты второй контрольной точки.

Использование квадратичных или кубических кривых Безье может быть спорным выходом, так как в отличие от приложений векторной графики типа Adobe Illustrator, мы не имеем полной видимой обратной связи с тем, что мы делаем. Этот факт делает довольно сложным процесс рисования сложных фигур. В следующем примере мы нарисуем совсем простую составную фигуру, но, если у вас есть время и ещё больше терпения, можно создать более сложные составные фигуры.

В этом примере нет ничего слишком тяжёлого. В обоих случаях мы видим последовательность кривых, рисуя которые, в результате получим составную фигуру.

#### Квадратичные кривые Безье

В этом примере многократно используются квадратичные кривые Безье для рисования речевой выноски.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Quadratric curves example
    ctx.beginPath();
    ctx.moveTo(75, 25);
    ctx.quadraticCurveTo(25, 25, 25, 62.5);
    ctx.quadraticCurveTo(25, 100, 50, 100);
    ctx.quadraticCurveTo(50, 120, 30, 125);
    ctx.quadraticCurveTo(60, 120, 65, 100);
    ctx.quadraticCurveTo(125, 100, 125, 62.5);
    ctx.quadraticCurveTo(125, 25, 75, 25);
    ctx.stroke();
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

#### Кубические кривые Безье

В этом примере нарисовано сердце с использованием кубических кривых Безье.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // Cubic curves example
    ctx.beginPath();
    ctx.moveTo(75, 40);
    ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
    ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
    ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
    ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
    ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
    ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
    ctx.fill();
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

### Прямоугольники

Все эти методы мы видели в Рисование прямоугольников, которые рисуют прямоугольники сразу в canvas, так же есть метод `rect()`, который не отображает, а только добавляет контур рисования (path) заданного прямоугольника к последнему открытому контуру.

`rect(x, y, width, height)`

Добавляет в path прямоугольник, верхний левый угол которого указан с помощью (x, y) с вашими width и height

Когда этот метод вызван, автоматически вызывается метод moveTo() с параметрами (x, y). Другими словами, позиция курсора устанавливается в начало добавленного прямоугольника.

### Создание комбинаций

 До сих пор, в каждом примере использовался только один тип функции контуров для каждой фигуры.
 Однако, нет никаких ограничений на количество или типы контуров, которые вы можете использовать для создания фигур. Давайте в этом примере объединим все вышеперечисленные функции контуров, чтобы создать набор очень известных игровых персонажей.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    roundedRect(ctx, 12, 12, 150, 150, 15);
    roundedRect(ctx, 19, 19, 150, 150, 9);
    roundedRect(ctx, 53, 53, 49, 33, 10);
    roundedRect(ctx, 53, 119, 49, 16, 6);
    roundedRect(ctx, 135, 53, 49, 33, 10);
    roundedRect(ctx, 135, 119, 25, 49, 10);

    ctx.beginPath();
    ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
    ctx.lineTo(31, 37);
    ctx.fill();

    for (var i = 0; i < 8; i++) {
      ctx.fillRect(51 + i \* 16, 35, 4, 4);
    }

    for (i = 0; i < 6; i++) {
      ctx.fillRect(115, 51 + i \* 16, 4, 4);
    }

    for (i = 0; i < 8; i++) {
      ctx.fillRect(51 + i \* 16, 99, 4, 4);
    }

    ctx.beginPath();
    ctx.moveTo(83, 116);
    ctx.lineTo(83, 102);
    ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
    ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
    ctx.lineTo(111, 116);
    ctx.lineTo(106.333, 111.333);
    ctx.lineTo(101.666, 116);
    ctx.lineTo(97, 111.333);
    ctx.lineTo(92.333, 116);
    ctx.lineTo(87.666, 111.333);
    ctx.lineTo(83, 116);
    ctx.fill();

    ctx.fillStyle = "white";
    ctx.beginPath();
    ctx.moveTo(91, 96);
    ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
    ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
    ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
    ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
    ctx.moveTo(103, 96);
    ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
    ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
    ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
    ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
    ctx.fill();

    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.arc(101, 102, 2, 0, Math.PI \* 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.arc(89, 102, 2, 0, Math.PI \* 2, true);
    ctx.fill();
  }
}

// A utility function to draw a rectangle with rounded corners.

function roundedRect(ctx, x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x, y + radius);
  ctx.arcTo(x, y + height, x + radius, y + height, radius);
  ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
  ctx.arcTo(x + width, y, x + width - radius, y, radius);
  ctx.arcTo(x, y, x, y + radius, radius);
  ctx.stroke();
}
```

Конечное изображение выглядит так:

| Screenshot | Live sample |
| --- | --- |
|  |  |

Мы не будем подробно останавливаться на том, так как это на самом деле удивительно просто. Наиболее важные вещи, которые следует отметить, это использование свойства `fillStyle` в контексте рисования и использование функции утилиты (в данном случае `roundedRect()`). Использование функций утилиты для битов чертежа часто может быть очень полезным и сократить количество необходимого кода, а также его сложность.

Позже, в этом уроке, мы ещё раз рассмотрим `fillStyle`, но более подробно. Здесь же мы используем его для изменения цвета заливки путей вместо цвета по умолчанию от чёрного до белого, а затем обратно.

Path2D объекты
--------------

 Как мы видели в последнем примере, есть серия путей и команд для рисования объектов на вашем холсте. Чтобы упростить код и повысить производительность, объект `Path2D` (en-US), доступный в последних версиях браузеров, позволяет вам кешировать или записывать эти команды рисования. Вы можете быстро запускать свои пути.
 Давайте посмотрим, как мы можем построить объект `Path2D` :

`Path2D()` (en-US)

Конструктор **`Path2D()`** возвращает вновь созданный объект `Path2D` необязательно с другим путём в качестве аргумента (создаёт копию) или необязательно со строкой, состоящей из данных пути SVG path .

```js
new Path2D(); // пустой path объект
new Path2D(path); // копирование из другого path
new Path2D(d); // path из SVG
```

Все методы path , такие как `moveTo`, `rect`, `arc`, или `quadraticCurveTo`, и т.п, которые мы уже знаем, доступны для объектов `Path2D`

API `Path2D` также добавляет способ комбинирования путей с использованием метода `addPath`. Это может быть полезно, если вы хотите, например, создавать объекты из нескольких компонентов.

`Path2D.addPath(path [, transform])` (en-US)

Добавляет путь к текущему пути с необязательной матрицей преобразования.

### Path2D пример

В этом примере мы создаём прямоугольник и круг. Оба они сохраняются как объект `Path2D`, поэтому они доступны для последующего использования. С новым API `Path2D` несколько методов были обновлены, чтобы при необходимости принять объект `Path2D` для использования вместо текущего пути. Здесь `stroke` и `fill` используются с аргументом пути, например, для рисования обоих объектов на холст.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="130" height="100"></canvas>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    var rectangle = new Path2D();
    rectangle.rect(10, 10, 50, 50);

    var circle = new Path2D();
    circle.moveTo(125, 35);
    circle.arc(100, 35, 25, 0, 2 \* Math.PI);

    ctx.stroke(rectangle);
    ctx.fill(circle);
  }
}
```

| Screenshot | Live sample |
| --- | --- |
|  |  |

### Использование SVG путей

Ещё одна мощная функция нового Canvas `Path2D` API использует данные пути SVG, SVG path data, для инициализации путей на вашем холсте. Это может позволить вам передавать данные пути и повторно использовать их как в SVG, так и в холсте.

 Путь перемещается в точку (`M10 10`), а затем горизонтально перемещается на 80 пунктов вправо (`h 80`), затем на 80 пунктов вниз (`v 80`), затем на 80 пунктов влево (`h -80`), а затем обратно на start (`z`).
 Этот пример можно увидеть на странице `Path2D` constructor (en-US).

```js
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
```

* « Предыдущая статья
* Следующая статья »


Пиксельная манипуляция с холстом - Интерфейсы веб API

Пиксельная манипуляция с холстом
================================

* « Предыдущая статья
* Следующая статья »

До сих пор мы не смотрели на фактические пиксели нашего объекта canvas (далее "холст"). С объектом `ImageData` вы можете напрямую читать и писать массив данных для управления пиксельными данными. Мы также рассмотрим, как можно сгладить сглаживание изображения (сглаживание) и как сохранить изображения с вашего холста.

Объект `ImageData`
------------------

Объект `ImageData` представляет базовые пиксельные данные области объекта холста. Он содержит следующие атрибуты только для чтения:

`width`

Ширина изображения в пикселях.

`height`

Высота изображения в пикселях.

`data`

A `Uint8ClampedArray` представляет собой одномерный массив, содержащий данные в порядке RGBA, с целыми значениями от `0` до `255` (в комплекте).

Свойство `data` возвращает `Uint8ClampedArray`, к которому можно получить доступ, чтобы посмотреть на необработанные пиксельные данные; каждый пиксель представлен четырьмя однобайтовыми значениями (красный, зелёный, синий и альфа в этом порядке, то есть формат «RGBA»). Каждый компонент цвета представлен целым числом от 0 до 255. Каждому компоненту присваивается последовательный индекс внутри массива, причём красный компонент верхнего левого пикселя находится в индексе 0 внутри массива. Затем пиксели идут слева направо, затем вниз, по всему массиву.

`Uint8ClampedArray` содержит высоту × ширину × 4 байта данных, значения индекса варьируются от 0 до (высота × ширина × 4) -1.

Например, чтобы прочитать значение синего компонента из пикселя в столбце 200, строка 50 на изображении, вы должны сделать следующее:

```js
blueComponent = imageData.data[50 \* (imageData.width \* 4) + 200 \* 4 + 2];
```

Вы можете получить доступ к размеру массива пикселей в байтах, прочитав атрибут `Uint8ClampedArray.length`:

```js
var numBytes = imageData.data.length;
```

Создание объекта `ImageData`
----------------------------

Чтобы создать новый пустой объект `ImageData` , вы должны использовать метод `createImageData ()` (en-US). Существуют две версии метода `createImageData()` :

```js
var myImageData = ctx.createImageData(width, height);
```

Это создаёт новый объект `ImageData` с указанными параметрами. Все пиксели заданы прозрачным черным.

Вы также можете создать новый объект `ImageData` ImageData с теми же размерами, что и объект, заданный `anotherImageData` . Все пиксели нового объекта установлены на прозрачный чёрный. **Это не копирует данные изображения!**

```js
var myImageData = ctx.createImageData(anotherImageData);
```

Получение пиксельных данных для контекста
-----------------------------------------

Чтобы получить объект `ImageData` , содержащий копию пиксельных данных для контекста холста, вы можете использовать метод `getImageData()` :

```js
var myImageData = ctx.getImageData(left, top, width, height);
```

Этот метод возвращает объект `ImageData` , представляющий пиксельные данные для области холста, углы которого представлены точками (`left` , `top`), (`left+width` , `top`), (`left` , `top+height`) и (`left+width` , `top+height`). Координаты задаются в единицах пространства координат холста.

**Примечание:** Любые пиксели за пределами холста возвращаются как прозрачный чёрный цвет в результирующий объект `ImageData` .

Этот метод также показан в статье Manipulating video using canvas (en-US).

### Выбор цвета

В этом примере мы используем метод getImageData() для отображения цвета под курсором мыши. Для этого нам нужна текущая позиция мыши с `layerX` и `layerY`, затем мы просматриваем пиксельные данные в этой позиции в массиве пикселей, который предоставляет нам getImageData(). Наконец, мы используем данные массива для установки цвета фона и текста `<div>` для отображения цвета.
```

<canvas id="canvas" width="300" height="227" style="float:left"></canvas>
<div id="color" style="width:200px;height:50px;float:left"></div>
```

```js
var img = new Image();
img.src = "rhino.jpg";
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
img.onload = function () {
  ctx.drawImage(img, 0, 0);
  img.style.display = "none";
};
var color = document.getElementById("color");
function pick(event) {
  var x = event.layerX;
  var y = event.layerY;
  var pixel = ctx.getImageData(x, y, 1, 1);
  var data = pixel.data;
  var rgba =
    "rgba(" +
    data[0] +
    ", " +
    data[1] +
    ", " +
    data[2] +
    ", " +
    data[3] / 255 +
    ")";
  color.style.background = rgba;
  color.textContent = rgba;
}
canvas.addEventListener("mousemove", pick);
```

Отображение пиксельных данных в контекст
----------------------------------------

Вы можете использовать метод putImageData() для рисования пиксельных данных в контексте:

```js
ctx.putImageData(myImageData, dx, dy);
```

Параметры `dx` и `dy` указывают координаты устройства в контексте, в котором будет отображаться верхний левый угол пиксельных данных, которые вы хотите нарисовать.

Например, чтобы нарисовать все изображение, представленное `myImageData`, в верхнем левом углу контекста, вы можете просто сделать следующее:

```js
ctx.putImageData(myImageData, 0, 0);
```

### Оттенки серого цвета и инвертирование цветов

В этом примере мы перебираем все пиксели для изменения их значений, а затем помещаем модифицированный массив пикселей обратно в canvas с помощью putImageData(). Функция инвертирования просто вычитает каждый цвет из максимального значения 255. Функция оттенков серого просто использует среднее значение красного, зелёного и синего. Вы также можете использовать средневзвешенное значение, заданное формулой `x = 0.299r + 0.587g + 0.114b`, например. Для дополнительной информации см. Grayscale в Википедии.
```

<canvas id="canvas" width="300" height="227"></canvas>
<div>
  <input id="grayscalebtn" value="Grayscale" type="button" />
  <input id="invertbtn" value="Invert" type="button" />
</div>
```

```js
var img = new Image();
img.src = "rhino.jpg";
img.onload = function () {
  draw(this);
};

function draw(img) {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  img.style.display = "none";
  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  var data = imageData.data;

  var invert = function () {
    for (var i = 0; i < data.length; i += 4) {
      data[i] = 255 - data[i]; // red
      data[i + 1] = 255 - data[i + 1]; // green
      data[i + 2] = 255 - data[i + 2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var grayscale = function () {
    for (var i = 0; i < data.length; i += 4) {
      var avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
      data[i] = avg; // red
      data[i + 1] = avg; // green
      data[i + 2] = avg; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

  var invertbtn = document.getElementById("invertbtn");
  invertbtn.addEventListener("click", invert);
  var grayscalebtn = document.getElementById("grayscalebtn");
  grayscalebtn.addEventListener("click", grayscale);
}
```

Масштабирование и сглаживание
-----------------------------

С помощью метода `drawImage ()`, второго холста и свойства `imageSmoothingEnabled` (en-US) мы способны увеличить изображение и посмотреть его более детально.

Мы получаем положение мыши и обрезаем изображение на 5 пикселей левее и выше и на 5 пикселей правее и ниже положения мыши. Затем мы копируем его на другой холст и изменяем размер изображения до размера, который мы хотим. При масштабировании мы изменяем холст с исходного размера 10×10 пикселей до 200×200.

```js
zoomctx.drawImage(
  canvas,
  Math.abs(x - 5),
  Math.abs(y - 5),
  10,
  10,
  0,
  0,
  200,
  200,
);
```

Поскольку по умолчанию включено сглаживание, мы можем захотеть отключить сглаживание, чтобы увидеть чёткие пиксели. Вы можете переключить флажок, чтобы увидеть эффект свойства `imageSmoothingEnabled` (которому нужны префиксы для разных браузеров).
```

<canvas id="canvas" width="300" height="227"></canvas>
<canvas id="zoom" width="300" height="227"></canvas>
<div>
  <label for="smoothbtn">
    <input type="checkbox" name="smoothbtn" checked="checked" id="smoothbtn" />
    Enable image smoothing
  </label>
</div>
```

```js
var img = new Image();
img.src = "rhino.jpg";
img.onload = function () {
  draw(this);
};

function draw(img) {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  img.style.display = "none";
  var zoomctx = document.getElementById("zoom").getContext("2d");

  var smoothbtn = document.getElementById("smoothbtn");
  var toggleSmoothing = function (event) {
    zoomctx.imageSmoothingEnabled = this.checked;
    zoomctx.mozImageSmoothingEnabled = this.checked;
    zoomctx.webkitImageSmoothingEnabled = this.checked;
    zoomctx.msImageSmoothingEnabled = this.checked;
  };
  smoothbtn.addEventListener("change", toggleSmoothing);

  var zoom = function (event) {
    var x = event.layerX;
    var y = event.layerY;
    zoomctx.drawImage(
      canvas,
      Math.abs(x - 5),
      Math.abs(y - 5),
      10,
      10,
      0,
      0,
      200,
      200,
    );
  };

  canvas.addEventListener("mousemove", zoom);
}
```

Сохранение изображений
----------------------

`HTMLCanvasElement` предоставляет метод `toDataURL()`, который полезен при сохранении изображений. Он возвращает data URI (en-US), содержащий представление изображения в формате, заданном параметром `type` (по умолчанию используется в PNG ). Возвращаемое изображение имеет разрешение 96 точек на дюйм.

**Примечание:**

Имейте в виду, что если холст содержит пиксели, полученные из другого origin без использования CORS, холст будет **испорчен**, и его содержимое больше не будет считываться и сохраняться. Смотрите Безопасность и испорченные холсты

`canvas.toDataURL('image/png')`

Настройки по умолчанию. Создаёт изображение в формате PNG.

`canvas.toDataURL('image/jpeg', quality)`

Создаёт изображение в формате JPG. Дополнительно вы можете задать параметр "качество" (quality) в диапазоне от 0 до 1, причём единица задаёт лучшее качество и 0 - почти не распознаваемый, но небольшой по размеру файл.

После того как вы создали URI данные из своего холста, вы можете использовать его как источник любого `<image>` или поместить его в гиперссылку с download attribute, чтобы сохранить его на диске, например.

Вы также можете создать `Blob` из холста.

`canvas.toBlob(callback, type, encoderOptions)`

Создаёт объект `Blob`, представляющий изображение, содержащееся в холсте.

Смотрите также
--------------

* `ImageData`
* Manipulating video using canvas (en-US)
* Canvas, images and pixels – by Christian Heilmann
* « Предыдущая статья
* Следующая статья »


Использование изображений - Интерфейсы веб API

Использование изображений
=========================

* « Предыдущая статья
* Следующая статья »

До сих пор мы создавали наши собственные фигуры и применяли стили к ним. Одна из самых впечатляющих функций `<canvas>` это возможность использования изображений. Они могут быть использованы для динамического композитинга фото или как фоны графиков, для спрайтов в играх, и так далее. Внешние изображения могут быть использованы в любых поддерживаемых браузером форматах, таких как PNG, GIF, или JPEG. Вы можете даже использовать изображение, произведённое другими canvas элементами на той же странице как источник!

Импортирование изображений в canvas в основном состоит из 2 этапов:

1. Дав ссылку на `HTMLImageElement` объект или для другого canvas элемента как источник. Также можно использовать изображение дав ссылку на URL.
2. Для рисования изображения на canvas используется функция `drawImage()`.

Давайте посмотрим как это сделать.

Использование изображений для рисования
---------------------------------------

Canvas API может использовать все перечисленные далее типы данных как источник изображения:

`HTMLImageElement`

Эти изображения созданы, используя конструктор `Image()`, также как все`<img>` элементы.

`HTMLVideoElement`

Используя HTML `<video>` элемент как источник изображения захватывает текущий кадр из видео и использует его как изображение.

`HTMLCanvasElement`

Вы можете использовать другой `<canvas>` элемент как источник изображения.

Эти источники совместно именуемые по типу `CanvasImageSource` (en-US).

Есть несколько способов, чтобы получить изображения для использования на холсте.

### Использование изображений из той же страницы

Мы можем получить ссылку на изображение, на той же странице, на canvas с используя один из способов:

* `document.images` коллекция
* The `document.getElementsByTagName()` метод
* Если вы знаете id конкретного изображения, который вы хотите использовать, вы можете использовать `document.getElementById ()`, чтобы получить это конкретное изображение
### Использование изображений из других доменов

Использование `crossorigin` атрибута `<img>` элемент (отображается `HTMLImageElement.crossOrigin` (en-US) свойства), вы можете запросить разрешение на загрузку другого домена для использования в `drawImage()`. Если хостинг домен разрешает доступ к междоменному изображению, то изображение может быть использовано в вашем canvas без without tainting it;иначе он может испортить ваш canvas.

### Использование других canvas элементов

Как и с обычными изображениями, мы можем получить доступ к другим canvas элементам используя либо `document.getElementsByTagName()` либо `document.getElementById()` метод. Проверьте, что в canvas источнике уже что-то нарисовано, прежде чем использовать его в целевом изображении canvas.

Одним из удобных способов было бы использование второго элемента canvas в качестве миниатюры другого большего изображения canvas.

### Создание изображений с нуля

Другой способ это создать новые `HTMLImageElement` объекты в нашем скрипте. Чтобы это сделать, вы можете использовать удобный `Image()` конструктор:

```js
var img = new Image(); // Создаёт новый элемент изображения
img.src = "myImage.png"; // Устанавливает путь
```

Когда этот скрипт выполнится, изображение начнёт загружаться.

Если вы попытаетесь вызвать функцию `drawImage()` перед тем как изображение загрузится, то скрипт ничего не сделает (или, в старых браузерах, может даже выдать исключение). Поэтому вам необходимо использовать событие load, чтобы вы не пытались сделать это прежде, чем изображение загрузится:

```js
var img = new Image(); // Создаёт новое изображение
img.addEventListener(
  "load",
  function () {
    // здесь выполняет drawImage функцию
  },
  false,
);
img.src = "myImage.png"; // Устанавливает источник файла
```

Если вы используете только одно стороннее изображение, то этот метод может быть хорошим примером, но если нужно следить за несколькими изображениями, то необходимо придумать что-то более умное. Хотя поиски тактики проверки загрузки изображений выходят за пределы этого обучающего курса, вы должны об этом помнить.

### Вложение изображения с помощью данных: URL

Другой возможный способ включить изображение это через data: url (en-US). Data URLs позволяет вам полностью определить изображение как Base64 кодированную строку символов прямо в ваш код.

```js
var img = new Image(); // Создаёт новый элемент img
img.src =
  "data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==";
```

Одним из преимуществ data URLs это то что полученное изображение доступно сразу без других запросов туда-обратно на сервер. Другое потенциальное преимущество в том, что также можно инкапсулировать всё в одном файле все ваши CSS, JavaScript, HTML, и изображения, что делает его более портативным в других местах.

Некоторые недостатки этого метода в том что ваше изображение не кешировано, и для изображений с большим размером кодирование url может стать очень долгим процессом.

### Использование кадров из видео

Вы также можете использовать кадры из видео представленных `<video>` элементом (даже если видео не видно). Например, если у вас есть `<video>` элемент с ID "myvideo", вы можете сделать:

```js
function getMyVideo() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    return document.getElementById("myvideo");
  }
}
```

Эта функция вернёт `HTMLVideoElement` объект для этого видео, который, как мы упоминали ранее, является одним из объектов, который можно использовать как `CanvasImageSource`.

Рисование изображений
---------------------

Как только мы получили ссылку на источник объекта изображения, мы можем использовать метод `drawImage()` для включения его в canvas. Как мы увидим далее, метод `drawImage()` перегружен и у него есть несколько вариантов. В базовом варианте он выглядит как:

`drawImage(image, x, y)`

Рисует изображение, указанное в `CanvasImageSource` в координатах (`x`, `y`).

**Примечание:** SVG изображения должны указывать ширину и высоту корневого <svg> элемента.

### Пример: Простой линейный график

В следующем примере, мы будем использовать внешнее изображение в качестве фона для небольшого линейного графика. Использование фонов может сделать ваш скрипт значительно меньше, потому что мы можем избежать необходимости писать код для создания фона. В этом примере мы используем только один образ, поэтому я использую обработчик событий изображения объекта загрузки для выполнения операторов рисования. `drawImage()` метод определяющий место фона с координатами (0, 0), которые привязаны к верхнему левому углу canvas.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="180" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var img = new Image();
  img.onload = function () {
    ctx.drawImage(img, 0, 0);
    ctx.beginPath();
    ctx.moveTo(30, 96);
    ctx.lineTo(70, 66);
    ctx.lineTo(103, 76);
    ctx.lineTo(170, 15);
    ctx.stroke();
  };
  img.src = "backdrop.png";
}
```

Получившийся график выглядит так:

| Screenshot | Live sample |
| --- | --- |
|  |  |

Изменение размеров
------------------

Второй вариант метода `drawImage()` добавляет два новых параметра и позволяет разместить изображение в canvas с изменёнными размерами.

`drawImage(image, x, y, width, height)`

Это добавляет параметр ширины и высоты, которые указывают до какого размера нужно изменить изображение при рисовании его в canvas.

### Пример: Тайлинг изображения

В этом примере, мы будем использовать изображение в качестве обоев и повторим его в canvas несколько раз. Это может быть сделано просто через цикл, располагая изменённые изображения на разных позициях. В коде внизу, первый цикл `for` проходит по рядам. Второй цикл `for` проходит по колонкам. Изображение уменьшено на треть от реального размера, которое было 50x38 пикселей.

**Примечание:** Изображения могут стать размытыми, при большом увеличении или зернистыми при значительном уменьшении. Возможно, лучше всего не изменять размеры изображения, если на них есть текст, который должен остаться читаемым.
```

<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var img = new Image();
  img.onload = function () {
    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.drawImage(img, j \* 50, i \* 38, 50, 38);
      }
    }
  };
  img.src = "rhino.jpg";
}
```

Получившийся рисунок canvas выглядит так:

| Screenshot | Live sample |
| --- | --- |
|  |  |

Нарезка
-------

У третьего и последнего варианта метода `drawImage()` в дополнении к источнику изображения есть ещё восемь параметров . Он позволяет нам вырезать кусок из изображения, затем изменить его размер и нарисовать его в canvas.

`drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)`

В данном изображении, эта функция берёт фрагмент из изображения, в виде прямоугольника, левый верхний угол которого - (`sx`, `sy`), ширина и высота - `sWidth` и `sHeight` и рисует в canvas, располагая его в точке (`dx`, `dy`) и изменяя его размер на указанные величины в `dWidth` и `dHeight`.

![](/ru/docs/Web/API/Canvas_API/Tutorial/Using_images/canvas_drawimage.jpg)Чтобы понять что делает нарезка, можно посмотреть на изображение справа. Первые четыре параметра определяют местоположение и размер фрагмента исходного изображения. Последние четыре параметра определяют прямоугольник, в который будет вписано изображение на целевом рисунке canvas.

Нарезка может быть полезным инструментом, когда вы захотите сделать композицию. Вы могли бы собрать все элементы в одном файле изображения и использовать этот метод для создания композиции. Например, если вы захотите сделать график, вы могли бы сделать PNG изображение, содержащее все необходимые тексты в одном файле и в зависимости от ваших данных, могли бы достаточно просто изменять график. Другим преимуществом является то, что нет необходимости загружать каждое изображение по отдельности, получив возможность увеличить скорость загрузки.

### Пример: Обрамление изображения

В этом примере, мы будем использовать того же носорога, что и в предыдущем примере, но мы отрежем его голову и включим её в рамку. Изображение рамки это 24-х битный PNG, который включает падающую тень. Так как в 24-х битные PNG изображения включается полный 8-ми битный альфа-канал, в отличие от GIF и 8-битных PNG изображений, он может быть помещён в любой фон, без беспокойства о матовом цвете.

```html
<html>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
    <div style="display:none;">
      <img id="source" src="rhino.jpg" width="300" height="227" />
      <img id="frame" src="canvas\_picture\_frame.png" width="132" height="150" />
    </div>
  </body>
</html>
```

```js
function draw() {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  // Рисуем фрагмент
  ctx.drawImage(
    document.getElementById("source"),
    33,
    71,
    104,
    124,
    21,
    20,
    87,
    104,
  );

  // Рисуем рамку
  ctx.drawImage(document.getElementById("frame"), 0, 0);
}
```

В этот раз мы применили другой способ загрузки изображения. Вместо загрузки методом создания новых `HTMLImageElement` объектов, мы включили их как `<img>` тэги прямо в наш HTML файл и из них выбрали изображения. Изображения скрыты с помощью CSS-свойства `display`, установленного в "none" для этих изображений.

| Screenshot | Live sample |
| --- | --- |
|  |  |

Скрипт, сам по себе, очень простой. Каждому `<img>` присвоен атрибут ID, который делает удобным их выбор с использованием `document.getElementById()`. Потом мы просто используем функцию `drawImage()`, чтобы из первого изображения вырезать фрагмент носорога и вставить его в canvas, затем рисуем рамку сверху, используя второй вызов функции `drawImage()`.

Пример галереи искусства
------------------------

В последнем примере этой главы, мы построим небольшую галерею искусств. Галерея состоит из таблицы, включающей несколько изображений. Когда страница загрузится, `<canvas>` элемент вставится в каждое изображение, а вокруг будет нарисована рамка.

В этом случае, у каждого изображения фиксированная ширина и высота, такая же, как и у рамки нарисованной вокруг них. Вы могли бы усовершенствовать этот скрипт так, чтобы он использовал ширину и высоту изображения, чтобы рамка идеально его окружила.

Код ниже должен говорить сам за себя. Мы проходим циклом через `document.images` контейнер и соответственно добавляем новые элементы canvas. Возможно следует упомянуть для тех, кто не слишком хорошо знаком с DOM, что для этого используется `Node.insertBefore` метод. `insertBefore()` это метод родительского узла (ячейки таблицы) элемента (изображения) перед которым мы хотим вставить наш новый узел (элемент canvas).

```html
<html>
  <body onload="draw();">
    <table>
      <tr>
        <td><img src="gallery\_1.jpg" /></td>
        <td><img src="gallery\_2.jpg" /></td>
        <td><img src="gallery\_3.jpg" /></td>
        <td><img src="gallery\_4.jpg" /></td>
      </tr>
      <tr>
        <td><img src="gallery\_5.jpg" /></td>
        <td><img src="gallery\_6.jpg" /></td>
        <td><img src="gallery\_7.jpg" /></td>
        <td><img src="gallery\_8.jpg" /></td>
      </tr>
    </table>
    <img id="frame" src="canvas\_picture\_frame.png" width="132" height="150" />
  </body>
</html>
```

И сюда какую-нибудь CSS для украшения:

```css
body {
  background: 0 -100px repeat-x url(bg\_gallery.png) #4f191a;
  margin: 10px;
}

img {
  display: none;
}

table {
  margin: 0 auto;
}

td {
  padding: 15px;
}
```

Связывая все вместе JavaScript рисует наши изображения в рамках:

```js
function draw() {
  // Цикл по всем изображениям
  for (var i = 0; i < document.images.length; i++) {
    // Не добавляет canvas для изображения рамки
    if (document.images[i].getAttribute("id") != "frame") {
      // Создаёт элемент canvas
      var canvas = document.createElement("canvas");
      canvas.setAttribute("width", 132);
      canvas.setAttribute("height", 150);

      // Вставляет перед изображением
      document.images[i].parentNode.insertBefore(canvas, document.images[i]);

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

      // Рисует изображение в canvas
      ctx.drawImage(document.images[i], 15, 20);

      // Добавляет рамку
      ctx.drawImage(document.getElementById("frame"), 0, 0);
    }
  }
}
```

Контроль изменений размеров изображения
---------------------------------------

Как было отмечено ранее, изменение размеров изображений может привести к размытости или к шуму в процессе преобразования. Вы можете использовать контекст рисования `imageSmoothingEnabled` (en-US) свойства, чтобы контролировать использование сглаживающего алгоритма, когда изменяющиеся изображения в вашем контексте. Обычно это свойство установлено в `true`, означая, что изображения будут сглажены во время изменения размеров. Вы можете отключить это свойство так:

```js
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
```

* « Предыдущая статья
* Следующая статья »


DOMTokenList.replace() - Интерфейсы веб API

DOMTokenList.replace()
======================

Метод **`replace()`** интерфейса `DOMTokenList` заменяет существующий класс на новый класс. Если первый не существует, то `replace()` сразу же возвращает `false`, без добавления нового класса в список классов.

Синтаксис
---------
```

tokenList.replace(oldToken, newToken);
```

### Параметры

`oldToken`

`DOMString` - класс, который вы хотите заменить.

`newToken`

`DOMString` класс, которым вы хотите заменить класс `oldToken`.

### Возвращает значение

Boolean, которое будет `true`, если `oldToken` был успешно заменён, или `false` в противном случае.

**Примечание:** В старых браузерах `replace()` возвращает void.

Примеры
-------

В следующем примере мы получаем список классов, установленных в элементе `<span>` как `DOMTokenList`, используя `Element.classList`. Затем мы заменяем класс в списке и записываем список в `Node.textContent`, принадлежащий `<span>`.

В HTML:

```html
<span class="a b c"></span>
```

В JavaScript:

```js
let span = document.querySelector("span");
let classes = span.classList;

let result = classes.replace("c", "z");
console.log(result);

if (result) {
  span.textContent = classes;
} else {
  span.textContent = "token not replaced successfully";
}
```

Результат выглядит так:

Полифил
-------

Следующий полифил добавит метод replace в класс `DOMTokenList`. Следующий код будет работать только с **IE10-11**. Чтобы использовать с более ранними версиями IE, обратитесь к полифилу по `element.classList#Polyfill`

```js
DOMTokenList.prototype.replace = function (a, b) {
  if (this.contains(a)) {
    this.add(b);
    this.remove(a);
    return true;
  }
  return false;
};
```

Спецификации
------------

| Specification |
| --- |
| DOM Standard # ref-for-dom-domtokenlist-replace① |

Совместимость с браузерами
--------------------------

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


Использование Geolocation API - Интерфейсы веб API

Использование Geolocation API
=============================

**Secure context:** This feature is available only in secure contexts (en-US) (HTTPS), in some or all supporting browsers.

Geolocation API позволяет пользователю предоставлять своё местоположение web-приложению, если пользователь согласится предоставить его. Из соображений конфиденциальности, у пользователя будет запрошено разрешение на предоставление информации о местоположении.

Объект геолокации
-----------------

API геолокации доступен через объект `navigator.geolocation`.

Если объект существует, функции определения местоположения доступны. Вы можете проверить это следующим образом:

```js
if ("geolocation" in navigator) {
  /\* местоположение доступно \*/
} else {
  /\* местоположение НЕ доступно \*/
}
```

### Получение текущего местоположения

Чтобы получить текущее местоположение пользователя, вы должны вызвать метод `getCurrentPosition()`. Это инициирует асинхронный запрос для обнаружения местоположения пользователя, и запрашивает аппаратные средства позиционирования, чтобы получить последнюю актуальную информацию. Когда местоположение определено, выполняется callback. По желанию вы можете указать вторую callback функцию для обработки ошибки, которая запустится в случае ошибки. Третий, опциональный параметр - объект с опциями, где вы можете настроить максимальное значение возвращаемых данных, время ожидания ответа на запрос, и, при желании, точность возвращаемых данных.

**Примечание:** По умолчанию `getCurrentPosition()` пытается вернуть результат так быстро, как это возможно, за счёт чего даёт не очень точный результат. Это может быть полезно, если вам нужно быстро получить ответ, при этом не важна точность. Устройства с GPS, например, могут пытаться скорректировать данные GPS около минуты и даже больше, поэтому в самом начале могут вернуться менее точные данные (местоположение IP или wifi-сети), полученные `getCurrentPosition()`.

```js
navigator.geolocation.getCurrentPosition(function (position) {
  do\_something(position.coords.latitude, position.coords.longitude);
});
```

Функция `do_something()`, в примере выше, будет вызвана лишь тогда, когда данные о местоположении будут получены.

### Наблюдение за текущим местоположением

Если данные о местоположении меняются (либо устройство находится в движении, либо пришли более точные данные о геопозиции), вы можете указать callback функцию, которая будет вызывается при любом обновлении данных о местоположении. Это делается с использованием функции `watchPosition()` (en-US), которая имеет несколько входных параметров: `getCurrentPosition()`. Эта функция вызывается много раз, позволяя браузеру обновлять данные о текущей локации либо во время движения, либо после получения более точной информации о местоположении (после применения более точных приёмов). Функция, которая вызывается при ошибке, для `getCurrentPosition()`, при желании, может быть вызвана неоднократно.

**Примечание:** вы можете использовать `watchPosition()` (en-US) без вызова `getCurrentPosition()`.

```js
var watchID = navigator.geolocation.watchPosition(function (position) {
  do\_something(position.coords.latitude, position.coords.longitude);
});
```

Метод `watchPosition()` (en-US) возвращает числовой ID, который может быть использован для идентификации наблюдателя за местоположением; используйте его вместе с методом `clearWatch()` (en-US), чтобы перестать получать новые данные о местоположении.

```js
navigator.geolocation.clearWatch(watchID);
```

### Точная настройка отклика

`getCurrentPosition()` и `watchPosition()` (en-US) принимают колбэк-функцию при успехе, необязательную колбэк-функцию при ошибке и необязательный объект `PositionOptions` (en-US).

Этот объект позволяет вам включить возможность определения позиции с высокой точностью, указать максимальное время кеширования значения позиции (при повторных запросах, пока время не вышло, вам будет возвращается кешированное значение; после браузер будет запрашивать актуальные данные), а также указать значение, устанавливающее интервал — как часто браузер должен пытаться получить данные о местоположении, прежде чем выйдет время.

Вызов `watchPosition` (en-US) может выглядит следующим образом:

```js
function geo\_success(position) {
  do\_something(position.coords.latitude, position.coords.longitude);
}

function geo\_error() {
  alert("Извините, нет доступной позиции.");
}

var geo_options = {
  enableHighAccuracy: true,
  maximumAge: 30000,
  timeout: 27000,
};

var wpid = navigator.geolocation.watchPosition(
  geo_success,
  geo_error,
  geo_options,
);
```

Описание позиции
----------------

Местоположение пользователя содержится в экземпляре объекта `GeolocationPosition`, содержащего внутри экземпляр другого объекта — `GeolocationCoordinates`.

Экземпляр `GeolocationPosition` содержит только две вещи, свойство `coords`, внутри которого `GeolocationCoordinates` и свойство timestamp, внутри которого экземпляр `DOMTimeStamp` (en-US), предоставляющее метку времени, созданную при получении данные.

Экземпляр `GeolocationCoordinates` содержит некоторое количество свойств, двое из которых вы будете чаще всего использовать: `latitude` и `longitude`, которые помогут вам отобразить полученную позицию на карте. Поэтому многие колбэк-функции с успешным получением позиции выглядят очень просто:

```js
function success(position) {
  const latitude = position.coords.latitude;
  const longitude = position.coords.longitude;

  // Дальше код, который что-то делает с широтой(latitude) и долготой(longitude)
}
```

Однако, вы также можете получить и другую информацию из объекта `GeolocationCoordinates`, такую как высота над уровнем моря, скорость, направление устройства и точные данные о высоте, долготе и широте.

Обработка ошибок
----------------

Callback-функция для ошибок, если она была передана в `getCurrentPosition()` или `watchPosition()`, ожидает экземпляр объекта `GeolocationPositionError` в качестве первого аргумента. Он будет содержать два свойства, `code`, который укажет на то, какая именно ошибка произошла и понятное для человека `message`, описывающее значение поля code.

Функция может выглядеть примерно так:

```js
function errorCallback(error) {
  alert("ERROR(" + error.code + "): " + error.message);
}
```

Примеры
-------

Следующий пример использует Geolocation API для того, чтобы получить широту и долготу пользователя. При успешном выполнении, ссылка будет вести на `openstreetmap.org`, который отобразит пользовательскую позицию на карте.
```

body {
  padding: 20px;
  background-color: #ffffc9;
}

button {
  margin: 0.5rem 0;
}
```

### HTML

```html
<button id="find-me">Show my location</button><br />
<p id="status"></p>
<a id="map-link" target="\_blank"></a>
```

### JavaScript

```js
function geoFindMe() {
  const status = document.querySelector("#status");
  const mapLink = document.querySelector("#map-link");

  mapLink.href = "";
  mapLink.textContent = "";

  function success(position) {
    const latitude = position.coords.latitude;
    const longitude = position.coords.longitude;

    status.textContent = "";
    mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`;
    mapLink.textContent = `Широта: ${latitude} °, Долгота: ${longitude} °`;
  }

  function error() {
    status.textContent = "Невозможно получить ваше местоположение";
  }

  if (!navigator.geolocation) {
    status.textContent = "Geolocation не поддерживается вашим браузером";
  } else {
    status.textContent = "Определение местоположения…";
    navigator.geolocation.getCurrentPosition(success, error);
  }
}

document.querySelector("#find-me").addEventListener("click", geoFindMe);
```

### Демо


StorageManager.estimate() - Интерфейсы веб API

StorageManager.estimate()
=========================

**Secure context:** This feature is available only in secure contexts (en-US) (HTTPS), in some or all supporting browsers.

The **`estimate()`** method of the `StorageManager` interface asks the Storage Manager to obtain quota and usage information for the current origin. This method operates asynchronously, so it returns a `Promise` which resolves once the information is available. The promise's fulfillment handler receives as an input a `StorageEstimate` (en-US) with the usage and quota data.

Синтаксис
---------
```

var estimatePromise = StorageManager.estimate();
```

### Параметры

None.

### Возвращаемый результат

A `Promise` that resolves to an object which conforms to the `StorageEstimate` (en-US) dictionary. This dictionary contains estimates of how much space is available to the origin or app (in `StorageEstimate.quota` (en-US), as well as how much is currently used (in `StorageEstimate.usage` (en-US)). These are not exact numbers; between compression, deduplication, and obfuscation for security reasons, they will not be precise.

You may find that the `quota` varies from app to app based on factors such as the frequency with which the user visits it, commonly-known site popularity data, and so forth.

Примеры
-------

In this example, we obtain the usage estimates and present the percentage of storage capacity currently used to the user.

### HTML content

```html
<p>
  You're currently using about <span id="percent"> </span>% of your available
  storage.
</p>
```

### JavaScript content

```js
navigator.storage.estimate().then(function (estimate) {
  document.getElementById("percent").innerHTML = (
    estimate.usage / estimate.quota
  ).toFixed(2);
});
```

### Результат

Спецификация
------------

| Specification |
| --- |
| Storage Standard # ref-for-dom-storagemanager-estimate |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* Storage API
* `Storage`, the object returned by `Window.localStorage`
* `StorageManager`
* `navigator.storage` (en-US)


Использование пользовательских элементов - Интерфейсы веб API

Использование пользовательских элементов
========================================

Одна из ключевых особенностей стандарта Веб-компонент это возможность создавать пользовательские элементы на HTML-странице, инкапсулирующие функциональность, вместо того чтобы создавать длинную, вложенную группу элементов, которые бы вместе реализовывали нужную пользовательскую фичу. Эта статья является вводной по использованию пользовательских HTML-компонент.

**Примечание:** Пользовательские элементы поддерживаются по умолчанию в Firefox, Chrome и Opera. Safari пока поддерживает только автономные пользовательские компоненты, Edge также работает над реализацией.

Высокоуровневый обзор
---------------------

Контроллером пользовательских элементов веб-документа является объект `CustomElementRegistry` (en-US) — этот элемент позволяет регистрировать пользовательские элементы на веб-странице, возвращает информацию о зарегистрированных элементах и т.п.

Чтобы зарегистрировать пользовательский элемент на странице, используйте метод `CustomElementRegistry.define()` method. Он принимает аргументы:

* `DOMString` - имя элемента. Обратите внимание, что в именах пользовательских элементах должен содержаться дефис; они не могут состоять только из одного слова.
* Объект типа class, определяющий поведение элемента.
* Опционально объект options, имеющий свойство `extends`, соответствующее встроенному элементу, от которого наследует объект.

К примеру, мы можем определить пользовательский элемент word-count element:

```js
customElements.define("word-count", WordCount, { extends: "p" });
```

Этот элемент называется `word-count`, объект соответствующего класса называется `WordCount`, и он наследует элементу `<p>`.

Объект класса пользовательского элемента определяется с помощью синтаксиса классов ES 2015. Например, `WordCount` имеют следующую структуру:

```js
class WordCount extends HTMLParagraphElement {
  constructor() {
    // Всегда первым делом вызывайте super() в конструкторе
    super();

    // Далее пишется функциональность элемента

    ...
  }
}
```

Это простой пример, но его можно дополнить. Можно определить специальные lifecycle callbacks, которые будут вызваны в определённые моменты жизненного цикла элемента. Например, `connectedCallback` будет вызван, когда пользовательский элемент оказывается впервые встроен в DOM, а `attributeChangedCallback` вызывается, когда пользовательскому элементу добавляют, удаляют или изменяют какой-то атрибут.

Подробнее об этом в секции Using the lifecycle callbacks ниже.

Есть два типа пользовательских элементов:

* **Автономные пользовательские элементы** независимы — они не наследуют встроенным HTML-элементам. Их используют на странице просто как обычный HTML-элемент. Например, `<popup-info>` или `document.createElement("popup-info")`.
* **Модифицированные встроенные элементы** наследуют базовым HTML-элементам. Чтобы создать один из них, вы определяете элемент, от которого они унаследованы (как в примерах выше), и используете их как обычный базовый элемент, но с добавлением атрибута/свойства с именем пользовательского элемента `is`. Например `<p is="word-count">`, или `document.createElement("p", { is: "word-count" })`.
Разбор простых примеров
-----------------------

А сейчас давайте разберём ещё несколько простых примеров, иллюстрирующих подробности создания пользовательских элементов.

### Автономные пользовательские элементы

Рассмотрим пример автономного пользовательского элемента — `<popup-info-box>` (см. работающий пример). Он содержит изображение и строку, и встраивает изображение в страницу. Когда на изображение наводят фокус, компонент показывает всплывающую подсказку с текстом.

Прежде всего файл JavaScript определяет класс `PopUpInfo`, наследующий от `HTMLElement`. Автономные пользовательские элементы почти всегда наследуют `HTMLElement`.

```js
class PopUpInfo extends HTMLElement {
  constructor() {
    // Всегда первым делом вызывайте super() в конструкторе
    super();

    // далее следует функциональность элемента

    ...
  }
}
```

В этом фрагменте кода содержится определение конструктора `constructor` класса, которое всегда начинается с вызова `super()` чтобы отработала цепочка прототипного наследования.

Внутри конструктора мы определяем всю функциональность, которую получит элемент при создании его объекта. В данном случае мы добавляем shadow root к пользовательскому элементу, производим манипуляции с DOM, чтобы создать определённую структуру shadow DOM внутри элемента — которая затем присоединяется к shadow root — и наконец добавляем CSS к shadow root, чтобы задать его стиль.

```js
// Создание shadow root
var shadow = this.attachShadow({ mode: "open" });

// Создание spans
var wrapper = document.createElement("span");
wrapper.setAttribute("class", "wrapper");
var icon = document.createElement("span");
icon.setAttribute("class", "icon");
icon.setAttribute("tabindex", 0);
var info = document.createElement("span");
info.setAttribute("class", "info");

// Берём содержимое атрибута и добавляем его в span
var text = this.getAttribute("text");
info.textContent = text;

// Вставляем иконку
var imgUrl;
if (this.hasAttribute("img")) {
  imgUrl = this.getAttribute("img");
} else {
  imgUrl = "img/default.png";
}
var img = document.createElement("img");
img.src = imgUrl;
icon.appendChild(img);

// Создаём CSS для shadow dom
var style = document.createElement("style");

style.textContent =
  ".wrapper {" +
  // CSS truncated for brevity

  // добавляем созданные элементы к shadow dom

  shadow.appendChild(style);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
```

Наконец, регистрируем пользовательский элемент в `CustomElementRegistry` с помощью метода `define()`, который упоминался ранее — в качестве параметров мы передаём ему имя элемента и имя класса, который содержит его функциональность:

```js
customElements.define("popup-info", PopUpInfo);
```

Теперь он доступен для использования на нашей странице. В HTML мы используем его так:

```html
<popup-info
 img="img/alt.png"
 text="Код валидации вашей карты (CVC)
 это дополнительная мера безопасности — это последние 3 или 4 цифры
 на обороте вашей карты."></popup-info>
```

**Примечание:** вы можете прочитать полный исходный код на JavaScript здесь.

### Модифицированные встроенные элементы

Теперь давайте взглянем на другой пример модифицированного пользовательского элемента — раскрывающийся список (см. действующий пример). Он превращает любой ненумерованный список в раскрывающееся/складывающееся меню.

Первым делом определим класс элемента наподобие того, как это делалось выше:

```js
class ExpandingList extends HTMLUListElement {
  constructor() {
    // Всегда первым делом вызываем super() в конструкторе
    super();

    // ниже следует функциональность элемента

    ...
  }
}
```

Здесь мы не будем во всех подробностях описывать функциональность элемента, вы можете понять как он работает, посмотрев исходный код. Единственное принципиальное различие с предыдущим примером состоит в том, что мы используем интерфейс `HTMLUListElement` (en-US), а не `HTMLElement`. Так что у него есть все характеристики элемента `<ul>`, плюс дополнительная функциональность, которую определили мы. Это и отличает модифицированный встроенный элемент от автономного пользовательского элемента.

Далее мы регистрируем этот элемент с помощью метода `define()` как в прошлом примере, только на сей раз мы добавляем объект options, который определяет, какому встроенному элементу наследует данный:

```js
customElements.define("expanding-list", ExpandingList, { extends: "ul" });
```

Встроенный элемент используется на веб-странице немного по-другому:

```html
<ul is="expanding-list">
  ...
</ul>
```

Вы задаёте элемент `<ul>` как обычно, но указываете имя модифицированного элемента в атрибуте `is`.

**Примечание:** Полный исходный код на JavaScript доступен здесь.

Использование lifecycle callbacks
---------------------------------

Вы можете определить несколько разных колбэков в конструкторе пользовательских элементов, которые сработают на разных этапах жизненного цикла элемента:

* `connectedCallback`: Срабатывает, когда пользовательский элемент впервые добавляется в DOM.
* `disconnectedCallback`: Срабатывает, когда пользовательский элемент удаляется из DOM.
* `adoptedCallback`: Срабатывает, когда пользовательский элемент перемещён в новый документ.
* `attributeChangedCallback`: Срабатывает, когда пользовательскому элементу добавляют, удаляют или изменяют атрибут.

Посмотрим на них в действии. Код ниже взят из примера life-cycle-callbacks (см. его в действии). Это тривиальный пример, создающий на странице цветной квадрат. Вот как выглядит код пользовательского элемента:

```html
<custom-square l="100" c="red"></custom-square>
```

Конструктор класса очень простой — мы просто добавляем shadow DOM к элементу, а затем добавляем пустые элементы `<div>` и `<style>` к shadow root:

```js
var shadow = this.attachShadow({ mode: "open" });

var div = document.createElement("div");
var style = document.createElement("style");
shadow.appendChild(style);
shadow.appendChild(div);
```

Наиболее важная функция в этом примере `updateStyle()` — она принимает элемент, находит его shadow root, находит его элемент `<style>`, и добавляет `width`, `height`, и `background-color` к стилям.

```js
function updateStyle(elem) {
  var shadow = elem.shadowRoot;
  var childNodes = shadow.childNodes;
  for (var i = 0; i < childNodes.length; i++) {
    if (childNodes[i].nodeName === "STYLE") {
      childNodes[i].textContent =
        "div {" +
        " width: " +
        elem.getAttribute("l") +
        "px;" +
        " height: " +
        elem.getAttribute("l") +
        "px;" +
        " background-color: " +
        elem.getAttribute("c");
    }
  }
}
```

Сами изменения стилей обрабатываются колбэками жизненного цикла, находящимися внутри конструктора. `connectedCallback()` срабатывает, когда элемент встраивается в DOM — здесь мы запускаем функцию `updateStyle()` которая обеспечивает, чтобы квадрат имел стиль, описанный в его атрибутах:

```js
connectedCallback() {
  console.log('Пользовательский элемент квадрат добавлен на страницу.');
  updateStyle(this);
}
```

колбэки `disconnectedCallback()` и `adoptedCallback()` логируют простые сообщения на консоль, которые уведомляют нас, что элемент удалён из DOM или перемещён на другую страницу:

```js
disconnectedCallback() {
  console.log('Пользовательский элемент квадрат удален.');
}

adoptedCallback() {
  console.log('Пользовательский элемент квадрат перемещён на другую страницу.');
}
```

Колбэк `attributeChangedCallback()` запускается когда один из атрибутов элемента меняется. Как видно из его свойств, можно воздействовать на индивидуальные атрибуты, глядя на их имена, и новые и старые значения атрибутов. В данном случае, однако, мы просто снова запускаем функцию `updateStyle()` чтобы убедиться, что атрибуты квадрата получили новые значения:

```js
attributeChangedCallback(name, oldValue, newValue) {
  console.log('Атрибуты пользовательского элемента квадрат изменились.');
  updateStyle(this);
}
```

Обратите внимание, что нужно наблюдать за атрибутами, чтобы запустить колбэк `attributeChangedCallback()` когда они изменятся. Это делается через вызов геттера `observedAttributes()` в конструкторе, который содержит оператор `return` возвращающий массив с именами атрибутов, которые вы хотите наблюдать:

```js
static get observedAttributes() {return ['w', 'l']; }
```

В нашем случае он расположен в начале конструктора.

**Примечание:** Смотрите полный исходный код на JavaScript здесь.


active - Интерфейсы веб API

active
======

Свойство только для чтения **`active`** интерфейса `MediaStream` возвращает логическое значение, которое может быть `true`, если стрим активен; во всех остальных случаях, будет возвращён `false`. Стрим считается **активным,** если хотя бы один из `MediaStreamTrack` не находится в состоянии `MediaStreamTrack.ended`. Как только все track завершатся, свойство `active` переключится в состояние `false`.

Синтаксис
---------
```

var isActive = MediaStream.active;
```

### Значение

Логическое значение равно `true`, если стрим активен; в противном случае, возвращается `false`.

Пример
------

В этом примере, стрим ведётся с пользовательского микрофона и веб-камеры, запрошенных с помощью `getUserMedia()`. Когда стрим становится активным (а это тогда, когда выполнен `Promise`, кнопка на странице обновляется, опираясь на состояние стрима, активен он или же нет.

```js
var promise = navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true
});

promise.then(function(stream) {
  var startBtn = document.querySelector('#startBtn');
  startBtn.disabled = stream.active;
};)
```

Спецификации
------------

| Specification |
| --- |
| Media Capture and Streams # dom-mediastream-active |

Совместимость с браузерами
--------------------------

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


console: статический метод group() - Интерфейсы веб API

console: статический метод group()
==================================

Статический метод **`console.group()`** создаёт новую группу сообщений в выводе веб-консоли. После вызова последующие сообщения, выводимые в консоль, будут иметь дополнительный отступ, до тех пор пока не будет вызван метод `console.groupEnd()` (en-US).

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------

```js
group()
group(label)
```

Параметры
---------

`label` Необязательный

Заголовок группы.

### Возвращаемое значение

Нет (`undefined`).

Примеры
-------

Использование групп помогает упорядочить вывод в консоль благодаря визуальной компоновке связанных сообщений. Чтобы создать новый блок нужно вызвать `console.group()`. Метод `console.groupCollapsed()` работает аналогичным образом, но выведенная группа будет свёрнута до тех пор, пока не будет развёрнута по клику.

Чтобы прекратить вывод в группу, требуется вызвать `console.groupEnd()`. Например, следующий код:

```js
console.log("This is the outer level");
console.group();
console.log("Level 2");
console.group();
console.log("Level 3");
console.warn("More of level 3");
console.groupEnd();
console.log("Back to level 2");
console.groupEnd();
console.log("Back to the outer level");
```

выведет следующий результат:

![Скриншот вложенных сообщений в консоли.](/ru/docs/Web/API/console/group_static/nesting.png)

Для дополнительной информации смотрите Использование групп в консоли в документации `console`.

Спецификация
------------

| Specification |
| --- |
| Console Standard # group |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* `console.groupEnd()` (en-US)
* `console.groupCollapsed()` (en-US)
* Документация Microsoft Edge: `console.group()`
* Документация Node.JS: `console.group()`
* Документация Google Chrome: `console.group()`


Console.timeEnd - Интерфейсы веб API

Console.timeEnd
===============

Останавливает таймер, который был запущен до этого через функцию `console.time()`.

Более подробная документация и примеры есть в разделе Таймеры (en-US) документации к `console`.

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------

```js
console.timeEnd(label);
```

### Параметры

`label`

Метка таймера, который будет остановлен. Останавливается один раз. После остановки время отображается в Веб-консоли.

Примеры
-------

```js
console.time("answer time");
alert("Click to continue");
console.timeLog("answer time");
alert("Do a bunch of other stuff...");
console.timeEnd("answer time");
```

В примере выше мы получим время, которое потребовалось пользователю, чтобы закрыть первое диалоговое окно, а затем время, которое потребовалось пользователю, чтобы закрыть второе диалоговое окно:

![timerresult.png](/ru/docs/Web/API/console/timeend_static/timer_output.png)

Обратите внимание, что метка таймера отображается как при вызове `timeLog()`, так и завершении таймера. Кроме того, вызов timeEnd() выводит дополнительную информацию "timer ended", чтобы очевидно показать, что таймер больше не отслеживается.

Спецификации
------------

| Specification |
| --- |
| Console Standard # timeend |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* `Console.time`
* Opera Dragonfly documentation: Console


console.error() - Интерфейсы веб API

console.error()
===============

Выводит сообщение об ошибке в веб-консоль.

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------
```

console.error(obj1 [, obj2, ..., objN]);
console.error(msg [, subst1, ..., substN]);
console.exception(obj1 [, obj2, ..., objN]);
console.exception(msg [, subst1, ..., substN]);
```

**Примечание:** `console.exception()` — это псевдоним для `console.error()`; они функционально идентичны.

### Параметры

`obj1` ... `objN`

Список объектов JavaScript для вывода. Строковое представление каждого из этих объектов объединяются в указанном порядке и выводятся.

`msg`

Строка JavaScript, содержащая 0 или более подстановочных символов для замены.

`subst1` ... `substN`

Объекты JavaScript, с помощью которых произойдёт замена символов в msg. Это предоставляет дополнительный контроль над форматом вывода.

Смотрите раздел Вывод текста в консоль в документации `console` для подробностей.

Спецификация
------------

| Specification |
| --- |
| Console Standard # error |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* Opera Dragonfly documentation: Console
* MSDN: Using the F12 Tools Console to View Errors and Status
* Chrome Developer Tools: Using the Console


Console.table() - Интерфейсы веб API

Console.table()
===============

**Не стандартно:** Эта возможность не является стандартной и стандартизировать её пока никто не собирается. Не используйте её на сайтах, смотрящих во внешний мир: она будет работать не у всех пользователей. Также могут присутствовать большие несовместимости между реализациями и её поведение может в будущем измениться.

Отображает наборы данных в виде таблицы.

Данная функция принимает один обязательный аргумент `data`, который должен быть представлен в виде массива или объекта, и один дополнительный параметр `columns`.

функция логирует аргумент `data` в виде таблицы. Каждый элемент массива (или каждое свойство, если `data -` это объект) будет представлять строку таблицы.

Первая колонка таблицы будет озаглавлена как `(index)`. Если `data` представлена массивом, тогда значения первой колонки будут представлять индексы массива. Если `data-` объект, тогда значения первой колонки будут представлять свойства объекта.

**Примечание:** Эта возможность доступна в Web Workers

### Простые наборы

Аргумент `data` может быть массивом или объектом.

```js
// массив строк

console.table(["apples", "oranges", "bananas"]);
```

![](/ru/docs/Web/API/console/table_static/console-table-array.png)

```js
// объект, чьи свойства содержат строки

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

var me = new Person("John", "Smith");

console.table(me);
```

![](/ru/docs/Web/API/console/table_static/console-table-simple-object.png)

### Наборы смешанных типов

Если элементы массива, либо свойства объекта, в свою очередь сами являются массивами или объектами, тогда эти элементы или свойства перечисляются построчно, перечисляя вложенные элементы/свойства в колонках:

```js
// массив массивов

var people = [
  ["John", "Smith"],
  ["Jane", "Doe"],
  ["Emily", "Jones"],
];
console.table(people);
```

![Table displaying array of arrays](/ru/docs/Web/API/console/table_static/console-table-array-of-array.png)

```js
// массив объектов

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

var john = new Person("John", "Smith");
var jane = new Person("Jane", "Doe");
var emily = new Person("Emily", "Jones");

console.table([john, jane, emily]);
```

Обратите внимание, что если массив состоит из объектов, колонки таблицы будут озаглавлены названиями свойств объекта.

![Table displaying array of objects](/ru/docs/Web/API/console/table_static/console-table-array-of-objects.png)

```js
// объект, свойства которого являются объектами

var family = {};

family.mother = new Person("Jane", "Smith");
family.father = new Person("John", "Smith");
family.daughter = new Person("Emily", "Smith");

console.table(family);
```

![Table displaying object of objects](/ru/docs/Web/API/console/table_static/console-table-object-of-objects.png)

### Ограничение видимости колонок

По умолчанию, `console.table()` отображает все элементы в каждой строке. Вы можете использовать опциональный параметр `columns` чтобы указать набор отображаемых колонок:

```js
// массив объектов, логируется только свойство firstName

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

var john = new Person("John", "Smith");
var jane = new Person("Jane", "Doe");
var emily = new Person("Emily", "Jones");

console.table([john, jane, emily], ["firstName"]);
```

![Table displaying array of objects with filtered output](/ru/docs/Web/API/console/table_static/console-table-array-of-objects-firstname-only.png)

### Сортировка колонок

Вы можете отсортировать необходимую колонку, кликнув по её заголовку.

Синтаксис
---------
```

console.table(data [, columns]);
```

### Параметры

`data`

Данные для отображения. Могут быть представлены массивом или объектом.

`columns`

Массив названий колонок, которые следует отобразить.

Спецификации
------------

| Specification |
| --- |
| Console Standard # table |

Совместимость с браузерами
--------------------------

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


clear() - Интерфейсы веб API

clear()
=======

**Не стандартно:** Эта возможность не является стандартной и стандартизировать её пока никто не собирается. Не используйте её на сайтах, смотрящих во внешний мир: она будет работать не у всех пользователей. Также могут присутствовать большие несовместимости между реализациями и её поведение может в будущем измениться.

Очищает консоль.

Содержимое консоли заменяется на информационное сообщение, которое может быть следующим: "Консоль очищена".

Примечание для Google Chrome, от функции `console.clear()` не будет никакого эффекта, если у пользователя установлена надстройка "Preserve log upon navigation" в настройках.

Синтаксис
---------
```

console.clear();
```

Спецификация
------------

| Specification |
| --- |
| Console Standard # clear |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* Opera Dragonfly documentation: Console
* MSDN: Using the F12 Tools Console to View Errors and Status
* Chrome Developer Tools: Using the Console


Console.profile() - Интерфейсы веб API

Console.profile()
=================

**Не стандартно:** Эта возможность не является стандартной и стандартизировать её пока никто не собирается. Не используйте её на сайтах, смотрящих во внешний мир: она будет работать не у всех пользователей. Также могут присутствовать большие несовместимости между реализациями и её поведение может в будущем измениться.

Запускает профайлер, встроенный в браузер (например, Firefox performance tool).

Вы можете указать аргумент, чтобы назвать профиль, это позволит вам остановить только этот профиль, если записывается несколько профилей. Смотрите `Console.profileEnd()` (en-US) чтобы понять, как этот аргумент интерпретируется.

Останавливает профайлер `Console.profileEnd()` (en-US).

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------
```

console.profile(profileName);
```

Параметры
---------

`profileName`

Вы можете указать метку профиля (по желанию).

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* `Console.profileEnd()` (en-US)


Console.trace() - Интерфейсы веб API

Console.trace()
===============

Выводит стэк трэйс в Веб Консоль.

**Примечание:** Эта возможность доступна в Web Workers

Посмотрите Стэк трэйсы в документации `console` с деталями и примерами.

Синтаксис
---------
```

console.trace();
```

Пример
------

```js
function foo() {
  function bar() {
    console.trace();
  }
  bar();
}

foo();
```

В консоли следующий трэйс будет отображён так:
```

bar
foo
<anonymous>
```

Спецификация
------------

| Specification |
| --- |
| Console Standard # trace |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* Документация Opera Dragonfly: Консоль


Console.debug() - Интерфейсы веб API

Console.debug()
===============

Выводит сообщение в веб.консоль Сообщение отображается только если консоль настроена на показ сообщений уровня отладки.

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------
```

console.debug(obj1 [, obj2, ..., objN]);
console.debug(msg [, subst1, ..., substN]);
```

### Параметры

`obj1` ... `objN`

Список объектов JavaScript для вывода. Строковые представления каждого из этих объектов соединяются вместе в указанном порядке и выводятся.

`msg`

Строка JavaScript, содержащая 0 и более подстановочных символов для замены (см. `subst1` ... `substN`).

`subst1` ... `substN`

JavaScript-объекты, с помощью которых произойдёт замена подстановочных символов в `msg`. Это даст вам дополнительный контроль над форматом вывода. (см. Преобразование строк с использованием масок).

Читайте Вывод текста в консоль в документации `console` для получения дополнительной информации.

Спецификации
------------

| Specification |
| --- |
| Console Standard # debug |

Совместимость с браузерами
--------------------------

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


Console.timeLog() - Интерфейсы веб API

Console.timeLog()
=================

Выводит в консоль текущее значение таймера, запущенного вызовом `console.time()`.
Подробности и примеры смотрите в документации Timers.

**Примечание:** Эта возможность доступна в Web Workers

Синтаксис
---------
```

console.timeLog(label);
```

### Параметры

`label`

Имя таймера для вывода в консоль.

### Результат

Если параметр `label` опущен, выводит значение таймера по умолчанию:
```

default: 1042ms
```

Если в параметре `label` указано имя существующего таймера:
```

timer name: 1242ms
```

### Исключения

При отсутствии запущенного таймера по умолчанию `timeLog()` выводит предупреждение:
```

Timer “default” doesn’t exist.
```

При указании имени несуществующего таймера:
```

 Timer “timer name” doesn’t exist.
```

Примеры
-------

```js
console.time("answer time");
alert("Нажмите для продолжения");
console.timeLog("answer time");
alert("Делаем кучу другой работы...");
console.timeEnd("answer time");
```

Отчёт таймера начинается с момента вызова `console.time()`. Сначала выводится время от начала отсчёта до закрытия первого уведомления. Затем выводится время с начала отсчёта до закрытия второго уведомления:

![timerresult.png](/ru/docs/Web/API/console/timelog_static/console-timelog.png)

Обратите внимание, имя таймера печатается как при вызовах `timeLog()` , так и при завершении таймера.

Спецификации
------------

| Specification |
| --- |
| Console Standard # timelog |

Совместимость с браузерами
--------------------------

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

Смотрите также
--------------

* Opera Dragonfly documentation: Console