Using textures in WebGL - Интерфейсы веб API

Using textures in WebGL
=======================

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

Сейчас наша программа рисует вращающийся объёмный куб - давайте натянем на него текстуру вместо заливки граней одним цветом.

Загрузка текстур
----------------

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

**Примечание:** Важно помнить, что загрузка текстур следует правилам кросс-доменности (en-US), что означает, что вы можете загружать текстуры только с сайтов, для которых ваш контент является CORS доверенным. См. подробности в секции "Кросс-доменные текстуры" ниже.

Код для загрузки текстур выглядит так::
```

//
// Инициализация текстуры и загрузка изображения.
// Когда загрузка изображения завершена - копируем его в текстуру.
//
function loadTexture(gl, url) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Так как изображение будет загружено из интернета,
  // может потребоваться время для полной загрузки.
  // Поэтому сначала мы помещаем в текстуру единственный пиксель, чтобы
  // её можно было использовать сразу. После завершения загрузки
  // изображения мы обновим текстуру.
  const level = 0;
  const internalFormat = gl.RGBA;
  const width = 1;
  const height = 1;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED_BYTE;
  const pixel = new Uint8Array([0, 0, 255, 255]);  // непрозрачный синий
  gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                width, height, border, srcFormat, srcType,
                pixel);

  const image = new Image();
  image.onload = function() {
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                  srcFormat, srcType, image);

    // У WebGL1 иные требования к изображениям, имеющим размер степени 2,
    // и к не имеющим размер степени 2, поэтому проверяем, что изображение
    // имеет размер степени 2 в обеих измерениях.
    if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
       // Размер соответствует степени 2. Создаём MIP'ы.
       gl.generateMipmap(gl.TEXTURE_2D);
    } else {
       // Размер не соответствует степени 2.
       // Отключаем MIP'ы и устанавливаем натяжение по краям
       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    }
  };
  image.src = url;

  return texture;
}

function isPowerOf2(value) {
  return (value & (value - 1)) == 0;
}
```

Функция `loadTexture()` начинается с создания объекта WebGL `texture` вызовом функции `createTexture()` (en-US). Сначала функция создаёт текстуру из единственного голубого пикселя, используя `texImage2D()` (en-US). Таким образом текстура может быть использована сразу (как сплошной голубой цвет) при том, что загрузка изображения может занять некоторое время.

Чтобы загрузить текстуру из файла изображения, функция создаёт объект `Image` и присваивает атрибуту `src` адрес, с которого мы хотим загрузить текстуру. Функция, которую мы назначили на событие `image.onload`,будет вызвана после завершения загрузки изображения. В этот момент мы вызываем `texImage2D()` (en-US), используя загруженное изображение как исходник для текстуры. Затем мы устанавливаем фильтрацию и натяжение, исходя из того, является ли размер изображения степенью 2 или нет.

В WebGL1 изображения размера, не являющегося степенью 2, могут использовать только `NEAREST` или `LINEAR` фильтрацию, и для них нельзя создать mipmap. Также для таких изображений мы должны установить натяжение `CLAMP_TO_EDGE`. С другой стороны, если изображение имеет размер степени 2 по обеим осям, WebGL может производить более качественную фильтрацию, использовать mipmap и режимы натяжения `REPEAT` или `MIRRORED_REPEAT`.

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

Мипмаппинг и UV-повторение могут быть отключены с помощью `texParameteri()` (en-US). Так вы сможете использовать текстуры с размером, не являющимся степенью 2 (NPOT - non-power-of-two), ценой отключения мипмаппинга, UV-натяжения, UV-повторения, и вам самому придётся контролировать, как именно устройство будет обрабатывать текстуру.
```

// также разрешено gl.NEAREST вместо gl.LINEAR, но не mipmap.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// Не допускаем повторения по s-координате.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// Не допускаем повторения по t-координате.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
```

Повторим, что с этими параметрами совместимые WebGL устройства будут допускать использование текстур с любым разрешением (вплоть до максимального). Без подобной настройки WebGL потерпит неудачу при загрузке NPOT-текстур, и вернёт прозрачный чёрный цвет `rgba(0,0,0,0)`.

Для загрузки изображения добавим вызов `loadTexture()` в функцию `main()`. Код можно разместить после вызова `initBuffers(gl)`.
```

// Загрузка текстуры
const texture = loadTexture(gl, 'cubetexture.png');
```

Отображение текстуры на гранях
------------------------------

Сейчас текстура загружена и готова к использованию. Но сначала мы должны установить соответствие между координатами текстуры и гранями нашего куба. Нужно заменить весь предыдущий код, который устанавливал цвета граней в `initBuffers()`.
```

  const textureCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);

  const textureCoordinates = [
    // Front
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Back
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Top
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Bottom
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Right
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
    // Left
    0.0,  0.0,
    1.0,  0.0,
    1.0,  1.0,
    0.0,  1.0,
  ];

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates),
                gl.STATIC_DRAW);

...
  return {
    position: positionBuffer,
    textureCoord: textureCoordBuffer,
    indices: indexBuffer,
  };
```

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

Массив `textureCoordinates` определяет координаты текстуры, соответствующие каждой вершине каждой грани. Заметьте, что координаты текстуры лежат в промежутке между 0.0 и 1.0. Размерность текстуры нормализуется в пределах между 0.0 и 1.0, независимо от реального размера изображения.

После определения массива координат текстуры, мы копируем его в буфер, и теперь WebGL имеет данные для отрисовки.

Обновление шейдеров
-------------------

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

### Вершинный шейдер

Заменяем вершинный шейдер, чтобы он получал координаты текстуры вместо цвета.
```

  const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec2 aTextureCoord;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying highp vec2 vTextureCoord;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vTextureCoord = aTextureCoord;
    }
  `;
```

Ключевое изменение в том, что вместо получения цвета вершины, мы получаем координаты текстуры и передаём их в вершинный шейдер, сообщая положение точки внутри текстуры, которая соответствует вершине.

### Фрагментный шейдер

Также нужно обновить фрагментный шейдер:
```

  const fsSource = `
    varying highp vec2 vTextureCoord;

    uniform sampler2D uSampler;

    void main(void) {
      gl_FragColor = texture2D(uSampler, vTextureCoord);
    }
  `;
```

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

### Атрибуты и uniform-переменные

Так как мы изменили атрибуты и добавили uniform-переменные, нам нужно получить их расположение
```

  const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
      textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
      uSampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
    },
  };
```

Рисование текстурированного куба
--------------------------------

Сделаем несколько простых изменений в функции `drawScene()`.

Во-первых, удаляем код, который определял цветовые буферы, и заменяем его на:
```

// Указываем WebGL, как извлечь текстурные координаты из буффера
{
    const num = 2; // каждая координата состоит из 2 значений
    const type = gl.FLOAT; // данные в буфере имеют тип 32 bit float
    const normalize = false; // не нормализуем
    const stride = 0; // сколько байт между одним набором данных и следующим
    const offset = 0; // стартовая позиция в байтах внутри набора данных
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
    gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, num, type, normalize, stride, offset);
    gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
}
```

Затем добавляем код, который отображает текстуру на гранях, прямо перед отрисовкой:
```

  // Указываем WebGL, что мы используем текстурный регистр 0
  gl.activeTexture(gl.TEXTURE0);

  // Связываем текстуру с регистром 0
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Указываем шейдеру, что мы связали текстуру с текстурным регистром 0
  gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
```

WebGL имеет минимум 8 текстурных регистров; первый из них `gl.TEXTURE0`. Мы указываем, что хотим использовать регистр 0. Затем мы вызываем функцию `bindTexture()`, которая связывает текстуру с `TEXTURE_2D` регистра 0. Наконец мы сообщаем шейдеру, что для `uSampler` используется текстурный регистр 0.

В завершение, добавляем аргумент `texture` в функцию `drawScene()`.
```

