Графические примитивы в JavaScript

Поле для рисования

Одним из самых востребованных инструментов в HTML5 (запущен с 2014 года) является холст (тег <canvas>) — поле для рисования, которое используется для построения графиков, рисунков, анимации и создания игр в браузерах. Холст стоит обособленно от всех других тегов HTML, поскольку для работы с ним требуется JavaScript.

Элемент имеет два главных атрибута - ширину и высоту (оба они не обязательны и по умолчанию холст имеет ширину в 300 пикселей и высоту - в 150 пикселей). А также, атрибут id, необходимый для его идентификации кодом JavaScript.

<canvas id="drawing" width="600" height="500"></canvas>

Тег <canvas> парный, т.е. имеет закрывающий его элемент </canvas>. Сделано это в целях предоставить альтернативное содержание внутри блока <canvas>Ошибка</canvas>. Этот контент будет отображаться в браузерах, которые не поддерживают canvas и в браузерах с отключённым JavaScript.

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

    <style type="text/css">
      #drawing { 
         border: 1px solid #000;
         background: #ffe;  
         display: block;
         margin: 0 auto;
      }
    </style>

И, непосредственно, чтобы рисовать на холсте, нужно использовать язык программирования JavaScript. Первым делом необходимо получить объект холста, для чего используется метод document.getElementById. Затем надо получить двумерный контекст рисования, для чего применяется метод getContext():

var obj = document.getElementById("drawing");
var ctx = obj.getContext("2d");

Итак, шаблон файла index.html, который позволяет правильно начать работать с построением графических изображений в браузере посредством связки HTML5+JavaScript, выглядит следующим образом:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Холст для рисования на JS</title>
    <style type="text/css">
      #drawing { border: 1px solid black; }
    </style>
  </head>
 
  <body>
    <canvas id="drawing" width="600" height="500"></canvas>
    <script type="text/javascript">
      var obj = document.getElementById('drawing'); // Получить объект холста
      // Проверить поддерживает ли браузер возможность рисования на холсте
      if (obj.getContext){
        var ctx = obj.getContext('2d');
        // тут рисуем ..... Пример ctx.fillRect(10, 10, 55, 50);
      }
    </script>
  </body>
</html>

Для изучения принципов работы с графикой удобно пользоваться онлайн-инструментом сайта https://codepen.io, который представляет собой WYSIWYG редактор кода HTML5+CSS+JavaScript.

Шаблон для рисования в этом онлайн-редакторе размещен здесь.

Графические примитивы

Графические примитивы - это базовые элементы, такие как линии, кривые и полигоны, которые могут быть объединены для создания более сложных графических изображений. Для построения графических примитивов используется декартова система координат, которая на холсте canvas определяется следующим образом (см. рис. 1):

  • 1 единица на сетке соответствует 1 пикселю на canvas;
  • начало координат расположено в верхнем левом углу в координате (0,0);
  • ось X направлена вправо, ось Y направлена вниз.

Рис.1. Декартова сетка на элементе <canvas>

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

fillRect(x, y, width, height) Рисование закрашенного прямоугольника.
strokeRect(x, y, width, height) Рисование прямоугольного контура.
clearRect(x, y, width, height) Очистка прямоугольной области, делая содержимое совершенно прозрачным.

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

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

Пример:

  var canvas = document.getElementById('drawing');
  if (canvas.getContext) {
    var ctx = canvas.getContext('2d');
 
    ctx.fillRect(25,25,100,100);
    ctx.clearRect(45,45,60,60);
    ctx.strokeRect(50,50,50,50);
  }

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

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

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

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

beginPath() Создаёт новый контур. После создания используется в дальнейшем командами рисования при построении контуров.
closePath() Замыкает контур, добавляя прямую линию от первой точки до последней.
stroke() Рисует фигуру с внешней обводкой, без заливки.
fill() Рисует фигуру с заливкой внутренней области.

Для определения контура используются следующие команды (п.2):

moveTo(x, y) Перемещает перо в точку с координатами x и y.
lineTo(x, y) Рисует линию с текущей позиции до позиции, определённой x и y.
arc(x, y, radius, startAngle, endAngle, anticlockwise) Рисует дугу с центром в точке (x,y) радиусом radius, начиная с угла startAngle и заканчивая в endAngle в направлении против часовой стрелки anticlockwise (по умолчанию по ходу движения часовой стрелки).
arcTo(x1, y1, x2, y2, radius) Рисуем дугу с заданными контрольными точками и радиусом, соединяя эти точки прямой линией.
quadraticCurveTo(cp1x, cp1y, x, y) Рисуется квадратичная кривая Безье с текущей позиции пера в конечную точку с координатами x и y, используя контрольную точку с координатами cp1x и cp1y.
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) Рисуется кубическая кривая Безье с текущей позиции пера в конечную точку с координатами x и y, используя две контрольные точки с координатами (cp1x, cp1y) и (cp2x, cp2y).
rect(x, y, width, height) Добавляет в контур прямоугольник, верхний левый угол которого указан с помощью (x, y) с шириной и высотой width и height. Когда этот метод вызван, автоматически вызывается метод moveTo() с параметрами (x, y).
roundRect(x, y, width, height, radius) Добавляет прямоугольник со скругленными углами к текущему контуру. Параметр радиус задает радиусы скругления углов. Когда этот метод вызван, автоматически вызывается метод moveTo() с параметрами (x, y). Не работает в FireFox.
ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, anticlockwise]) Создаёт эллиптическую дугу с центром в точках (x, y) с радиусом radiusX и radiusY. Путь начинается от точки startAngle и заканчивается в точке endAngle, идёт по направлению, указанному в параметре (по часовой стрелке или против неё) anticlockwise. Параметр rotation задает вращение эллипса, выраженное в радианах.

