Теперь, установив наш canvas environment, мы можем погрузиться в детали того, как рисовать в canvas. К концу этой статьи, Вы научитесь рисовать прямоугольники, треугольники, линии, дуги и кривые, при условии что Вы хорошо знакомы с основными геометрическими фигурами. Работа с путями весьма важна, когда рисуете объекты на canvas и мы увидим как это может быть сделано.
Сетка
Перед тем как мы начнем рисовать, нам нужно поговорить о сетке canvas или координатной плоскости. Наш HTML каркас из предыдущей страницы включал в себя элемент canvas 150 пикселей в ширину и 150 пикселей в высоту. Справа можно увидеть этот canvas с сеткой, накладываемой по умолчанию. Обычно 1 единица на сетке соответствует 1 пикселю на canvas. Начало координат этой сетки расположено в верхнем левом углу в координате (
0,0)
. Все элементы размещены относительно этого начала. Таким образом, положение верхнего левого угла синего квадрата составляет х
пикселей слева и у
пикселей сверху, на координате (х
, у)
. Позже в этом уроке мы увидим, как можно перевести начало координат в другое место, вращать сетку и даже масштабировать ее, но сейчас мы будем придерживаться настроек сетки по умолчанию.
Рисование прямоугольников
В отличие от SVG, <canvas>
поддерживает только одну примитивную фигуру: прямоугольник. Все другие фигуры должны быть созданы комбинацией одного или большего количества контуров, набором точек, соединенных в линии. К счастью в ассортименте рисования контуров у нас есть функции, которые делают возможным составление очень сложных фигур.
Сначала рассмотрим прямоугольник. Ниже представлены три функции рисования прямоугольников в canvas:
fillRect(x, y, width, height)
- Рисование заполненного прямоугольника.
strokeRect(x, y, width, height)
- Рисование прямоугольного контура.
clearRect(x, y, width, height)
- Очистка прямоугольной области, делая содержимое совершенно прозрачным.
Каждая из приведенных функций принимает несколько параметров:
- x, y устанавливают положение верхнего левого угла прямоугольника в canvas (относительно начала координат);
width
(ширина) и height
(высота) определяют размеры прямоугольника.
Ниже приведена функция draw(), использующая эти три функции.
Пример создания прямоугольных фигур
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext) { var ctx = canvas.getContext('2d'); ctx.fillRect(25,25,100,100); ctx.clearRect(45,45,60,60); ctx.strokeRect(50,50,50,50); } }
Этот пример изображен ниже.
Screenshot | Live sample |
---|---|
Функция fillRect() рисует большой чёрный квадрат со стороной 100 px. Функция clearRect() вырезает квадрат 60х60 из центра, а функция strokeRect() создает прямоугольный контур 50х50 пикселей внутри очищенного квадрата.
На следующей странице мы рассмотрим две альтернативы методу clearRect(), и также увидим, как можно изменять цвет и стиль контура отображаемых фигур.
В отличие от функций создания контуров, которые будут рассмотрены в следующем разделе, все три функции создания прямоугольника сразу же отображаются на canvas.
Рисование контуров (path)
Остальные примитивные фигуры создаются контурами. Контур - это набор точек, которые, соединяясь в отрезки линий, могут образовывать различные фигуры, изогнутые или нет, разной ширины и разного цвета. Контур (или субконтур) может быть закрытым.
Создание фигур используя контуры происходит в несколько важных шагов:
- Сначала вы создаете контур.
- Затем, используя команды рисования, рисуете контур.
- Потом закрываете контур.
- Созданный контур вы можете обвести или залить для его отображения.
Здесь приведены функции, которые можно использовать в описанных шагах:
beginPath()
- Создает новый контур. После создания используется в дальнейшем командами рисования при построении контуров.
- Path методы
- Методы для установки различных контуров объекта.
closePath()
- Закрывает контур, так что будущие команды рисования вновь направлены контекст.
stroke()
- Рисует фигуру с внешней обводкой.
fill()
- Рисует фигуру с заливкой внутренней области.
Первый шаг создания контура заключается в вызове функции beginPath()
. Внутри содержатся контуры в виде набора суб-контуров (линии, дуги и др.), которые вместе образуют форму фигуры. Каждый вызов этого метода очищает набор, и мы можем начинать рисовать новые фигуры.
beginPath()
или на вновь созданном canvas), первой командой построения контура всегда является функция moveTo()
. Поэтому мы всегда можем установить начальную позицию рисования контура после перезагрузки.Вторым шагом является вызов методов, определяемых видом контура, который нужно нарисовать. Их мы рассмотрим позднее.
Третий и необязательный шаг - это вызов closePath()
. Этот метод пытается закрыть фигуру, рисуя прямую линию из текущей точки в начальную. Если фигура была уже закрыта или является просто точкой, то функция ничего не делает.
fill()
, то каждая открытая фигура закрывается автоматически, так что вы можете не использовать closePath()
. Это обстоятельство не имеет место в случае вызова stroke()
.Рисование треугольника
Например, код для рисования треугольника будет выглядеть как-то так:
<html> <body onload="draw();"> <canvas id="canvas" width="100" height="100"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.moveTo(75,50); ctx.lineTo(100,75); ctx.lineTo(100,25); ctx.fill(); } }
Результат выглядит так:
Screenshot | Live sample |
---|---|
Передвижение пера
Одна очень полезная функция, которая ничего не рисует, но связана по смыслу с вышеописанными функциями - это moveTo()
. Вы можете представить это как отрыв (подъем) пера от бумаги и его перемещение в другое место.
moveTo(x, y)
- Перемещает перо в точку с координатами x и y.
При инициализации canvas или при вызове beginPath()
, вы захотите использовать функцию moveTo() для
перемещения в точку начала рисования. Можно использовать moveTo()
и для рисования несвязанного(незакрытого) контура
. Посмотрите на смайлик ниже.
Вы можете проверить это сами, используя участок кода ниже. Просто вставьте в функцию draw()
, рассмотренную ранее.
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.arc(75,75,50,0,Math.PI*2,true); // Внешняя окружность ctx.moveTo(110,75); ctx.arc(75,75,35,0,Math.PI,false); // рот (по часовой стрелке) ctx.moveTo(65,65); ctx.arc(60,65,5,0,Math.PI*2,true); // Левый глаз ctx.moveTo(95,65); ctx.arc(90,65,5,0,Math.PI*2,true); // Правый глаз ctx.stroke(); } }
Результат этого ниже:
Screenshot | Live sample |
---|---|
Если вы захотите увидеть соединные линии, то можете удалить вызов moveTo()
.
Note: Подробнее о функции arc()
,посмотрите Arcs .
Линии
Для рисования прямых линий используйте метод lineTo()
.
lineTo(x, y)
- Рисует линию с текущей позиции до позиции, определенной
x и y
.
Этот метод принимает два аргумента x и y
, которые являются координатами конечной точки линии. Начальная точка зависит от ранее нарисованных путей, причём конечная точка предыдущего пути является начальной точкой следующего и т. д. Начальная точка также может быть изменена с помощью метода moveTo()
.
Пример ниже рисует два треугольника, один закрашенный и другой обведен контуром.
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); // Filled triangle ctx.beginPath(); ctx.moveTo(25,25); ctx.lineTo(105,25); ctx.lineTo(25,105); ctx.fill(); // Stroked triangle ctx.beginPath(); ctx.moveTo(125,125); ctx.lineTo(125,45); ctx.lineTo(45,125); ctx.closePath(); ctx.stroke(); } }
Отрисовка начинается с вызова beginPath()
, чтобы начать рисовать путь новой фигуры. Затем мы используем метод moveTo()
, чтобы переместить начальную точку в нужное положение. Ниже рисуются две линии, которые образуют две стороны треугольника.
Screenshot | Live sample |
---|---|
Вы заметите разницу между закрашенным и обведенным контуром треугольниками. Это, как упоминалось выше, из-за того, что фигуры автоматически закрываются, когда путь заполнен (т. е. закрашен), но не тогда, когда он очерчен (т. е. обведен контуром). Если бы мы не учли closePath()
для очерченного треугольника, тогда только две линии были бы нарисованы, а не весь треугольник.
Дуги
Для рисования дуг и окружностей, используем методы arc() и arcTo().
arc(x, y, radius, startAngle, endAngle, anticlockwise)
- Рисуем дугу с центром в точке
(x,y)
радиусомr
, начиная с углаstartAngle
и заканчивая вendAngle
в направлении против часовой стрелкиanticlockwise
arcTo(x1, y1, x2, y2, radius)
- Рисуем дугу с заданными контрольными точками и радиусом, соединяя эти точки прямой линией.
Давайте более детальнее рассмотрим метод arc(), который имеет пять параметров: x
и y
-- это координаты центра окружности, в которой должна быть нарисована дуга. radius
is self-explanatory. Углы startAngle
and endAngle
определяют начальную и конечную точки дуги в радианах, вдоль кривой окружности. Отсчет происходит от оси x. Параметр anticlockwise
-- логическое значение, которое, если true
, то рисование дуги совершается против хода часовой стрелки; иначе рисование происходит по ходу часовой стрелки.
Note: Углы в функции arc() измеряют в радианах, не в градусах. Для перевода градусов в радианы вы можете использовать JavaScript-выражение: radians = (Math.PI/180)*degrees
.
Следующий пример немного сложнее, чем мы рассматривали ранее. Здесь нарисованы 12 различных дуг с разными углами и заливками.
Два for
цикла размещают дуги по столбцам и строкам. Для каждой дуги, мы начинаем новый контур, вызывая beginPath()
. В этом коде каждый параметр дуги для большей ясности задан в виде переменной, но вам не обязательно делать так в реальных проектах.
Координаты x
и y
должны быть достаточны ясны. radius
and startAngle
-- фиксированы. endAngle
начинается со 180 градусов (полуокружность) в первой колонке и, увеличиваясь с шагом 90 градусов, достигает кульминации полноценной окружностью в последнем столбце.
Установка параметра clockwise
определяет результат, в первой и третьей строках рисование дуг происходит по часовой стрелке, а во второй и четвертой против часовой стрелки. Благодаря if-условию верхняя половина дуг образуется с контуром(обводкой), а нижняя половина дуг с заливкой.
Note: Этот пример требует немного большего холста (canvas), чем другие на этой странице: 150 x 200 pixels.
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="200"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); for(var i=0;i<4;i++){ for(var j=0;j<3;j++){ ctx.beginPath(); var x = 25+j*50; // x coordinate var y = 25+i*50; // y coordinate var radius = 20; // Arc radius var startAngle = 0; // Starting point on circle var endAngle = Math.PI+(Math.PI*j)/2; // End point on circle var anticlockwise = i%2==0 ? false : true; // clockwise or anticlockwise ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise); if (i>1){ ctx.fill(); } else { ctx.stroke(); } } } } }
Screenshot | Live sample |
---|---|
Безье и квадратичные кривые
Следующим типом доступных контуров являются кривые Безье, и к тому же доступны в кубическом и квадратичном вариантах. Обычно они используются при рисовании сложных составных фигур.
quadraticCurveTo(cp1x, cp1y, x, y)
- Рисуется квадратичная кривая Безье с текущей позиции пера в конечную точку с координатами
x
иy
, используя контрольную точку с координатамиcp1x
иcp1y
. bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
- Рисуется кубическая кривая Безье с текущей позиции пера в конечную точку с координатами
x
иy
, используя две контрольные точки с координатами (cp1x
,cp1y
) и (cp2x, cp2y).
Различие между ними можно увидеть на рисунке, изображенном справа. Квадратичная кривая Безье имеет стартовую и конечную точки (синие точки) и всего одну контрольную точку (красная точка), в то время как кубическая кривая Безье использует две контрольные точки.
Параметры x
и y
в этих двух методах являются координатами конечной точки. cp1x
и cp1y
-- координаты первой контрольной точки, а cp2x
и cp2y
-- координаты второй контрольной точки.
Использование квадратичных или кубических кривых Безье может быть спорным выходом, так как в отличие от приложений векторной графики типа Adobe Illustrator, мы не имеем полной видимой обратной связи с тем, что мы делаем. Этот факт делает довольно сложным процесс рисования сложных фигур. А следующем примере мы нарисуем совсем простую составную фигуру, но, если у вас есть время и ещё больше терпения, можно создать более сложные составные фигуры.
В этом примере нет ничего слишком тяжелого. В обоих случая мы видим последовательность кривых, рисуя которые в результате получим составную фигуру.
Квадратичные кривые Безье
В этом примере многократно используются квадратичные кривые Безье для рисования речевой выноски.
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext) { var ctx = canvas.getContext('2d'); // Quadratric curves example ctx.beginPath(); ctx.moveTo(75,25); ctx.quadraticCurveTo(25,25,25,62.5); ctx.quadraticCurveTo(25,100,50,100); ctx.quadraticCurveTo(50,120,30,125); ctx.quadraticCurveTo(60,120,65,100); ctx.quadraticCurveTo(125,100,125,62.5); ctx.quadraticCurveTo(125,25,75,25); ctx.stroke(); } }
Screenshot | Live sample |
---|---|
Cubic Bezier curves
This example draws a heart using cubic Bézier curves.
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); // Quadratric curves example ctx.beginPath(); ctx.moveTo(75,40); ctx.bezierCurveTo(75,37,70,25,50,25); ctx.bezierCurveTo(20,25,20,62.5,20,62.5); ctx.bezierCurveTo(20,80,40,102,75,120); ctx.bezierCurveTo(110,102,130,80,130,62.5); ctx.bezierCurveTo(130,62.5,130,25,100,25); ctx.bezierCurveTo(85,25,75,37,75,40); ctx.fill(); } }
Screenshot | Live sample |
---|---|
Прямоугольники
In addition to the three methods we saw in Drawing rectangles, which draw rectangular shapes directly to the canvas, there's also the rect()
method, which adds a rectangular path to a currently open path.
rect(x, y, width, height)
- Draws a rectangle whose top-left corner is specified by (
x
,y
) with the specifiedwidth
andheight
.
When this method is executed, the moveTo()
method is automatically called with the parameters (0,0). In other words, the current pen position is automatically reset to the default coordinates.
Создание комбинаций
So far, each example on this page has used only one type of path function per shape. However, there's no limitation to the number or types of paths you can use to create a shape. So in this final example, let's combine all of the path functions to make a set of very famous game characters.
<html> <body onload="draw();"> <canvas id="canvas" width="150" height="150"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); roundedRect(ctx,12,12,150,150,15); roundedRect(ctx,19,19,150,150,9); roundedRect(ctx,53,53,49,33,10); roundedRect(ctx,53,119,49,16,6); roundedRect(ctx,135,53,49,33,10); roundedRect(ctx,135,119,25,49,10); ctx.beginPath(); ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,false); ctx.lineTo(31,37); ctx.fill(); for(var i=0;i<8;i++){ ctx.fillRect(51+i*16,35,4,4); } for(i=0;i<6;i++){ ctx.fillRect(115,51+i*16,4,4); } for(i=0;i<8;i++){ ctx.fillRect(51+i*16,99,4,4); } ctx.beginPath(); ctx.moveTo(83,116); ctx.lineTo(83,102); ctx.bezierCurveTo(83,94,89,88,97,88); ctx.bezierCurveTo(105,88,111,94,111,102); ctx.lineTo(111,116); ctx.lineTo(106.333,111.333); ctx.lineTo(101.666,116); ctx.lineTo(97,111.333); ctx.lineTo(92.333,116); ctx.lineTo(87.666,111.333); ctx.lineTo(83,116); ctx.fill(); ctx.fillStyle = "white"; ctx.beginPath(); ctx.moveTo(91,96); ctx.bezierCurveTo(88,96,87,99,87,101); ctx.bezierCurveTo(87,103,88,106,91,106); ctx.bezierCurveTo(94,106,95,103,95,101); ctx.bezierCurveTo(95,99,94,96,91,96); ctx.moveTo(103,96); ctx.bezierCurveTo(100,96,99,99,99,101); ctx.bezierCurveTo(99,103,100,106,103,106); ctx.bezierCurveTo(106,106,107,103,107,101); ctx.bezierCurveTo(107,99,106,96,103,96); ctx.fill(); ctx.fillStyle = "black"; ctx.beginPath(); ctx.arc(101,102,2,0,Math.PI*2,true); ctx.fill(); ctx.beginPath(); ctx.arc(89,102,2,0,Math.PI*2,true); ctx.fill(); } } // A utility function to draw a rectangle with rounded corners. function roundedRect(ctx,x,y,width,height,radius){ ctx.beginPath(); ctx.moveTo(x,y+radius); ctx.lineTo(x,y+height-radius); ctx.quadraticCurveTo(x,y+height,x+radius,y+height); ctx.lineTo(x+width-radius,y+height); ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius); ctx.lineTo(x+width,y+radius); ctx.quadraticCurveTo(x+width,y,x+width-radius,y); ctx.lineTo(x+radius,y); ctx.quadraticCurveTo(x,y,x,y+radius); ctx.stroke(); }
The resulting image looks like this:
Screenshot | Live sample |
---|---|
We won't go over this in detail, since it's actually surprisingly simple. The most important things to note are the use of the fillStyle
property on the drawing context, and the use of a utility function (in this case roundedRect()
). Using utility functions for bits of drawing you do often can be very helpful and reduce the amount of code you need, as well as its complexity.
We'll take another look at fillStyle
, in more detail, later in this tutorial. Here, all we're doing is using it to change the fill color for paths from the default color of black to white, and then back again.
Path2D objects
As we have seen in the last example, there can be a series of paths and drawing commands to draw objects onto your canvas. To simplify the code and to improve performance, the Path2D
object, available in recent versions of browsers, lets you cache or record these drawing commands. You are able to play back your paths quickly.
Lets see how we can construct a Path2D
object:
Path2D()
- The
Path2D()
constructor returns a newly instantiatedPath2D
object, optionally with another path as an argument (creates a copy), or optionally with a string consisting of SVG path data.
new Path2D(); // empty path object new Path2D(path); // copy from another path new Path2D(d); // path from from SVG path data
All path methods like moveTo
, rect
, arc
or quadraticCurveTo
, etc., which we got to know above, are available on Path2D
objects.
The Path2D
API also adds a way to combine paths using the addPath
method. This can be useful when you want to built objects from several components, for example.
Path2D.addPath(path [, transform])
- Adds a path to the current path with an optional transformation matrix.
Path2D example
In this example, we are creating a rectangle and a circle. Both are stored as a Path2D
object, so that they are available for later usage. With the new Path2D
API, several methods got updated to optionally accept a Path2D
object to use instead of the current path. Here, stroke
and fill
are used with a path argument to draw both objects onto the canvas, for example.
<html> <body onload="draw();"> <canvas id="canvas" width="130" height="100"></canvas> </body> </html>
function draw() { var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); var rectangle = new Path2D(); rectangle.rect(10, 10, 50, 50); var circle = new Path2D(); circle.moveTo(125, 35); circle.arc(100, 35, 25, 0, 2 * Math.PI); ctx.stroke(rectangle); ctx.fill(circle); } }
Screenshot | Live sample |
---|---|
Using SVG paths
Another powerful feature of the new canvas Path2D
API is using SVG path data to initialize paths on your canvas. This might allow you to pass around path data and re-use them in both, SVG and canvas.
The path will move to point (M10 10
) and then move horizontally 80 points to the right (h 80
), then 80 points down (v 80
), then 80 points to the left (h -80
), and then back to the start (z
). You can see this example on the Path2D
constructor page.
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");