drawScene(gl, programInfo, buffers, texture, deltaTime);
...
function drawScene(gl, programInfo, buffers, texture, deltaTime) {
```

Сейчас вращающийся куб должен иметь текстуру на гранях.

Посмотреть код примера полностью | Открыть демо в новом окне

Кросс-доменные текстуры
-----------------------

Загрузка кросс-доменных текстур контролируется правилами кросс-доменного доступа. Чтобы загрузить текстуру с другого домена, она должна быть CORS доверенной. См. детали в статье HTTP access control.

В статье на hacks.mozilla.org есть объяснение с примером, как использовать изображения CORS для создания WebGL текстур.

**Примечание:** Поддержка CORS для текстур WebGL и атрибут `crossOrigin` для элементов изображений реализованы в Gecko 8.0.

Tainted (только-для-записи) 2D canvas нельзя использовать в качестве текстур WebGL. Например, 2D `<canvas>` становится "tainted", когда на ней отрисовано кросс-доменное изображение.

**Примечание:** Поддержка CORS для Canvas 2D `drawImage` реализована в Gecko 9.0. Это значит, что использование CORS доверенных кросс-доменных изображений больше не делает 2D canvas "tained" (только-для-записи), и вы можете использовать такую 2D canvas как исходник для текстур WebGL.

**Примечание:** Поддержка CORS для кросс-доменного видео и атрибут `crossorigin` для HTML-элемента `<video>` реализованы в Gecko 12.0.

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


Range.getBoundingClientRect - Интерфейсы веб API

Range.getBoundingClientRect
===========================

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

Метод **`Range.getBoundingClientRect()`** возвращает объект `ClientRect`, соответствующий фрагменту документа, на основе которого создан объект Range; это прямоугольник, непосредственно обрамляющий объединение прямоугольников для каждого элемента фрагмента документа.

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

boundingRect = range.getBoundingClientRect()
```

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

```js
range = document.createRange();
range.selectNode(document.getElementsByTagName("div").item(0));
rect = range.getBoundingClientRect();
```

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

| Specification |
| --- |
| CSSOM View Module # dom-range-getboundingclientrect |

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

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

See also
--------

* The DOM interfaces index


Range.collapsed - Интерфейсы веб API

Range.collapsed
===============

Доступное только для чтения свойство **`Range.collapsed`** возвращает флаг `Boolean` (en-US), который указывает, в одной ли и той же позиции находятся начало и конец диапазона `Range`. Оно возвращает `true`, если начало и конец граничных точек диапазона `Range` являются одной и той же точкой в DOM, `false` – в противном случае.

Свёрнутый диапазон `Range` является пустым, не имеет содержимого, определяет единую точку в древе DOM. Для того, чтобы свернуть диапазон, смотрите метод `Range.collapse()` (en-US).

Syntax
------
```

isCollapsed = range.collapsed;
```

Example
-------

```js
var range = document.createRange();

range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
isCollapsed = range.collapsed;
```

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

| Specification |
| --- |
| DOM Standard # ref-for-dom-range-collapsed① |

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

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

See also
--------

* The DOM interfaces index


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

Range.surroundContents()
========================

Метод **`Range.surroundContents()`** перемещает содержимое `Range` в новый элемент, который расположится в начале указанного диапазона.

Этот метод приблизительно эквивалентен коду `newNode.appendChild(range.extractContents()); range.insertNode(newNode)`. После его работы крайние точки диапазона будут включать в себя `newNode`.

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

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

range.surroundContents(newNode);
```

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

*newNode*

Элемент (en-US) который будет содержать в себе выделенный текст.

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

```js
var range = document.createRange();
var newNode = document.createElement("p");

range.selectNode(document.getElementsByTagName("div").item(0));
range.surroundContents(newNode);
```

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

| Specification |
| --- |
| DOM Standard # dom-range-surroundcontents |

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

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

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

* The DOM interfaces index


Руководство по Canvas - Интерфейсы веб API

Руководство по Canvas
=====================

![](/ru/docs/Web/API/Canvas_API/Tutorial/canvas_tut_examples.jpg)

**`<canvas>`** — это HTML элемент, использующийся для рисования графики средствами языков программирования (обычно это JavaScript). Он может, к примеру, использоваться для рисования графов, создания коллажей или простой анимации.
 Изображения в правой части статьи являются примерами использования `<canvas>.`
 Примеры их создания приводятся в этой статье.

В этом руководстве описываются основы использования элемента `<canvas>` для рисования 2D-графики. Приведённые примеры дадут вам понимание того, что вы можете сделать с помощью <canvas>, а использованные в статье фрагменты кода помогут в создании собственных проектов.

`Впервые <canvas>` использовался компанией Apple для создания Mac OS X Dashboard, а затем был реализован в Web-браузерах. На сегодняшний день все основные браузеры поддерживают работу с <canvas>. Тег `<canvas>` часть спецификации WhatWG Web applications 1.0 также известной как HTML5.

Прежде чем начать
-----------------

Работа с элементом **`<canvas>`** довольно проста, но потребует от вас базовых знаний HTML и JavaScript. Следует предупредить о том, что элемент `<canvas>` не работает в некоторых старых браузерах, но поддерживается большинством современных браузеров. Стандартный размер **`<canvas>`** 300px × 150px (ширина × высота), однако эти размеры могут быть изменены при помощи HTML-атрибутов `height` и `width`. Для рисования графики **`<canvas>`** мы будем использовать `javascript context object`, который создаёт графику динамически.

В этом руководстве
------------------

* Базовое использование
* Рисование форм (en-US)
* Использование изображений (en-US)
* Применение стилей и цветов (en-US)
* Трансформации (en-US)
* Композирование и обрезка (en-US)
* Базовое анимирование (en-US)
* Оптимизация canvas (en-US)
Смотрите Также
--------------

* Canvas topic page
* Drawing Graphics with Canvas (en-US)
* Canvas examples
* HTML5 Tutorial
* Drawing Text Using a Canvas (en-US)
* Adding Text to Canvas
* Canvas Drawing and Animation Application
* Interactive canvas tutorial
* HTML5CanvasTutorials
* How to draw N grade Bézier curves with the Canvas API
* W3C Standard
Примечание для помощников
-------------------------

Из-за досадной технической ошибки, которая произошла 17 июня 2013 года, мы утеряли историю по этому руководству, в том числе атрибуции ко всем прошлым, участвовавшие в его создании. Мы приносим извинения за это, и надеемся, что вы простите нас за этот несчастный случай.

* Следующая статья »


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

Базовое использование Canvas
============================

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

Давайте начнём этот урок с изучения элемента `<canvas>` как такового. В конце этой страницы вы узнаете как устанавливать canvas 2D context и нарисуете первый пример в вашем браузере.

Элемент `<canvas>`
------------------

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

Он выглядит как элемент `<img>`, но его отличие в том, что он не имеет атрибутов `src` и `alt`. Элемент `<canvas>` имеет только два атрибута - **ширину** и **высоту**. Оба они не обязательны и могут быть выставлены с использованием свойств DOM (en-US). Если атрибуты высоты и ширины не установлены, canvas будет по умолчанию шириной **300 пикселей** и в высоту **150 пикселей**. Вы так же можете выставить размеры произвольно в CSS, но во время рендеринга изображение будет масштабироваться в соответствии с его размером и ориентацией.

**Примечание:** Если вид вашего изображения кажется вам искажённым, попробуйте указать атрибуты ширины и высоты в явном виде в атрибутах <canvas> , а не с помощью CSS.

Атрибут id не специфичен для элемента `<canvas>,` но он может быть применён по умолчанию в атрибутах HTML, так как он может быть использован (почти) для любого элемента HTML (так же как класс). Это всегда отличная идея использовать `id`, так как это позволяет намного проще идентифицировать наш элемент в сценарии.

Элемент `<canvas>` может быть стилизован также, как любое изображение (margin, border, background, и т. д.). Эти правила, как бы то ни было, фактически не влияют на отрисовку в canvas. Мы увидим как это сделано позже в этом руководстве. Когда стили не указаны, canvas будет по умолчанию абсолютно прозрачным.

### Запасное содержимое

Из-за того, что старые браузеры (в особенности, версии Internet Explorer раннее чем 9) не поддерживают элемент `<canvas>`, вам следует предоставить запасное содержимое для отображения этими браузерами.

Это очень просто: мы всего лишь предоставляем альтернативное содержимое внутри элемента `<canvas>`. Браузеры, которые не поддерживают `<canvas>` проигнорируют container и отобразят запасное содержимое этого тега. Браузеры, которые поддерживают `<canvas>` проигнорируют запасное содержимое, и просто нормально отобразят canvas.

Например, мы можем предоставить текстовое описание содержимого canvas или предоставить статичное изображение динамически отображаемого содержимого. Это может выглядеть как-то так:

```html
<canvas id="stockGraph" width="150" height="150">
  current stock price: $3.15 +0.15
</canvas>

<canvas id="clock" width="150" height="150">
  <img src="images/clock.png" width="150" height="150" alt="" />
</canvas>
```

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

### Требуется тег `</canvas>`

В отличии от элемента `<img>`, элемент `<canvas>` **требует** закрывающийся тег (`</canvas>`). Если этот тег не предоставлен, остаток документа будет считаться запасным контентом и не будет отображён.

Если запасной контент не нужен, простой `<canvas id="foo" ...></canvas>` полностью совместим со всеми браузерами, что поддерживают canvas.

Рендеринг содержимого (контекста)
---------------------------------

Элемент `<canvas>` в документе создаётся с фиксированным размером элемента для рисования, который может иметь один или несколько контекстов для рендеринга, создавая и манипулируя содержимым для показа. В данном руководстве мы сфокусируемся на 2D рендеринге. Другие контексты могут предоставлять разные типы рендеринга, к примеру WebGl использует 3D контекст основанный на OpenGL ES.

Холст изначально пустой и прозрачный. Первым делом скрипт получает доступ к контексту и отрисовывает его. Элемент `<canvas>` имеет метод `getContext()`, используется для получения контекста визуализации и её функции рисования. `getContext()` принимает один параметр, тип контекста. Для 2D графики, которая охвачена этим руководством будем использовать метку "2d".

```js
const canvas = document.getElementById("tutorial");
const ctx = canvas.getContext("2d");
```

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

Проверка поддержки
------------------

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

```js
const canvas = document.getElementById("tutorial");

if (canvas.getContext) {
  const ctx = canvas.getContext("2d");
  // drawing code here
} else {
  // canvas-unsupported code here
}
```

Скелет шаблона
--------------

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

**Примечание:** вставка скрипта внутрь HTML не является хорошей практикой. Мы делаем это здесь, чтобы сделать пример короче.

```html
<!doctype html>
<html>
  <head>
    <title>Canvas tutorial</title>
    <script type="text/javascript">
 function draw() {
 const canvas = document.getElementById("tutorial");
 if (canvas.getContext) {
 const ctx = canvas.getContext("2d");
 }
 }
 </script>
    <style type="text/css">
 canvas {
 border: 1px solid black;
 }
 </style>
  </head>
  <body onload="draw();">
    <canvas id="tutorial" width="150" height="150"></canvas>
  </body>
</html>
```

Скрипт вызывает функцию draw(), которая выполнится, когда страница закончит загрузку. Это делается путём события `load (en-US)` в документе. Эта функция может быть вызвана как единожды, так и с помощью данных методов `window.setTimeout()` (en-US), `window.setInterval()` (en-US), или любым другим обработчиком события, когда страница будет загружена.

Вот как шаблон будет выглядеть в действии.

Простой пример
--------------

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

```html
<!doctype html>
<html>
  <head>
    <script type="application/javascript">
 function draw() {
 const canvas = document.getElementById("canvas");
 if (canvas.getContext) {
 const ctx = canvas.getContext("2d");

 ctx.fillStyle = "rgb(200,0,0)";
 ctx.fillRect(10, 10, 55, 50);

 ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
 ctx.fillRect(30, 30, 55, 50);
 }
 }
 </script>
  </head>
  <body onload="draw();">
    <canvas id="canvas" width="150" height="150"></canvas>
  </body>
</html>
```

Этот пример выглядит так:

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

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


Композиция и обрезка - Интерфейсы веб API

Композиция и обрезка
====================

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

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

`globalCompositeOperation`
--------------------------

Мы можем не только рисовать новые фигуры за существующие формы, но мы также можем использовать его, чтобы замаскировать определённые участки, очистить разделы от холста (не ограничивается прямоугольниками, как`clearRect()` method does) и другое.

`globalCompositeOperation = type` (en-US)

Это задаёт Тип операции композиции для применения при разработке новых форм, где Тип является строкой, идентифицирующей, какие из двенадцати операций композитинг в использовании.

См. примеры компоновки (en-US) кода из следующих примеров.

Обрезка контуров
----------------

![](/ru/docs/Web/API/Canvas_API/Tutorial/Compositing/canvas_clipping_path.png)Отсечённый контур похож на обычную форму холста, но он действует как маска, чтобы скрыть нежелательные части фигур. Это визуализируется на изображении справа. Форма красной звезды - наша отправочная дорожка. Все, что выходит за пределы этого пути, не будет нарисовано на холсте.

Если мы сравниваем отсечённый контур со свойством `globalCompositeOperation` на изображении, мы видим два режима композитинга, которые достигают более или менее того же эффекта в исходном и исходном состоянии. Наиболее важные различия между ними заключаются в том, что отсечение контура фактически никогда не обращается к холсту и контур обрезки никогда не влияет добавление новых форм. Это делает обрезку контура идеальным для рисования нескольких фигур в ограниченной области.

В главе о рисовании форм, я назвал только `stroke()` и `fill()` методы, но есть третий способ можно использовать с контурами, так называемый `clip()`.

`clip()` (en-US)

Преобразует текущий выстраиваемый контур в отсечённый контур.

Используйте `clip()` вместо `closePath()` для закрытия контура и его преобразования в отсечённый контур вместо создания заполняющего или обрамляющего контура.

По умолчанию элемент `<canvas>` использует отсечённый контур, который в точности совпадает по размеру с размером самого холста. Это означает, что никакого отсечения попросту не произойдёт.

### Пример обрезки

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

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.fillRect(0, 0, 150, 150);
  ctx.translate(75, 75);

  // Create a circular clipping path
  ctx.beginPath();
  ctx.arc(0, 0, 60, 0, Math.PI \* 2, true);
  ctx.clip();

  // draw background
  var lingrad = ctx.createLinearGradient(0, -75, 0, 75);
  lingrad.addColorStop(0, "#232256");
  lingrad.addColorStop(1, "#143778");

  ctx.fillStyle = lingrad;
  ctx.fillRect(-75, -75, 150, 150);

  // draw stars
  for (var j = 1; j < 50; j++) {
    ctx.save();
    ctx.fillStyle = "#fff";
    ctx.translate(
      75 - Math.floor(Math.random() \* 150),
      75 - Math.floor(Math.random() \* 150),
    );
    drawStar(ctx, Math.floor(Math.random() \* 4) + 2);
    ctx.restore();
  }
}

function drawStar(ctx, r) {
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(r, 0);
  for (var i = 0; i < 9; i++) {
    ctx.rotate(Math.PI / 5);
    if (i % 2 === 0) {
      ctx.lineTo((r / 0.525731) \* 0.200811, 0);
    } else {
      ctx.lineTo(r, 0);
    }
  }
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}
```

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

```
draw();
```

В первых нескольких строках кода мы рисуем чёрный прямоугольник размером с холстом в качестве фона, а затем переводим начало координат в центр. Затем мы создаём круговой обтравочный контур, рисуя дугу и вызывающий `clip()`. Обрезанные контуры также являются частью состояния сохранения холста. Если бы мы хотели сохранить исходный обтравочный контур, мы могли бы сохранить состояние холста перед созданием нового.

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

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

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


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

Finale
======

* « Предыдущая статья

Поздравляем! Вы завершили Canvas tutorial! Эти знания помогут вам создавать великолепную 2d графику в сети.

Больше примеров и учебных материалов
------------------------------------

Различные демки и дополнительные разъяснения о canvas расположены на этих сайтах:

Codepen.io

Песочница для фронтенд разработчика и редактор кода в браузере.

Canvasdemos.com

Приложения, игры, инструменты и учебные пособия для HTML5 canvas элементов.

HTML5CanvasTutorials

Примеры для большинства canvas APIs.

31 days of Canvas tutorials

Прекрасное введение в разработку графики на JavaScript.

Game development

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

Другие Web API
--------------

Эти APIs могут быть полезны в дальнейшей работе с canvas и графикой:

WebGL

API для отображения интерактивной 3D графики.

SVG

Масштабируемая Векторная Графика (Scalable Vector Graphics) позволяет описывать картинки в виде набора векторов (линий) и форм, позволяющих плавно изменять размер вне зависимости от размера в котором они нарисованы.

Web Audio

Web Audio API представляет мощную и универсальную систему контроля аудио в сети, позволяющая разработчикам получить аудио ресурсы, добавлять эффекты аудио, создавать аудио визуализацию, применять пространственные эффекты (такие как сдвиг) и многое другое.

Вопросы
-------

Stackoverflow

Вопросы с тегом "canvas".

Comments about this tutorial – the MDN documentation community

Если у вас остались вопросы об этом учебнике или вы хотите поблагодарить нас, не стесняйтесь обращаться к нам!

* « Предыдущая статья


Оптимизация canvas - Интерфейсы веб API

Оптимизация canvas
==================

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

Элемент `<canvas>` является одним из наиболее широко используемых инструментов для рендеринга 2D-графики в Интернете. Однако когда веб-сайты и приложения используют Canvas API на пределе его возможностей, производительность начинает снижаться. В этой статье приводятся предложения по оптимизации использования элемента canvas для обеспечения хорошей работы графики.

Советы по производительности
----------------------------

Ниже приведён сборник советов по улучшению производительности canvas.

### Предварительно отрисуйте похожие примитивы или повторяющиеся объекты на offscreen canvas

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

```js
myCanvas.offscreenCanvas = document.createElement("canvas");
myCanvas.offscreenCanvas.width = myCanvas.width;
myCanvas.offscreenCanvas.height = myCanvas.height;

myCanvas.getContext("2d").drawImage(myCanvas.offScreenCanvas, 0, 0);
```

### Избегайте координат с плавающей точкой и используйте вместо них целые числа

Субпиксельный рендеринг происходит при рендеринге объектов на canvas без целых значений.

```js
ctx.drawImage(myImage, 0.3, 0.5);
```

Это заставляет браузер выполнять дополнительные вычисления для создания эффекта сглаживания. Чтобы избежать этого, обязательно округлите все координаты, используемые в вызовах `drawImage()`, например, с помощью `Math.floor()`.

### Не масштабируйте изображения в `drawImage`

При загрузке кешируйте изображения разных размеров на offscreen canvas, а не постоянно масштабируйте их в `drawImage()`.

### Используйте несколько слоёв canvas для сложных сцен

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

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

```html
<div id="stage">
  <canvas id="ui-layer" width="480" height="320"></canvas>
  <canvas id="game-layer" width="480" height="320"></canvas>
  <canvas id="background-layer" width="480" height="320"></canvas>
</div>

<style>
 #stage {
 width: 480px;
 height: 320px;
 position: relative;
 border: 2px solid black;
 }

 canvas {
 position: absolute;
 }
 #ui-layer {
 z-index: 3;
 }
 #game-layer {
 z-index: 2;
 }
 #background-layer {
 z-index: 1;
 }
</style>
```

### Используйте простой CSS для больших фоновых изображений

Если у вас есть статическое фоновое изображение, вы можете нарисовать его на простом элементе `<div>`, используя свойство CSS `background`, и расположить его под canvas. Это сведёт на нет необходимость рендеринга фона на canvas на каждом тике.

### Масштабирование холста с использованием CSS-преобразований

CSS-преобразования (en-US) быстрее, поскольку они используют графический процессор. В идеале, не стоит не масштабировать canvas, или можно использовать меньший canvas и увеличивать его при необходимости, но не уменьшать.

```js
var scaleX = window.innerWidth / canvas.width;
var scaleY = window.innerHeight / canvas.height;

var scaleToFit = Math.min(scaleX, scaleY);
var scaleToCover = Math.max(scaleX, scaleY);

stage.style.transformOrigin = "0 0"; //scale from top left
stage.style.transform = "scale(" + scaleToFit + ")";
```

### Отключите прозрачность

Если ваше приложение использует canvas и не нуждается в прозрачном фоне, установите для параметра `alpha` значение `false` при создании контекста рисования с помощью `HTMLCanvasElement.getContext()`. Эта информация может использоваться браузером для оптимизации рендеринга.

```js
var ctx = canvas.getContext("2d", { alpha: false });
```

### Другие советы

* Объединяйте запросы к canvas. Например, рисуйте одну ломанную линию вместо нескольких отдельных линий.
* Избегайте ненужных изменений состояния canvas.
* Отображайте только изменения экрана, а не заново перерисовывайте.
* По возможности избегайте свойства `shadowBlur` (en-US).
* Избегайте рендеринга текста, когда это возможно.
* Попробуйте разные способы очистки canvas ((`clearRect()`, или `fillRect()`, или изменение размера canvas).
* С анимациями используйте `window.requestAnimationFrame()` вместо `window.setInterval()` (en-US).
* Будьте осторожны с тяжёлыми физическими библиотеками.
Смотрите также
--------------

* Improving HTML5 Canvas Performance – HTML5 Rocks
* Optimizing your JavaScript game for Firefox OS – Mozilla Hacks
* « Предыдущая статья
* Следующая статья »


Рисование текста - Интерфейсы веб API

Рисование текста
================

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

После того, как мы увидели в предыдущей главе, как применять стили и цвета, взглянем на написание текста в canvas.

Рисование текста
----------------

Контекст рендеринга canvas предоставляет два метода для рисования текста:

`fillText(text, x, y [, maxWidth])`

Вставляет заданный текст в положении (x,y). Опционально может быть указана максимальная ширина.

`strokeText(text, x, y [, maxWidth])`

Вставляет контур заданного текста в положении (x,y). Опционально может быть указана максимальная ширина.

### Пример `fillText`

Текст вставлен с использованием текущего `fillStyle`.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.font = "48px serif";
  ctx.fillText("Hello world", 10, 50);
}
```

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

```
draw();
```

### Пример `strokeText`

Текст вставлен с использованием текущего `strokeStyle`.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.font = "48px serif";
  ctx.strokeText("Hello world", 10, 50);
}
```

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

```
draw();
```

Стилизация текста
-----------------

В примерах выше мы уже использовали свойство font для изменения размера текста. Кроме него существуют ещё несколько свойств, позволяющие настроить вывод текста на canvas:

`font = value`

Это основной стиль, который будет использоваться для вывода текста. Строка имеет такой же синтаксис, как CSS-свойство `font`. По умолчанию - sans-serif высотой 10px.

`textAlign = value`

Настройка выравнивания текста. Возможные значения: `start`, `end`, `left`, `right` или `center`. По умолчанию - `start`.

`textBaseline = value`

Настройка выравнивания текста по вертикали. Возможные значения: `top`, `hanging`, `middle`, `alphabetic`, `ideographic`, `bottom`. По умолчанию - `alphabetic`.

`direction = value` (en-US)

Направление текста. Возможные значения: `ltr`, `rtl`, `inherit`. По умолчанию - `inherit`.

Эти свойства могут быть вам знакомы если вы работали с CSS.

Изображение от WHATWG ниже показывает различные варианты свойства `textBaseline`.
 ![The top of the em square is
roughly at the top of the glyphs in a font, the hanging baseline is
where some glyphs like आ are anchored, the middle is half-way
between the top of the em square and the bottom of the em square,
the alphabetic baseline is where characters like Á, ÿ,
f, and Ω are anchored, the ideographic baseline is
where glyphs like 私 and 達 are anchored, and the bottom
of the em square is roughly at the bottom of the glyphs in a
font. The top and bottom of the bounding box can be far from these
baselines, due to glyphs extending far outside the em square.](http://www.whatwg.org/specs/web-apps/current-work/images/baselines.png)

### Пример textBaseline

Редактируя код ниже, вы можете видеть, как меняется отображение текста на canvas в реальном времени:

```js
ctx.font = "48px serif";
ctx.textBaseline = "hanging";
ctx.strokeText("Hello world!", 0, 100);
```

```
<canvas id="canvas" width="400" height="200" class="playable-canvas"></canvas>
<div class="playable-buttons">
  <input id="edit" type="button" value="Edit" />
  <input id="reset" type="button" value="Reset" />
</div>
<textarea id="code" class="playable-code">
ctx.font = "48px serif";
ctx.textBaseline = "hanging";
ctx.strokeText("Hello world", 0, 100);</textarea
>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var textarea = document.getElementById("code");
var reset = document.getElementById("reset");
var edit = document.getElementById("edit");
var code = textarea.value;

function drawCanvas() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  drawCanvas();
});

edit.addEventListener("click", function () {
  textarea.focus();
});

textarea.addEventListener("input", drawCanvas);
window.addEventListener("load", drawCanvas);
```

Измерение ширины текста
-----------------------

Для измерения ширины текста (без рисования его на canvas) можно воспользоваться следующим методом:

`measureText()`

Возвращает объект `TextMetrics` (en-US), содержащий ширину текста в пикселах, до отрисовки на canvas.

Пример ниже показывает, как можно измерить ширину текста.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var text = ctx.measureText("foo"); // TextMetrics object
  text.width; // 16;
}
```

Примечания
----------

В ранних версиях Gecko (движок рендеринга в Firefox, Firefox OS и других приложениях Mozilla) были реализованы методы API с префиксами для рисования текста на canvas. На данный момент они устарели и уже, возможно, удалены, поэтому их правильная работа не гарантируется.

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


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

Расширенные анимации
====================

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

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

Рисование мяча
--------------

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

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

Как обычно, нам нужен контекст рисования. Чтобы нарисовать шар, мы создадим объект `ball`, который содержит свойства и метод `draw()` , чтобы нарисовать его на `canvas`.

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

var ball = {
  x: 100,
  y: 100,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

ball.draw();
```

Здесь нет ничего особенного, шар на самом деле представляет собой простой круг и рисуется с помощью метода `arc()`.

Добавление скорости
-------------------

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

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

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

Границы
-------

Без какого-либо граничного коллизионного тестирования наш мяч быстро выбегает из холста. Нам нужно проверить, не находятся ли `x` и `y` положения шара вне размеров холста и не инвертируют направление векторов скорости. Для этого мы добавим следующие проверки в метод `draw` :

```js
if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
  ball.vy = -ball.vy;
}
if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
  ball.vx = -ball.vx;
}
```

### Первое демо

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

<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

Ускорение
---------

Чтобы сделать движение более реальным, вы можете играть со скоростью, например так:

```js
ball.vy \*= 0.99;
ball.vy += 0.25;
```

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

<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy \*= 0.99;
  ball.vy += 0.25;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

Скользящий эффект
-----------------

До сих пор мы использовали метод `clearRect`, когда очищали предыдущий кадр. Если заменить этот метод на `fillRect` с полу-прозрачным стилем, можно легко создать эффект скольжения.

```js
ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
```

```
<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 2,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function draw() {
  ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy \*= 0.99;
  ball.vy += 0.25;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mouseover", function (e) {
  raf = window.requestAnimationFrame(draw);
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
});

ball.draw();
```

Добавление управления мышью
---------------------------

Чтобы получить некоторый контроль над мячом, мы можем заставить его следовать за нашей мышью, например, с помощью события `mousemove` (en-US). Событие `click` (en-US) отпускает мяч и позволяет ему снова прыгать.
```

<canvas id="canvas" style="border: 1px solid" width="600" height="300"></canvas>
```

```js
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var running = false;

var ball = {
  x: 100,
  y: 100,
  vx: 5,
  vy: 1,
  radius: 25,
  color: "blue",
  draw: function () {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI \* 2, true);
    ctx.closePath();
    ctx.fillStyle = this.color;
    ctx.fill();
  },
};

function clear() {
  ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function draw() {
  clear();
  ball.draw();
  ball.x += ball.vx;
  ball.y += ball.vy;

  if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
    ball.vy = -ball.vy;
  }
  if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
    ball.vx = -ball.vx;
  }

  raf = window.requestAnimationFrame(draw);
}

canvas.addEventListener("mousemove", function (e) {
  if (!running) {
    clear();
    ball.x = e.clientX;
    ball.y = e.clientY;
    ball.draw();
  }
});

canvas.addEventListener("click", function (e) {
  if (!running) {
    raf = window.requestAnimationFrame(draw);
    running = true;
  }
});

canvas.addEventListener("mouseout", function (e) {
  window.cancelAnimationFrame(raf);
  running = false;
});

ball.draw();
```

Переместите шар с помощью мыши и отпустите его одним щелчком.

Breakout(арканоид)
------------------

В этой короткой главе описаны некоторые приёмы создания продвинутой анимации. Как насчёт того, что бы добавить доску, кирпичи и превратить это демо в игру Breakout(в Росси более известный клон этой игры - арканоид)? Посетите Game development чтобы узнать больше об играх.

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

* `window.requestAnimationFrame()`
* Efficient animation for web games (en-US)
* « Предыдущая статья
* Следующая статья »


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

Transformations
===============

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

Ранее в этом уроке мы узнали о сетке холста и **координатном пространстве**. До сих пор мы использовали только сетку по умолчанию и изменили размер всего холста для наших нужд. При преобразованиях существуют более мощные способы изменения исходных координат в различные положение, поворот сетки и даже масштабирование.

Сохранение и восстановление состояния
-------------------------------------

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

`save()`

Сохраняет все состояние холста.

`restore()`

Восстанавливает последнее сохранённое состояние холста.

Состояние холста сохраняется в стеке. Каждый раз, когда вызывается метод `save()`, текущее состояние отрисовки записывается в стек. Состояние отрисовки содержит:

* Трансформации, которые были применены (например, `translate`, `rotate` and `scale` – см. ниже).
* Текущее значение следующих атрибутов: `strokeStyle`, `fillStyle`, `globalAlpha`, `lineWidth`, `lineCap`, `lineJoin`, `miterLimit`, `lineDashOffset`, `shadowOffsetX` (en-US), `shadowOffsetY` (en-US), `shadowBlur` (en-US), `shadowColor` (en-US), `globalCompositeOperation` (en-US), `font`, `textAlign`, `textBaseline`, `direction` (en-US), `imageSmoothingEnabled` (en-US).
* Текущее значение границ вырезанного холста (clipping path), которые будут рассматриваться в следующем разделе.

Вы можете вызывать метод `save()` столько раз, сколько захотите. В то же время, при вызове метода `restore()` последнее сохранённое состояние будет считано из стека, и все сохранённые настройки будут восстановлены.

### Пример сохранения и восстановления состояния холста

Здесь показано, как функционирует сохранение в стек состояния отрисовки на примере последовательной отрисовки набора прямоугольников.

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

  ctx.fillRect(0, 0, 150, 150); // рисуем прямоугольник с настройками по умолчанию
  ctx.save(); // сохраняем состояние

  ctx.fillStyle = "#09F"; // вносим изменения в настройки
  ctx.fillRect(15, 15, 120, 120); // рисуем прямоугольник с новыми настройками
  ctx.save(); // сохраняем состояние

  ctx.fillStyle = "#FFF"; // вносим изменения в настройки
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30, 30, 90, 90); // рисуем прямоугольник с новыми настройками

  ctx.restore(); // возвращаемся к предыдущим настройкам
  ctx.fillRect(45, 45, 60, 60); // рисуем прямоугольник с восстановленными настройками

  ctx.restore(); // возвращаемся к начальным настройкам
  ctx.fillRect(60, 60, 30, 30); // рисуем прямоугольник с изначальными настройками
}
```

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

```
draw();
```

Сначала рисуется большой прямоугольник с настройками по умолчанию. Затем мы сохраняем состояние холста, после чего изменяем цвет заливки. Затем рисуем второй синий прямоугольник меньшего размера и опять сохраняем состояние. Снова изменяем какие-то настройки и рисуем третий полупрозрачный белый прямоугольник.

До сих пор наши действия ничем не отличались от тех, что мы делали в предыдущем разделе. Однако, как только мы сделали первый вызов `restore(),` последнее сохранённое состояние отрисовки было восстановлено из стека, вернув все сохранённые настройки. Если бы мы не сохранили предыдущее состояние, используя `save()`, нам бы пришлось менять цвет заливки и настройки прозрачности вручную для возврата к предыдущему состоянию. Для каких-нибудь двух простых свойств это, может быть, сделать не так сложно. Но если таких свойств гораздо больше, это чревато очень быстрым разрастанием кода.

Когда второй вызов `restore()` сделан, изначальное состояние (то самое, которое было сделано перед первым вызовом `save`) восстанавливается и последний нарисованный прямоугольник вновь становится чёрным.

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

Трансляция (смещение)
---------------------

![](/ru/docs/Web/API/Canvas_API/Tutorial/Transformations/canvas_grid_translate.png)Первый метод для трансформирования холста `translate()`. Он используется для перемещения холста в любую точку нашей сетки.

`translate(x, y)` (en-US)

Перемещение холста на сетке. `x` и `y` - смещение по горизонтали и вертикали соответственно.

Неплохая идея - сохранять `canvas state` перед использованием любых трансформаций. Обычно удобнее использовать метод `restore`, чем выполнять обратные преобразования, чтобы вернуться к начальному состоянию. Кроме того, если вы выполняете преобразования внутри цикла не используя `save` и `restore`, вы рискуете потерять часть рисунка, потому что он был нарисован за пределами края холста.

### Пример использования `translate`

Этот пример демонстрирует некоторые преимущества при использовании смещения холста. Без использования метода `translate()` все прямоугольники будут отрисованы в одинаковой позиции (0,0). Метод `translate()` даёт возможность размещения прямоугольника в любой позиции без изменения параметров функции `fillRect()`. Это может дать некоторое упрощение для понимания и использования.

Внутри функции `draw()` мы вызываем `fillRect()` девять раз, используя два цикла `for`. Каждый раз мы сохраняем состояние холста, смещаем его, рисуем прямоугольник, а затем восстанавливаем исходное состояние. Заметьте, что `fillRect()` всегда использует одни и те же параметры, а изменение позиции фигуры осуществляется с помощью `translate()`.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 3; j++) {
      ctx.save();
      ctx.fillStyle = "rgb(" + 51 \* i + ", " + (255 - 51 \* i) + ", 255)";
      ctx.translate(10 + j \* 50, 10 + i \* 50);
      ctx.fillRect(0, 0, 25, 25);
      ctx.restore();
    }
  }
}
```

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