На рисунке 2 представлен способ построения дуги функцией arcTo. Синим обозначена начальная точка (ее параметры известны и не передаются в arcTo). Красным обозначены контрольные точки по порядку. Дуга является касательной для обеих линий.


Рис.2. Построение дуги методом arcTo.

На рисунке 3 представлен способ построения кривой Безье с одной и двумя контрольными точками (обозначены красным цветом).


Рис.3. Построение кривой Безье

Определение цвета

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

fillStyle = color;    // цвет заливки
strokeStyle = color;  // цвет контура

Параметр color может быть цветом, (строка, представленная в CSS ), градиентом или паттерном.

Цвет можно задать следующими способами:

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

Пример:

var obj = document.getElementById('drawing');
if (obj.getContext){ 
  var ctx = obj.getContext('2d');
  // Рисуем красный прямоугольник с синей обводкой
  ctx.fillStyle = 'rgb(255,51,0)';
  ctx.fillRect(1,1,80,150);
  ctx.strokeStyle = 'rgb(0,0,255)';
  ctx.strokeRect(1,1,80,150);
  // Используем прозрачность и рисуем еще два прямоугольника
  ctx.fillStyle = 'rgba(255,221,0,0.5)';
  ctx.fillRect(40,1,150,50);
  ctx.fillStyle = 'rgba(102,204,0,0.5)';
  ctx.fillRect(40,50,150,50);
}

Результат построения:

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

createLinearGradient(x1, y1, x2, y2) Создает объект линейного градиента с начальной точкой ( x1, y1) и конечной точкой ( x2, y2).
createRadialGradient(x1, y1, r1, x2, y2, r2) Создает радиальный градиент. Параметры представляют собой две окружности, одна с центром в точке ( x1, y1) и радиусом r1, а другая с центром в точке ( x2, y2) и радиусом r2.
gradient.addColorStop(position, color) Создает новую цветовую точку на gradient объекте. Это position число от 0,0 до 1,0, определяющее относительное положение цвета в градиенте, а color аргумент должен быть строкой, представляющей CSS , указывающей цвет, которого должен достичь градиент при этом смещении в переходе.

Пример:

var obj = document.getElementById('drawing'); 
if (obj.getContext){
  var ctx = obj.getContext('2d');
 
// Определяем градиент
 
const gradient = ctx.createLinearGradient(0, 0, 200, 200);
 
// Добавляем в него цвета плавных переходов
gradient.addColorStop(0, "green");
gradient.addColorStop(0.5, "cyan");
gradient.addColorStop(1, "green");
 
// Заливаем фигуру созданным градиентом
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 200, 200);
}

Результат:

Тени

Использование теней включает всего четыре свойства:

shadowOffsetX = float Указывает горизонтальное расстояние, на которое тень должна простираться от объекта. Значение по умолчанию – 0.
shadowOffsetY = float Указывает вертикальное расстояние, на которое тень должна простираться от объекта. Значение по умолчанию – 0.
shadowBlur = float Указывает размер эффекта размытия. Значение по умолчанию — 0.
shadowColor = color Стандартное значение цвета CSS, указывающее цвет эффекта тени; по умолчанию он полностью прозрачный черный.

Пример:

var obj = document.getElementById('drawing'); 
if (obj.getContext){ 
  var ctx = obj.getContext('2d');
  ctx.shadowOffsetX = 20;
  ctx.shadowOffsetY = 20;
  ctx.shadowBlur = 20;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
  ctx.fillStyle = "#00DD00";
  ctx.fillRect(0, 0, 200, 200);
}

Результат:

Размещение текста

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

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

Помимо свойств цвета, тени, градиента, описанных выше, для текста работает еще ряд свойств:

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

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

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

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

Пример:

var obj = document.getElementById('drawing'); 
if (obj.getContext){ 
  var ctx = obj.getContext('2d');
  ctx.shadowOffsetX = 5;
  ctx.shadowOffsetY = 5;
  ctx.shadowBlur = 4;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
  ctx.strokeStyle = "rgb(0,0,255)";
  ctx.font = '48px serif';
  ctx.strokeText('Hello world', 10, 50);
}

Результат: