Элемент Canvas
173Веб-программирование --- HTML5 --- Элемент Canvas
Самым важным новым инструментом для расширенных приложений HTML5 является холст (Canvas) — поверхность для рисования, на которой пользователь может дать волю своим непризнанным художественным способностям. Холст стоит обособленно от всех других элементов HTML, т.к. для работы с ним требуется JavaScript. Иного способа для черчения фигур или рисования изображений попросту нет. Это означает, что холст, по сути, является средством программирования, таким, которое позволяет выйти далеко за пределы первоначального концепта Интернета, согласно которому он основывается на содержимом документного типа.
С первого взгляда использование холста может казаться похожим на использование программы MS Paint, вставленной в окно браузера. Но, копнув поглубже, можно увидеть, что холст — ключевой компонент для ряда графически продвинутых приложений, включая некоторые приложения, о которых вы уже, наверное, и сами подумали — игры, картографические инструменты и динамические графики, и такие, которые вы, возможно, не могли вообразить — музыкально-световые представления и эмуляторы физических процессов.
В не очень далеком прошлом создание таких приложений без помощи модулей расширения, таких как Flash, являлось чрезвычайно сложной задачей. Сегодня же холст внезапно делает все эти приложения возможными, если, конечно, вы готовы на дополнительные временные и интеллектуальные затраты.
Базовые возможности Canvas
Элемент <canvas> предоставляет рабочее пространство для рисования. С точки зрения разметки, это простой до предела элемент с тремя атрибутами — id, width и height:
<canvas id="drawingCanvas" width="500" height="300"></canvas>
Атрибут id присваивает данному холсту однозначное имя, требуемое для его идентификации кодом JavaScript А атрибуты width и height устанавливают ширину и высоту холста в пикселах, соответственно.
Размеры холста всегда следует устанавливать посредством атрибутов width и height элемента <canvas>, а не с помощью свойств width и height таблицы стилей. В противном случае возможно возникновение проблемы с искажением рисунков.
Обычно холст отображается как пустой прямоугольник без рамки, т.е. он не виден вообще. Чтобы сделать холст видимым, с помощью таблицы стилей ему можно дать цветной фон или рамку, как показано в следующем коде:
canvas {
border: 1px dashed black;
}
Полученный результат, который будет нашей отправной точкой в изучении Canvas показан на рисунке:
Чтобы рисовать на холсте, нужно написать определенный объем кода JavaScript. Эта задача состоит из двух этапов. Первым делом наш сценарий должен получить объект холста, для чего используется метод document.getElementById. Затем надо получить двумерный контекст рисования, для чего применяется метод getContext():
var canvas = document.getElementById("drawingCanvas");
var context = canvas.getContext("2d");
Контекст можно рассматривать как сверхмощный инструмент рисования, который выполняет все необходимые для этого операции, такие как создание прямоугольников, печатание текста, вставка изображений и т.п. Это что-то наподобие универсальной мастерской для операций рисования на холсте.
Тот факт, что контекст явно называется двумерным (и в коде указывается как "2d"), порождает очевидный вопрос, а именно: существует ли трехмерный контекст рисования? Ответ на этот вопрос — пока нет, но ясно, что создатели HTML5 оставили место для него в будущем.
Получить объект контекста и начать рисование можно в любой момент, например, сразу же после загрузки страницы, когда пользователь щелкнет мышью, и т.п. Вам, скорее всего, уже не терпится создать страницу, на которой можно было бы сразу же приступить к практической работе с холстом. В следующем листинге приведен код для создания шаблона такой страницы:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>HTML5 Canvas</title>
<style>
canvas {
border: 1px dashed black;
}
</style>
<script>
window.onload = function() {
var canvas = document.getElementById("drawingCanvas");
var context = canvas.getContext("2d");
// Код для рисования вставляется сюда
}
</script>
</head>
<body>
<canvas id="drawingCanvas" width="500" height="300"></canvas>
</body>
</html>
В разделе разметки <style> для холста создается рамка, указывающая его местонахождение на странице. А в разделе <script> обрабатывается событие window.onload, которое происходит после полной загрузки страницы браузером. После этого код получает объект холста и создает контекст рисования, подготовившись таким образом к рисованию. Эту разметку можно использовать в качестве отправной точки для дальнейших экспериментов с холстом.
Прямые линии
Теперь мы почти готовы приступить к рисованию. Почему почти? Потому что прежде нам нужно разобраться с системой координат холста. Принцип ее работы показан на рисунке ниже:
Координаты левой верхней точки холста, называющейся исходной точкой, равны (0, 0). Значение абсциссы (т.е. x-координаты) увеличивается при перемещении вправо от исходной точки, а значение ординаты (т.е. y-координаты) — при перемещении вниз. Таким образом, для холста размером в 500x300 пикселов координаты конечной точки (правого нижнего угла) холста будут равны (500, 300).
Самой простой фигурой, которую можно нарисовать на холсте, будет прямая линия. Для этого нужно выполнить три действия с контекстом. Сперва надо указать начальную точку линии с помощью метода moveTo(). Потом с помощью метода lineTo() задать конечную точку линии. Наконец, метод stroke() собственно рисует линию:
// JS
context.moveTo(10,10);
context.lineTo(400,40);
context.stroke();
С точки зрения обычного рисования эти действия можно рассматривать так: сначала мы ставим карандашом начальную точку линии (метод moveTo()), потом ставим конечную (метод lineTo()) и, наконец, собственно рисуем линию, соединяя ее начальную и конечную точки (метод stroke()). В результате исполнения этого кода точки (10,10) и (400,40) соединяются линией толщиной в один пиксел.
К счастью, стиль линии можно разнообразить. На любом этапе рисования линии, но перед тем как вызывать метод stroke(), можно установить три свойства контекста рисования: lineWidth, strokeStyle и lineCap. Эти свойства продолжают действовать до тех пор, пока не будут изменены.
Свойство lineWidth определяет толщину линии в пикселах. Например, следующая строка кода устанавливает толщину линии в 10 пикселов:
// JS
context.lineWidth = 10;
Свойство strokeStyle определяет цвет линий. Значение этого свойства может быть в виде названия цвета HTML, кода цвета HTML или же CSS-функции rgb(), которая позволяет создать цвет из его красной, зеленой и синей составляющей:
// JS
context.strokeStyle = "rgb(16,155,252)";
Свойство strokeStyle называется именно так, а не strokeColor, т.к позволяет устанавливать не только чистые цвета. Как мы увидим позже, цвет можно задавать в виде градиентной закраски или в виде узора из изображений.
Наконец, свойство lineCap указывает тип концов линии. По умолчанию этому свойству присваивается значение butt (что придает концам линии прямоугольную форму), но можно также присвоить значение round, делая концы округлыми, и squre. Последнее значение также делает концы линии прямоугольными, как и значение butt, но удлиняет ее на каждом конце на половину значения толщины линии. Таким же образом линия удлиняется и при форме концов round.
Далее приведен код для рисования трех горизонтальных линий, каждая со своим стилем концов:
// Толщина и цвет для всех линий
context.lineWidth = 20;
context.strokeStyle = "rgb(16,155,252)";
// Линия с концами типа butt
context.moveTo(10,50);
context.lineTo(400,50);
context.stroke();
// Линия с концами типа round
context.beginPath();
context.moveTo(10,120);
context.lineTo(400,120);
context.lineCap = "round";
context.stroke();
// Линия с концами типа square
context.beginPath();
context.moveTo(10,190);
context.lineTo(400,190);
context.lineCap = "square";
context.stroke();
Верхняя линия имеет стандартные прямоугольные концы типа butt, а на концы двух других добавлены наконечники (округлые и прямоугольные), которые удлиняют эти линии на половину их толщины.
В этом примере вводится новая функция: метод beginPath() контекста рисования. Вызов метода beginPath() начинает новый, отдельный путь рисунка. Если не выполнять этот шаг, то при каждом новом вызове метода stroke() холст будет рисовать все снова. (Особенно это представляет проблему при изменении свойств контекста, когда рисование начинает выполняться по существующему контексту теми же фигурами, но с новым цветом, толщиной или окончаниями линий.)
Для того чтобы начать новый путь, надо вызвать метод beginPath(), а чтобы завершить путь, ничего особенного делать не нужно. Текущий путь автоматически считается завершенным, как только создается новый путь.
Пути и фигуры
В предыдущем примере, чтобы отделить линии друг от друга, для каждой из них создавался новый путь. Этот подход позволяет присвоить каждой линии свой цвет, а также толщину и тип окончания. Важность путей также состоит в том, что они позволяют заполнять цветом фигуры. Например, нарисуем красными линиями треугольник посредством следующего кода:
// JS
context.moveTo(250,50);
context.lineTo(50,250);
context.lineTo(450,250);
context.lineTo(250,50);
context.lineWidth = 10;
context.strokeStyle = "red";
context.stroke();
Теперь мы хотим закрасить внутреннюю область этого треугольника, но метод stroke() для этой задачи не подходит. Здесь нужно закрыть текущий путь с помощью метода closePath(), выбрать цвет заливки, установив значение свойства fillStyle, а потом вызвать метод fill(), чтобы собственно выполнить заливку.
В этом примере стоит сделать пару доводок. Первое: при закрытии пути нет надобности рисовать последний сегмент линии, т.к. вызов метода closePath() автоматически строит линию между последней нарисованной точкой и начальной точкой. Второе: лучше сначала выполнить заливку фигуры и только потом очертить ее контуры. В противном случае линии контура могут быть частично перекрыты заливкой.
Далее приведен полный код для рисования и заливки треугольника:
context.moveTo(250,50);
context.lineTo(50,250);
context.lineTo(450,250);
context.closePath();
// Заливка
context.fillStyle = "#109bfc";
context.fill();
// Контур
context.lineWidth = 10;
context.strokeStyle = "orange";
context.stroke();
Обратите внимание, что в этом примере метод beginPath() вызывать не нужно, путь создается автоматически. Метод beginPath() следует вызывать, только когда надо начать новый путь, например при изменении параметров линии или новой фигуры. Результаты выполнения кода показаны на рисунке:
Вершины фигур, создаваемые соединяющимися линиями, можно оформить тремя разными способами, присваивая свойству контекста lineJoin соответствующие значения. Значение round округляет вершины, значение mitre соединяет линии в вершине "под ус", а значение bevel обрезает вершины прямой линией. По умолчанию свойству lineJoin присвоено значение mitre.
В большинстве случаев, чтобы создать сложную фигуру, ее очертания нужно создавать пошагово, по одному отрезку за раз. Но одна фигура является достаточно важной, и для нее выделен отдельный метод. Это прямоугольник. Заполнить прямоугольную область заливкой можно за один шаг методом fillRect(), которому в параметрах передаются координаты левого верхнего угла, ширина и высота прямоугольника. В один присест, используя метод strokeRect(), также можно нарисовать и очертания прямоугольника.
Цвет заливки для метода fillRect() устанавливается так же, как и для метода fill() свойством fillStyle. Толщина и цвет линий прямоугольника определяются текущими значениями свойств lineWidth и strokeStyle, точно так же, как для метода stroke().
Например, следующий код заполняет заливкой прямоугольную область размером 100x200 пикселов с левым верхним углом в точке (100, 40):
// JS
context.fillStyle = "#109bfc";
context.strokeStyle = "orange";
context.lineWidth = 10;
context.fillRect(100,40,100,200);
context.strokeRect(100,40,100,200);
Кривые линии
Чтобы рисовать что-то более сложное, чем линии и прямоугольники, нужно изучить следующие четыре метода: arc(), arcTo(), bezierCurveTo() и quadraticCurveTo(). Все эти методы рисуют кривые линии, и хотя каждый делает это по-своему, все они требуют хотя бы небольших (а некоторые и больших) знаний математики.
Изо всех этих методов самый простой — метод arc(), который рисует дугу. Чтобы нарисовать дугу, нужно сначала представить себе в уме полный круг, а потом решить, какую часть его окружности вы хотите рисовать:
Дуга выглядит достаточно простой фигурой, но чтобы полностью ее описать, требуется несколько единиц информации. Сначала нужно нарисовать воображаемый круг. Для этого надо знать координаты центра (1) и радиуса (2), который определяет размер круга. Далее следует описать длину дуги на окружности, для чего требуется угол начала дуги (3) и угол ее окончания (4). Значения углов должны быть в радианах, которые выражаются через число π. Угол всей окружности равен 2π, половины — 1π и т.д.
Собрав все необходимые данные, передаем их методу arc():
// Толщина и цвет дуги
context.lineWidth = 20;
context.strokeStyle = "rgb(16,155,252)";
// Создаем переменные для хранения информации о дуге
var centerX = 150;
var centerY = 200;
var radius = 100;
var startingAngle = 1.25 * Math.PI;
var endingAngle = 1.75 * Math.PI;
// Рисуем дугу на основе этой информации
context.arc(centerX, centerY, radius, startingAngle, endingAngle);
context.stroke();
Дугу можно закрыть, соединив ее концы прямой линией. Для этого нужно вызвать метод closePath() перед тем, как вызывать метод stroke(). Кстати, окружность — это та же дуга, просто с углом 2π. Рисуется окружность следующим образом:
// ...
var startingAngle = 0;
var endingAngle = 2 * Math.PI;
Метод arc() нельзя применять для рисования овала (вытянутого круга). Для этого нужно использовать либо более сложные методы для рисования кривых, либо применить трансформации.
Три других метода рисования кривых — arcTo(), bezierCurveTo() и quadraticCurveTo() — могут быть несколько посложнее для тех, кто не в ладах с геометрией. Они основаны на принципе контрольных точек, т.е. точек, которые сами не являются частью кривой, но управляют ее формой. Наиболее известным типом таких кривых являются кривые Безье, которые используются практически в каждой программе рисования. Причиной популярности этого метода является, его способность создавать плавные кривые, независимо от их размера.
Кривая Безье создается следующим кодом:
// Толщина и цвет кривой
context.lineWidth = 10;
context.strokeStyle = "rgb(16,155,252)";
// Устанавливаем начало кривой
context.moveTo(62, 242);
// Контрольные и конечная точки
var controlX_1 = 187;
var controlY_1 = 32;
var controlX_2 = 429;
var controlY_2 = 480;
var endPointX = 365;
var endPointY = 133;
// Рисуем кривую
context.bezierCurveTo(controlX_1, controlY_1, controlX_2, controlY_2,
endPointX, endPointY);
context.stroke();
Контур сложной фигуры часто состоит из ряда дуг и кривых, соединяющихся друг с другом. По окончанию рисования всех составляющих можно вызвать метод closePath(), чтобы обвести или закрасить всю фигуру. Чтобы побольше узнать о кривых Безье, лучше поэкспериментировать с ними, а хорошую игровую площадку для этого можно найти на этой странице: Canvas Bézier Curve Example.
Рисование на холсте для тех, кто ненавидит математику
Если вы планируете создавать на холсте захватывающую графику, но не желаете углубленно изучать геометрию, вам, может быть, придется немного разочароваться. К счастью, существует несколько способов, которые могут помочь вам рисовать требуемые фигуры, не беспокоясь о математических принципах в их основе:
- Использовать библиотеку рисования
Зачем набивать себе шишки на лбу, если можно использовать готовую библиотеку для рисования кругов, треугольников, овалов и многоугольников за один прием? Идея простая — вызывается метод высшего уровня (скажем, fillEllipse(), которому передаются соответствующие координаты), и библиотека JavaScript преобразует все это в требуемую операцию на холсте. В качестве двух хороших примеров такой библиотеки можно назвать CanvasPlus и Artisan JS. Но эти, и другие, библиотеки продолжают развиваться, поэтому еще рано говорить, какие из них выживут и окажутся пригодными для профессионального применения.
- Рисовать растровые изображения
Вместо того чтобы кропотливо рисовать каждую требуемую фигуру самому, можно скопировать в холст уже готовую графику. Например, изображение круга, сохраненное в файле формата PNG, можно вставить в холст. Но этот подход не позволит использовать гибкость при манипулировании изображением, например растягивать его, перемещать или удалять его части и т.п.
- Использовать профессиональный инструмент
Для того чтобы манипулировать сложной графикой на холсте или сделать ее интерактивной, фиксированного растрового изображения будет недостаточно. Решением этой проблемы может быть средство преобразования, которое способно исследовать вашу графику и сгенерировать правильный код для применения с холстом.
В этом отношении представляет интерес одно из таких средств — модуль Ai->Canvas для Adobe Illustrator. Данный модуль преобразует созданную в Adobe Illustrator графику в страницу HTML с кодом JavaScript, который воспроизводит эту графику на холсте.