```
draw();
```

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

Поворот
-------

![](/ru/docs/Web/API/Canvas_API/Tutorial/Transformations/canvas_grid_rotate.png)Второй метод трансформации `rotate()`. Он используется для поворота нашего холста.

`rotate(angle)`

Поворачивает наш холст по часовой стрелке вокруг начальной точки на угол `anglе` в радианах.

Центр поворота - всегда начало координат. Для изменения координат центра мы должны сместить холст, используя метод `translate()`.

### Пример использования `rotate`

В этом примере мы сначала используем `rotate()` для поворота прямоугольника относительно начала координат, а затем, используя `translate()` совместно с `rotate()` поворачиваем прямоугольник относительно его центра.

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

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

  // left rectangles, rotate from canvas origin
  ctx.save();
  // blue rect
  ctx.fillStyle = "#0095DD";
  ctx.fillRect(30, 30, 100, 100);
  ctx.rotate((Math.PI / 180) \* 25);
  // grey rect
  ctx.fillStyle = "#4D4E53";
  ctx.fillRect(30, 30, 100, 100);
  ctx.restore();

  // right rectangles, rotate from rectangle center
  // draw blue rect
  ctx.fillStyle = "#0095DD";
  ctx.fillRect(150, 30, 100, 100);

  ctx.translate(200, 80); // translate to rectangle center
  // x = x + 0.5 \* width
  // y = y + 0.5 \* height
  ctx.rotate((Math.PI / 180) \* 25); // rotate
  ctx.translate(-200, -80); // translate back

  // draw grey rect
  ctx.fillStyle = "#4D4E53";
  ctx.fillRect(150, 30, 100, 100);
}
```

Для поворота прямоугольника относительно его центра мы сначала смещаем начало координат, выполняем поворот, а затем выполняем обратное смещение к точке 0,0, и наконец рисуем прямоугольник.
```

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

```
draw();
```

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

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

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

`scale(x, y)` (en-US)

Масштабирует координатную сетку холста по горизонтали и вертикали. Оба параметра - вещественные числа. Значения меньше 1.0 уменьшают, а больше 1.0 увеличивают масштаб сетки. Значение 1.0 не изменяет его.

Используя отрицательные значения вы можете зеркально отразить направление осей. Например, используя `translate(0,canvas.height); scale(1,-1);` вы получите хорошо известную декартову систему координат с началом в нижнем левом углу.

По умолчанию единица координатной сетки точно соответствует одному пикселю. Если же вы, например, зададите масштабный коэффициент 0.5, то единица сетки будет равна половине пикселя, и нарисованная фигура будет иметь размер в два раза меньше оригинала. Наоборот, если задать масштабный коэффициент 2.0, единица сетки будет соответствовать двум пикселям, а нарисованная фигура станет в два раза больше.

### Пример использования `scale`

В этом примере мы нарисуем прямоугольники, используя разные масштабные коэффициенты.

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

  // рисуем масштабированный прямоугольник.
  ctx.save();
  ctx.scale(10, 3);
  ctx.fillRect(1, 10, 10, 10);
  ctx.restore();

  // размещаем текст, отражённый по горизонтали
  ctx.scale(-1, 1);
  ctx.font = "48px serif";
  ctx.fillText("MDN", -135, 120);
}
```

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

```
draw();
```

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

Матричное преобразование
------------------------

В заключении рассмотрим метод, который вызывает изменения в соответствии с матрицей преобразования.

`transform(a, b, c, d, e, f)` (en-US)

Накладывает матрицу преобразования, заданную параметрами, на текущую матрицу. Матрица преобразования задаётся следующим образом:

[

a

c

e

b

d

f

0

0

1

]

\left[ \begin{array}{ccc} a & c & e \ b & d & f \ 0 & 0 & 1 \end{array} \right]

If any of the arguments are `Infinity` the transformation matrix must be marked as infinite instead of the method throwing an exception.

Параметры функции:

`a (m11)`

Horizontal scaling.

*`b (m12)`*

Horizontal skewing.

`c (m21)`

Vertical skewing.

`d (m22)`

Vertical scaling.

`e (dx)`

Horizontal moving.

`f (dy)`

Vertical moving.

`setTransform(a, b, c, d, e, f)` (en-US)

Сбрасывает текущую матрицу преобразования, а затем вызывает `transform()` в соответствии с аргументами.

`resetTransform()` (en-US)

Сбрасывает текущую матрицу преобразования к значению по умолчанию. Аналогично вызову `ctx.setTransform(1, 0, 0, 1, 0, 0);`

### Пример использования `transform` и `setTransform`

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

  var sin = Math.sin(Math.PI / 6);
  var cos = Math.cos(Math.PI / 6);
  ctx.translate(100, 100);
  var c = 0;
  for (var i = 0; i <= 12; i++) {
    c = Math.floor((255 / 12) \* i);
    ctx.fillStyle = "rgb(" + c + ", " + c + ", " + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }

  ctx.setTransform(-1, 0, 0, 1, 100, 100);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}
```

```
<canvas id="canvas" width="200" height="250"></canvas>
```

```
draw();
```

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

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


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

Применение стилей и цветов
==========================

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

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

Цвета
-----

До сих пор мы видели только методы рисования контекста. Если мы хотим применить цвета к фигуре, то есть два важных свойства, которые мы можем использовать: `fillStyle` и `strokeStyle`.

`fillStyle = color`

Устанавливает стиль для фона фигур.

`strokeStyle = color`

Устанавливает стиль контура фигуры.

*`color`* может быть цветом, (строка, представленная в CSS `<color>`), градиентом или паттерном. Градиенты и паттерны мы рассмотрим позже. По умолчанию цвет фона и контура — чёрный (значение CSS цвета `#000000`).

**Примечание:** Когда вы устанавливаете значения `strokeStyle` и/или `fillStyle`, то новое значение становится стандартным для всех фигур, которые будут нарисованы с этого момента. Когда вам нужен другой цвет, вы должны перезаписать значение в `fillStyle` или в `strokeStyle` для каждой фигуры.

Чтобы строка *`color`* считалась валидной, она должна соответствовать CSS `<color>`. Далее приведены примеры того, как можно по-разному задать один и тот же цвет.

```js
// these all set the fillStyle to 'orange'

ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";
```

### Пример `fillStyle`

В этом примере мы опять воспользуемся двойным циклом, чтобы нарисовать сетку из прямоугольников, каждый из которых имеет свой цвет. Окончательное изображение должно иметь вид, как показано на скриншоте. Здесь не происходит ничего сверхъестественного. Мы используем две переменные `i` и `j` для генерации уникального RGB цвета для каждого квадрата и изменяем только красные и зелёные значения. Синий канал представляет собой фиксированное значение. Путём изменения каналов вы можете генерировать всю палитру. Увеличив количество шагов вы можете достигнуть такого вида палитры, какая используется в Photoshop.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  for (var i = 0; i < 6; i++) {
    for (var j = 0; j < 6; j++) {
      ctx.fillStyle =
        "rgb(" +
        Math.floor(255 - 42.5 \* i) +
        "," +
        Math.floor(255 - 42.5 \* j) +
        ",0)";
      ctx.fillRect(j \* 25, i \* 25, 25, 25);
    }
  }
}
```

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

```
draw();
```

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

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

### Пример `strokeStyle`

Этот пример похож на предыдущий, но мы используем свойство `strokeStyle` чтобы изменить цвета очертаний фигур. Так же мы используем метод `arc()` для рисования окружностей вместо квадратов.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  for (var i = 0; i < 6; i++) {
    for (var j = 0; j < 6; j++) {
      ctx.strokeStyle =
        "rgb(0," +
        Math.floor(255 - 42.5 \* i) +
        "," +
        Math.floor(255 - 42.5 \* j) +
        ")";
      ctx.beginPath();
      ctx.arc(12.5 + j \* 25, 12.5 + i \* 25, 10, 0, Math.PI \* 2, true);
      ctx.stroke();
    }
  }
}
```

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

```
draw();
```

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

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

Прозрачность
------------

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

`globalAlpha = transparencyValue`

Для применения, указывается значения прозрачности для всех будущих фигур, что будут нарисованы на canvas. Значение полупрозрачности могут быть между 0.0 (полная прозрачность) и 1.0 (полная непрозрачность). Значение 1.0 (полная непрозрачность) установлено по умолчанию.

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

Так как свойства `strokeStyle` и `fillStyle` принимают цветовые значения rgba через CSS, мы можем использовать следующее обозначение для назначения прозрачных цветов.

```js
// Assigning transparent colors to stroke and fill style

ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";
```

Функция `rgba()` похожа на функцию `rgb()`, но имеет один дополнительный параметр. Последний параметр устанавливает значение прозрачности для конкретного цвета. Действующий диапазон значений находится между 0.0 (полная прозрачность) и 1.0 (полная непрозрачность).

### Пример `globalAlpha`

В данном примере мы нарисуем фон и четыре квадрата с различными цветами. Сверху изображения будет выведен набор полупрозрачных кругов. Установим свойство `globalAlpha` значением 0.2, которое будет использовано для всех последующих форм. Каждый шаг цикла рисует круг с большим радиусом. По окончанию получим радиальный градиент. Накладывая ещё больше кругов друг на друга, мы фактически сможем уменьшить прозрачность ранее нарисованных кругов. Увеличив счётчик итераций, при этом рисуя ещё круги, мы сможем добиться исчезновение центра изображения.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  // фон изображения
  ctx.fillStyle = "#FD0";
  ctx.fillRect(0, 0, 75, 75);
  ctx.fillStyle = "#6C0";
  ctx.fillRect(75, 0, 75, 75);
  ctx.fillStyle = "#09F";
  ctx.fillRect(0, 75, 75, 75);
  ctx.fillStyle = "#F30";
  ctx.fillRect(75, 75, 75, 75);
  ctx.fillStyle = "#FFF";

  // устанавливаем значение прозрачности
  ctx.globalAlpha = 0.2;

  // Рисуем полупрозрачные круги
  for (i = 0; i < 7; i++) {
    ctx.beginPath();
    ctx.arc(75, 75, 10 + 10 \* i, 0, Math.PI \* 2, true);
    ctx.fill();
  }
}
```

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

```
draw();
```

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

### Пример использования `rgba()`

В этом втором примере мы делаем что-то похожее на предыдущее, но вместо рисования кругов друг над другом, я рисовал маленькие прямоугольники с увеличением непрозрачности. Использование `rgba()` добавляет контроля и гибкости, поскольку мы можем индивидуально настраивать стиль заливки и штриха.

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

  // Нарисовать фон
  ctx.fillStyle = "rgb(255,221,0)";
  ctx.fillRect(0, 0, 150, 37.5);
  ctx.fillStyle = "rgb(102,204,0)";
  ctx.fillRect(0, 37.5, 150, 37.5);
  ctx.fillStyle = "rgb(0,153,255)";
  ctx.fillRect(0, 75, 150, 37.5);
  ctx.fillStyle = "rgb(255,51,0)";
  ctx.fillRect(0, 112.5, 150, 37.5);

  // Нарисовать полупрозрачные прямоугольники
  for (var i = 0; i < 10; i++) {
    ctx.fillStyle = "rgba(255,255,255," + (i + 1) / 10 + ")";
    for (var j = 0; j < 4; j++) {
      ctx.fillRect(5 + i \* 14, 5 + j \* 37.5, 14, 27.5);
    }
  }
}
```

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

```
draw();
```

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

Стили линий
-----------

Есть несколько свойств, которые позволяют нам стилизовать линии.

`lineWidth = value`

Устанавливает ширину линий, рисуемых в будущем.

`lineCap = type`

Устанавливает внешний вид концов линий.

`lineJoin = type`

Устанавливает внешний вид «углов», где встречаются линии.

`miterLimit = value`

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

`getLineDash()`

Возвращает текущий массив тире штриховки, содержащий чётное число неотрицательных чисел.

`setLineDash(segments)` (en-US)

Устанавливает текущий пунктир линии.

`lineDashOffset = value`

Указывает, где следует начинать тире массива в строке.

Вы лучше поймёте, что они делают, глядя на приведённые ниже примеры.

### Пример `lineWidth`

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

Ширина линии - это толщина хода, центрированного по данному пути. Другими словами, область, которая нарисована, простирается до половины ширины линии по обе стороны пути. Поскольку координаты холста не напрямую ссылаются на пиксели, особое внимание следует уделять получению чётких горизонтальных и вертикальных линий.

В приведённом ниже примере 10 прямых линий рисуются с увеличением ширины линий. Линия в крайнем левом углу - 1.0 единицы. Тем не менее, толщина левой и всех других линий нечётной ширины не выглядят чёткими из-за позиционирования пути.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  for (var i = 0; i < 10; i++) {
    ctx.lineWidth = 1 + i;
    ctx.beginPath();
    ctx.moveTo(5 + i \* 14, 5);
    ctx.lineTo(5 + i \* 14, 140);
    ctx.stroke();
  }
}
```

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

```
draw();
```

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

Получение чётких строк требует понимания путей сглаживания. На рисунках ниже представлена сетка координат холста. Квадраты между сетками являются фактическими экранными пикселями. В первом изображении сетки ниже прямоугольник от (2, 1) до (5, 5) заполняется. Вся область между ними (светло-красный) падает на границы пикселей, поэтому полученный заполненный прямоугольник будет иметь чёткие края.

![](/ru/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors/canvas-grid.png)

Если вы рассмотрите путь от (3, 1) до (3, 5) с толщиной строки `1.0`, вы получите ситуацию во втором изображении. Фактическая заполняемая область, (синяя), распространяется только наполовину в пикселях по обе стороны пути. Приблизительно это означает, что частично затенённые пиксели приводят к заполнению всей области (светло-голубой и синей) цветом, только наполовину темным, чем фактический цвет штриха. Это то, что происходит с линией шириной `1.0` в предыдущем примере кода.

Чтобы исправить это, вы должны быть более точными при создании пути. Зная, что линия шириной `1.0` занимает половину единицы по обе стороны пути, создание пути от (3.5, 1) до (3.5, 5) приведёт к ситуации в третьем изображении - ширина линии `1.0` закончится верно, точно заполняя вертикальную линию с одним пикселем.

**Примечание:** Имейте в виду, что в нашем примере с вертикальной линией позиция Y по-прежнему ссылается на целочисленную позицию сетки - иначе мы увидели бы пиксели с половинным охватом в конечных точках (также обратите внимание, что это поведение зависит от текущего стиля `lineCap`, значение по умолчанию - `butt`; вы можете вычислить согласованные штрихи с полупиксельными координатами для линий с нечётной шириной, установив стиль `lineCap` в `square`, чтобы внешняя граница вокруг конечной точки линии автоматически расширялась, охватывая весь пиксель в точку).

Также обратите внимание, что затронуты только начальные и конечные точки пути: если путь закрыт с помощью `closePath()`, - нет начальной и конечной точки; вместо этого все конечные точки в пути подключены к их прикреплённому предыдущему и следующему сегментам и при текущей настройке стиля `lineJoin` в значении по умолчанию - `miter`, с эффектом автоматического расширения внешних границ подключённых сегментов до их точки пересечения - обработанный ход будет точно покрывать полные пиксели с центром в каждой конечной точке, если эти связанные сегменты горизонтальны и/или вертикальны). См. следующие два раздела, демонстрирующие эти дополнительные стили.

Для линий с чётной шириной каждая половина заканчивается как целое количество пикселей, поэтому вам нужен путь, который находится между пикселями (то есть (3,1) - (3,5)), вместо середины пикселей.

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

### Пример `lineCap`

Свойство `lineCap` определяет, как выводятся конечные точки каждой строки. Для этого свойства есть три возможных значения: `butt`, `round` и `square`. По умолчанию для этого свойства установлено значение `butt`.

![](/ru/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors/canvas_linecap.png)

`butt`

Концы линий соответствуют крайним точкам.

`round`

Концы линий округлены.

`square`

Концы линий описаны квадратом с равной шириной и половиной высоты толщины линии.

В этом примере мы проведём три строки, каждая из которых имеет другое значение для свойства `lineCap`. Я также добавил два руководства, чтобы увидеть точные различия между ними. Каждая из этих линий начинается и заканчивается именно на этих направляющих.

Строка слева использует `butt` опцию по умолчанию. Вы заметите, что она полностью очищена от направляющих. Второй вариант - `round` опция. Это добавляет полукруг к концу, который имеет радиус, равный половине ширины линии. Строка справа использует `square` опцию. Это добавляет поле с равной шириной и половиной высоты толщины линии.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var lineCap = ["butt", "round", "square"];

  // Draw guides
  ctx.strokeStyle = "#09f";
  ctx.beginPath();
  ctx.moveTo(10, 10);
  ctx.lineTo(140, 10);
  ctx.moveTo(10, 140);
  ctx.lineTo(140, 140);
  ctx.stroke();

  // Draw lines
  ctx.strokeStyle = "black";
  for (var i = 0; i < lineCap.length; i++) {
    ctx.lineWidth = 15;
    ctx.lineCap = lineCap[i];
    ctx.beginPath();
    ctx.moveTo(25 + i \* 50, 10);
    ctx.lineTo(25 + i \* 50, 140);
    ctx.stroke();
  }
}
```

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

```
draw();
```

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

### Пример `lineJoin`

Свойство `lineJoin` определяет, как соединяются два сегмента (линий, дуг или кривых) с ненулевой длиной в форме (вырожденные сегменты с нулевой длиной, заданные конечные точки и контрольные точки находятся точно в том же положении - пропущены).

Для этого свойства есть три возможных значения: `round`, `bevel` и `miter`. По умолчанию для этого свойства установлено значение `miter`. Обратите внимание, что настройка `lineJoin` не действует, если два связанных сегмента имеют одно и то же направление, потому что в этом случае не будет добавлена область соединения.

![](/ru/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors/canvas_linejoin.png)

`round`

Радиус заполняемой части для скруглённых углов равен половине ширины линии. центр этого радиуса совпадает с концами подключённых сегментов.

`bevel`

Заполняет дополнительную треугольную область между общей конечной точкой подключённых сегментов и отдельными внешними прямоугольными углами каждого сегмента.

`miter`

Подключённые сегменты соединяются путём расширения их внешних краёв для соединения в одной точке с эффектом заполнения дополнительной области в форме пастилки. Эта настройка выполняется с помощью свойства `miterLimit`, которое объясняется ниже.

В приведённом ниже примере показаны три разных пути, демонстрирующие каждый из этих трёх свойств `lineJoin`; результат - выше.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  var lineJoin = ["round", "bevel", "miter"];
  ctx.lineWidth = 10;
  for (var i = 0; i < lineJoin.length; i++) {
    ctx.lineJoin = lineJoin[i];
    ctx.beginPath();
    ctx.moveTo(-5, 5 + i \* 40);
    ctx.lineTo(35, 45 + i \* 40);
    ctx.lineTo(75, 5 + i \* 40);
    ctx.lineTo(115, 45 + i \* 40);
    ctx.lineTo(155, 5 + i \* 40);
    ctx.stroke();
  }
}
```

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

```
draw();
```

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

### Демонстрация свойства `miterLimit`

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

Свойство `miterLimit` определяет, как далеко можно установить внешнюю точку соединения из внутренней точки подключения. Если две линии превышают это значение, вместо этого получается привязка конуса. Обратите внимание, что максимальная длина митра является произведением ширины линии, измеренной в текущей системе координат, значением этого свойства `miterLimit` (значение по умолчанию 10,0 в HTML `<canvas>`), поэтому `miterLimit` может устанавливаться независимо от текущей шкалы дисплея или любых аффинных преобразований путей: она влияет только на эффективно визуализированную форму рёбер линии.

Точнее, предел митры является максимально допустимым отношением длины расширения (в холсте HTML он измеряется между внешним углом соединённых краёв линии и общей конечной точкой соединительных сегментов, указанными на пути), до половины ширины линии. Его можно равнозначно определить как максимально допустимое отношение расстояния между внутренней и внешней точками перехода краёв к общей ширине линии. Затем он равен косекансу с половиной минимального внутреннего угла соединительных сегментов, ниже которого не будет создано ни одного соединения митра, а только скос соединяется:

* `miterLimit` = **max** `miterLength` / `lineWidth` = 1 / **sin** ( **min** *θ* / 2 )
* Предел митры по умолчанию, равный 10,0, разделит все митры углов, острее примерно 11 градусов.
* Предел митры, равный √2 ≈ 1.4142136 (rounded up) сгладит миты для всех острых углов, поддерживая митры только для тупых или прямых углов.
* Предел митры, равный 1,0, действителен, но отключит все миты.
* Значения ниже 1.0 являются недопустимыми для предела митры.

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

Если вы укажете в этой демонстрации значение `miterLimit` ниже 4.2, ни один из видимых углов не присоединится к расширению митры, но только с небольшим скосом рядом с синими линиями; с отметкой `miterLimit` выше 10, большинство углов в этой демонстрации должны соединяться с митрой, удалённой от синих линий, высота которой уменьшается между углами слева направо, потому что они соединяются с растущими углами; с промежуточными значениями углы с левой стороны будут соединяться только с скосом рядом с синими линиями, а углы с правой стороны с удлинителем митры (также с уменьшающейся высотой).

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

  // Clear canvas
  ctx.clearRect(0, 0, 150, 150);

  // Draw guides
  ctx.strokeStyle = "#09f";
  ctx.lineWidth = 2;
  ctx.strokeRect(-5, 50, 160, 50);

  // Set line styles
  ctx.strokeStyle = "#000";
  ctx.lineWidth = 10;

  // check input
  if (document.getElementById("miterLimit").value.match(/\d+(\.\d+)?/)) {
    ctx.miterLimit = parseFloat(document.getElementById("miterLimit").value);
  } else {
    alert("Value must be a positive number");
  }

  // Draw lines
  ctx.beginPath();
  ctx.moveTo(0, 100);
  for (i = 0; i < 24; i++) {
    var dy = i % 2 == 0 ? 25 : -25;
    ctx.lineTo(Math.pow(i, 1.5) \* 2, 75 + dy);
  }
  ctx.stroke();
  return false;
}
```

```
<table>
  <tr>
    <td><canvas id="canvas" width="150" height="150"></canvas></td>
    <td>
      Change the <code>miterLimit</code> by entering a new value below and
      clicking the redraw button.<br /><br />
      <form onsubmit="return draw();">
        <label>Miter limit</label>
        <input type="text" size="3" id="miterLimit" />
        <input type="submit" value="Redraw" />
      </form>
    </td>
  </tr>
</table>
```

```
document.getElementById("miterLimit").value = document
  .getElementById("canvas")
  .getContext("2d").miterLimit;
draw();
```

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

### Использование штрихов

Метод setLineDash и свойство lineDashOffset задают шаблон штрихов для линий. Метод setLineDash принимает список чисел, который определяет расстояния для попеременного рисования линии и разрыва, а свойство lineDashOffset устанавливает смещение, с которого начинается шаблон.

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

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

```js
var ctx = document.getElementById("canvas").getContext("2d");
var offset = 0;

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.setLineDash([4, 2]);
  ctx.lineDashOffset = -offset;
  ctx.strokeRect(10, 10, 100, 100);
}

function march() {
  offset++;
  if (offset > 16) {
    offset = 0;
  }
  draw();
  setTimeout(march, 20);
}

march();
```

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

Градиенты
---------

Just like any normal drawing program, we can fill and stroke shapes using linear and radial gradients. We create a `CanvasGradient` object by using one of the following methods. We can then assign this object to the `fillStyle` or `strokeStyle` properties.

`createLinearGradient(x1, y1, x2, y2)`

Creates a linear gradient object with a starting point of (`x1`, `y1`) and an end point of (`x2`, `y2`).

`createRadialGradient(x1, y1, r1, x2, y2, r2)` (en-US)

Creates a radial gradient. The parameters represent two circles, one with its center at (`x1`, `y1`) and a radius of `r1`, and the other with its center at (`x2`, `y2`) with a radius of `r2`.

For example:

```js
var lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
var radialgradient = ctx.createRadialGradient(75, 75, 0, 75, 75, 100);
```

Once we've created a `CanvasGradient` object we can assign colors to it by using the `addColorStop()` method.

`gradient.addColorStop(position, color)` (en-US)

Creates a new color stop on the `gradient` object. The `position` is a number between 0.0 and 1.0 and defines the relative position of the color in the gradient, and the `color` argument must be a string representing a CSS `<color>`, indicating the color the gradient should reach at that offset into the transition.

You can add as many color stops to a gradient as you need. Below is a very simple linear gradient from white to black.

```js
var lineargradient = ctx.createLinearGradient(0, 0, 150, 150);
lineargradient.addColorStop(0, "white");
lineargradient.addColorStop(1, "black");
```

### Пример `createLinearGradient`

In this example, we'll create two different gradients. As you can see here, both the `strokeStyle` and `fillStyle` properties can accept a `canvasGradient` object as valid input.

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

  // Create gradients
  var lingrad = ctx.createLinearGradient(0, 0, 0, 150);
  lingrad.addColorStop(0, "#00ABEB");
  lingrad.addColorStop(0.5, "#fff");
  lingrad.addColorStop(0.5, "#26C000");
  lingrad.addColorStop(1, "#fff");

  var lingrad2 = ctx.createLinearGradient(0, 50, 0, 95);
  lingrad2.addColorStop(0.5, "#000");
  lingrad2.addColorStop(1, "rgba(0,0,0,0)");

  // assign gradients to fill and stroke styles
  ctx.fillStyle = lingrad;
  ctx.strokeStyle = lingrad2;

  // draw shapes
  ctx.fillRect(10, 10, 130, 130);
  ctx.strokeRect(50, 50, 50, 50);
}
```

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

```
draw();
```

The first is a background gradient. As you can see, we assigned two colors at the same position. You do this to make very sharp color transitions—in this case from white to green. Normally, it doesn't matter in what order you define the color stops, but in this special case, it does significantly. If you keep the assignments in the order you want them to appear, this won't be a problem.

In the second gradient, we didn't assign the starting color (at position 0.0) since it wasn't strictly necessary, because it will automatically assume the color of the next color stop. Therefore, assigning the black color at position 0.5 automatically makes the gradient, from the start to this stop, black.

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

### Пример `createRadialGradient`

In this example, we'll define four different radial gradients. Because we have control over the start and closing points of the gradient, we can achieve more complex effects than we would normally have in the "classic" radial gradients we see in, for instance, Photoshop (that is, a gradient with a single center point where the gradient expands outward in a circular shape).

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

  // Create gradients
  var radgrad = ctx.createRadialGradient(45, 45, 10, 52, 50, 30);
  radgrad.addColorStop(0, "#A7D30C");
  radgrad.addColorStop(0.9, "#019F62");
  radgrad.addColorStop(1, "rgba(1,159,98,0)");

  var radgrad2 = ctx.createRadialGradient(105, 105, 20, 112, 120, 50);
  radgrad2.addColorStop(0, "#FF5F98");
  radgrad2.addColorStop(0.75, "#FF0188");
  radgrad2.addColorStop(1, "rgba(255,1,136,0)");

  var radgrad3 = ctx.createRadialGradient(95, 15, 15, 102, 20, 40);
  radgrad3.addColorStop(0, "#00C9FF");
  radgrad3.addColorStop(0.8, "#00B5E2");
  radgrad3.addColorStop(1, "rgba(0,201,255,0)");

  var radgrad4 = ctx.createRadialGradient(0, 150, 50, 0, 140, 90);
  radgrad4.addColorStop(0, "#F4F201");
  radgrad4.addColorStop(0.8, "#E4C700");
  radgrad4.addColorStop(1, "rgba(228,199,0,0)");

  // draw shapes
  ctx.fillStyle = radgrad4;
  ctx.fillRect(0, 0, 150, 150);
  ctx.fillStyle = radgrad3;
  ctx.fillRect(0, 0, 150, 150);
  ctx.fillStyle = radgrad2;
  ctx.fillRect(0, 0, 150, 150);
  ctx.fillStyle = radgrad;
  ctx.fillRect(0, 0, 150, 150);
}
```

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

```
draw();
```

In this case, we've offset the starting point slightly from the end point to achieve a spherical 3D effect. It's best to try to avoid letting the inside and outside circles overlap because this results in strange effects which are hard to predict.

The last color stop in each of the four gradients uses a fully transparent color. If you want to have a nice transition from this to the previous color stop, both colors should be equal. This isn't very obvious from the code because it uses two different CSS color methods as a demonstration, but in the first gradient `#019F62 = rgba(1,159,98,1)`.

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

Шаблоны
-------

В одном из предыдущих примеров мы использовали несколько циклов, чтобы создать шаблон из повторяющихся изображений. Однако, есть более простой способ сделать подобное - метод `createPattern()`.

`createPattern(image, type)` (en-US)

Создаёт и возвращает новый canvas объект - шаблон (pattern). `image` - `CanvasImageSource` (en-US) (то есть `HTMLImageElement`, другой холст, элемент `<video>` или подобный объект. `type` - строка, указывающая, как использовать `image`.

Тип указывает, как использовать image для создания шаблона и должен быть одним из следующих значений:

`repeat`

Повторяет изображение в вертикальном и горизонтальном направлениях.

`repeat-x`

Повторяет изображение по горизонтали, но не по вертикали.

`repeat-y`

Повторяет изображение по вертикали, но не по горизонтали.

`no-repeat`

Не повторяет изображение. Используется только один раз.

Мы используем этот метод, чтобы создать `CanvasPattern` объект, который очень похож на методы градиента, рассмотренные ранее. Как только мы создали шаблон, мы можем назначить ему свойства `fillStyle` или `strokeStyle`. Например:

```js
var img = new Image();
img.src = "someimage.png";
var ptrn = ctx.createPattern(img, "repeat");
```

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

### Пример `createPattern`

In this last example, we'll create a pattern to assign to the `fillStyle` property. The only thing worth noting is the use of the image's `onload` handler. This is to make sure the image is loaded before it is assigned to the pattern.

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

  // create new image object to use as pattern
  var img = new Image();
  img.src = "canvas\_createpattern.png";
  img.onload = function () {
    // create pattern
    var ptrn = ctx.createPattern(img, "repeat");
    ctx.fillStyle = ptrn;
    ctx.fillRect(0, 0, 150, 150);
  };
}
```

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

```
draw();
```

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

Тени
----

Using shadows involves just four properties:

`shadowOffsetX = float` (en-US)

Indicates the horizontal distance the shadow should extend from the object. This value isn't affected by the transformation matrix. The default is 0.

`shadowOffsetY = float` (en-US)

Indicates the vertical distance the shadow should extend from the object. This value isn't affected by the transformation matrix. The default is 0.

`shadowBlur = float` (en-US)

Indicates the size of the blurring effect; this value doesn't correspond to a number of pixels and is not affected by the current transformation matrix. The default value is 0.

`shadowColor = color` (en-US)

A standard CSS color value indicating the color of the shadow effect; by default, it is fully-transparent black.

The properties `shadowOffsetX` and `shadowOffsetY` indicate how far the shadow should extend from the object in the X and Y directions; these values aren't affected by the current transformation matrix. Use negative values to cause the shadow to extend up or to the left, and positive values to cause the shadow to extend down or to the right. These are both 0 by default.

The `shadowBlur` property indicates the size of the blurring effect; this value doesn't correspond to a number of pixels and is not affected by the current transformation matrix. The default value is 0.

The `shadowColor` property is a standard CSS color value indicating the color of the shadow effect; by default, it is fully-transparent black.

**Примечание:** Shadows are only drawn for `source-over` compositing operations.

### Пример текста с тенью

This example draws a text string with a shadowing effect.

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

  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 2;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";

  ctx.font = "20px Times New Roman";
  ctx.fillStyle = "Black";
  ctx.fillText("Sample String", 5, 30);
}
```

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

```
draw();
```

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

We will look at the `font` property and `fillText` method in the next chapter about drawing text.

Canvas fill rules
-----------------

When using `fill` (or `clip` (en-US) and `isPointinPath` (en-US)) you can optionally provide a fill rule algorithm by which to determine if a point is inside or outside a path and thus if it gets filled or not. This is useful when a path intersetcs itself or is nested.

Two values are possible:

`nonzero`

The non-zero winding rule, which is the default rule.

`evenodd`

The even-odd winding rule.

In this example we are using the `evenodd` rule.

```js
function draw() {
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.beginPath();
  ctx.arc(50, 50, 30, 0, Math.PI \* 2, true);
  ctx.arc(50, 50, 15, 0, Math.PI \* 2, true);
  ctx.fill("evenodd");
}
```

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

```
draw();
```

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

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