Тег <canvas>
появился HTML5, но с точки зрения управления им, как тегом или записи для него css-свойств он малоинтересен. Смысл его использования виден только при применении JavaScript, т.к. именно код на JS способен оживить и анимировать рисунке на холсте (канве).
Тег <canvas>
в какой-то степени похож на <svg>
- он тоже дает по умолчанию прямоугольную область размером 300x150px и предназначен для рисования программными методами. Но в <svg>
это происходит с помощью вложенных тегов, а в <canvas>
- с помощью JS-кода. Как правило, для тега <canvas>
задают атрибут id
:
1 | <canvas id="my-canvas"></canvas> |
Также можно изменить размеры прямоугольной области <canvas>
с помощью атрибутов width
и height
:
1 | <canvas id="my-canvas" width="450" height="500"></canvas> |
Ни в первом, ни во втором случае вы не увидите этой прямоугольной области, если выше/ниже него нет текста или пока не заданы css-свойства в виде background
или border
, т.к. canvas
- это прозрачный по умолчанию элемент:
1 2 3 4 5 6 7 8 9 10 11 | <style> #start { background-color: #ccc; margin: 20px; border: 3px solid #777; width: 500px; height: 250px; } </style> <canvas id="start"></canvas> |
Также в css можно управлять размерами холста с помощью css-свойств width
и height
.
Теперь мы видим, где расположен наш тег <canvas>
:
Можно сказать, что холст (canvas) представляет собой прямоугольную область на экране, в которой вы можете рисовать фигуры - прямоугольники или круги, линии - прямые и изогнутые, изображения и даже текст. Первыми, кто внедрил canvas
была фирма Apple, и этот элемент прижился настолько, что попал в стандарт HTML5. Еще ранее подобные инструменты существовали во Flash (сейчас ее заменила Adobe Animate) в языке ActionScript, который базируется на тех же стандартах EcmaScript, что и JavaScript для браузеров. Кстати, та же компания Apple, которая вывела canvas на html-страницы, изрядно поспособствовала пропаже с них flash-роликов.
В отличие от SVG - формата векторной графики - canvas позволяет рисовать с помощью пикселей, а не векторных кривых. Поэтому и возможности этих 2-х форматов отличаются. SVG - это набор тегов, каждым из которых можно управлять и с точки зрения CSS, и с точки зрения управления DOM-структурой в JavaScript. А canvas
- это набор команд, которыми вы будете преимущественно управлять из JavaScript. Хотя есть ряд возможностей, которые доступны и в SVG, и в Canvas, что делает их похожими. На одной и той же html-странице вы можете использовать и тег (и) <svg>
, и тег(и) <canvas>
, и даже накладывать их друг на друга с помощью CSS.
Сейчас нам надо будет разобраться с рисованием на canvas
, и начнем с простых фигур, которые в графических программах называют примитивами.
Рисуем прямоугольники в canvas
Для рисования на canvas
для начала нужно обратиться к самому элементу. Проще и быстрее всего это сделать с помощью метода document.getElementById('some-id')
. А затем сохранить в переменную (например, context
) ссылку на двухмерный контекст методом getContext('2d')
.
Рисование прямоугольника предполагает использование метода context.fillRect(x, y, width, height)
:
1 2 3 4 5 6 7 | <canvas width="500" height="250" id="for_rect"></canvas> <script> var canvas = document.getElementById('for_rect'); var context = canvas.getContext('2d'); context.fillStyle = "green"; context.fillRect(50,50,400,200); </script> |
В примере ниже на холсте в 500x200px мы нарисовали прямоугольник 400x200px с заливкой зеленого цвета, сместившись на 50px вправо и на 50px вниз.
Примечание: для того чтобы можно было ориентироваться в координатах, для canvas был добавлен фон в виде сетки с шагом в 50px, которую вы можете скачать для собственных экспериментов.
Также для создания прямоугольника без заливки, только с контуром, используется метод:
1 | context.strokeRect(x, y, width, height) |
Для того чтобы стереть прямоугольник, нужен метод:
1 | context.clearRect(x, y, width, height) |
Этот метод может также использоваться, чтобы стереть все, что нарисовано на вашем холсте:
1 | context.clearRect(0, 0, canvas.width, canvas.height); |
С помощью всех 3-х методов можно сделать рамку вместо прямоугольника.
Код примера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <canvas width="600" height="300" id="rect2" class="grid"></canvas> <script> var canvas = document.getElementById('rect2'); var context = canvas.getContext('2d'); context.fillRect(50, 50, 200, 200); context.clearRect(100, 100, 100, 100); context.strokeRect(110, 110, 80, 80); context.strokeRect(60, 60, 180, 180); context.fillStyle = "#900"; context.strokeStyle = "#f00"; context.fillRect(300, 50, 200, 200); context.clearRect(350, 100, 100, 100); context.strokeRect(360, 110, 80, 80); context.strokeRect(310, 60, 180, 180); </script> |
Обратите внимание, что в коде использованы еще 2 метода:
1 2 | context.fillStyle = "#900"; context.strokeStyle = "#f00"; |
Эти методы задают цвет заливки и обводки в canvas
. Если вы этого не делаете, то по умолчанию цвет заливки и контура будет черным, и это видно на примере первых прямоугольников. Можно задавать цвет любым способом, доступным в CSS: "red", "#ff0000", "rgb(255,0,0)", "rgba(255,0,0,0.5)", а также использовать градиенты и текстуры в виде изображений.
Для управления толщиной линии (контура) используется свойство lineWidth
(по умолчанию равно 1px):
1 | context.lineWidth = 5; |
Рисуем линии, или пути (path), в canvas
Если разбирать процесс создания контуров в графических программах, то любой дизайнер или обычный пользователь сначала выбирает место, в котором начнется рисование и перемещает к нему курсор мыши, а потом начинает создавать линии, кликая курсором последовательно в различных точках рабочей области.
Аналогично выполняется процесс и в JavaScript (и в его брате ActionScript). Поэтому в canvas
существуют методы, подобные действиям в программах. Сначала мы перемещаемся в точку с координатами x, y
, ничего пока не рисуя:
1 | context.moveTo(x,y); |
Затем создаем прямую линию, идущую от тех координат, которые мы задали в moveTo(x,y)
до новых координат x1, y1
в методе lineTo()
:
1 2 3 4 | context.lineTo(x1,y1); context.lineTo(x2,y2); ... context.lineTo(xn,yn); |
Этот метод может быть вызван любое количество раз в зависимости от того, какая вам нужна фигура. Учтите, что рисование начинается с точки, указанной в moveTo()
и продолжается до точки с координатами в методе lineTo()
, причём конечная точка первой линии (пути) является начальной точкой второй линии и т. д. Можно использовать метод moveTo()
, чтобы перенестись в другую точку.
Для создания контура сначала нужно указать его начало методом:
1 | context.beginPath() |
Выполняем обводку фигуры, добавляя к ней контур:
1 | context.stroke() |
Выполняем заливку фигуры:
1 | context.fill() |
Закрываем контур:
1 | context.closePath() |
Для фигур с заливкой закрывать контур (путь) необязательно - это сделает заливка по кратчайшему расстоянию между двумя соседними точками. Для фигуры с одной обводкой нужно либо закрыть контур (context.closePath()
), либо дорисовать контур до начальной точкой методом lineTo(xstart, ystart)
.
Нарисуем елку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <canvas width="600" height="300" id="path"></canvas> <script> var path = document.getElementById('path'); var context = path.getContext('2d'); context.strokeStyle = "#048012"; context.lineWidth = 3; context.fillStyle = "#0aad1c"; context.beginPath(); context.moveTo(300, 75); context.lineTo(425, 175); context.lineTo(175, 175); context.lineTo(300, 75); context.moveTo(300, 125); context.lineTo(475, 275); context.lineTo(125, 275); context.lineTo(300, 125); context.moveTo(300, 25); context.lineTo(375, 100); context.lineTo(225, 100); context.lineTo(300, 25); context.closePath(); context.stroke(); //context.fill(); </script> |
В начале и в конце пути мы указали, что мы начинаем (метод beginPath()
) и заканчиваем (метод closePath()
) контур (путь), а в середине перемещались к определенным координатам методом moveTo()
и проводили линии методом lineTo()
. Чтобы увидеть контуры нам понадобился метод stroke()
, а заливку добавим, раскомментировав последнюю строку context.fill()
(в примере ниже кликнув на кнопку).
У контуров есть еще ряд свойств:
Свойство/Метод | Значение | Назначение |
---|---|---|
lineCap | "butt" | "round" | "square" | Устанавливает внешний вид концов линий |
lineJoin | "bevel" | "round" | "miter" (по умолчанию) | Устанавливает внешний вид углов, которые получаются при соединении линий |
miterLimit | Положительное число | Определяет максимальную длину среза линий в месте соединения. Если текущая длина среза будет превышать заданное значение, то угол будет отображаться как при значении "bevel" свойства lineJoin |
setLineDash(массив) | Массив чисел [линия, пропуск] | Определяет размер линий и пустых мест в пунктирной линии |
getLineDash() | возвращает массив чисел [линия, пропуск] | Возвращает массив чисел для линий и пустых мест в пунктирной линии |
lineDashOffset | Число с плавающей запятой (0.0 по умолчанию) | Указывает, где следует начинать тире массива в строке. |
Пример со свойством lineJoin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <canvas id="lineJoin" class="grid" width="500" height="210"></canvas> <script> var ljCanvas = document.getElementById('lineJoin'), ctx = ljCanvas.getContext('2d'); ctx.lineWidth = 15; ctx.strokeStyle = "rgba(255,9,9,.5)"; ctx.lineJoin = "bevel"; ctx.beginPath(); ctx.moveTo(50, 150); ctx.lineTo(100, 50); ctx.lineTo(150, 150); ctx.closePath(); ctx.stroke(); ctx.lineJoin = "round"; ctx.beginPath(); ctx.moveTo(200, 150); ctx.lineTo(250, 50); ctx.lineTo(300, 150); ctx.closePath(); ctx.stroke(); ctx.lineJoin = "miter"; ctx.beginPath(); ctx.moveTo(350, 150); ctx.lineTo(400, 50); ctx.lineTo(450, 150); ctx.closePath(); ctx.stroke(); </script> |
Результат: сначала "bevel"
, затем "round"
и за ним "mitter"
.
Пример со свойством lineCap
Для демонстрации различных концов наших линий нарисуем сначала направляющие, которые определяют начало и конец каждого отрезка, а затем уже сами линии-отрезки. обратите внимание, что 2 последних варианта свойства lineCap приводят к увеличению длины отрезка.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <canvas id="lineCap" class="grid" width="400" height="180"></canvas> <script> var lcCanvas = document.getElementById('lineCap'), ctx = lcCanvas.getContext('2d'); const lineCap = ['butt', 'round', 'square']; // Рисуем направляющие ctx.strokeStyle = '#7278ff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(50, 30); ctx.lineTo(50, 180); ctx.moveTo(250, 30); ctx.lineTo(250, 180); ctx.stroke(); // Рисуем линии с разными концами ctx.strokeStyle = 'rgba(153, 0, 0, 0.8)'; for (let i = 0; i < lineCap.length; i++) { ctx.lineWidth = 15; ctx.lineCap = lineCap[i]; ctx.beginPath(); ctx.moveTo(50, 50 + i * 50); ctx.lineTo(250, 50 + i * 50 ); ctx.stroke(); } </script> |
Результат: сначала "butt"
, затем "round"
и за ним "square"
.
Пример использования методов setLineDash() и getLineDash() для рисования пунктирных линий
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <canvas id="dashed" class="grid" width="400" height="180"></canvas> <script> var dashed = document.getElementById('dashed'), ctxD = dashed.getContext('2d'); ctxD.lineWidth = 5; ctxD.setLineDash([30, 10]); console.log(ctxD.getLineDash()); // [30, 10] // Рисуем 1-ую пунктирную линию ctxD.beginPath(); ctxD.moveTo(50, 50); ctxD.lineTo(350, 50); ctxD.stroke(); // Рисуем 2-ую пунктирную линию ctxD.setLineDash([30, 10, 5, 10]); console.log(ctxD.getLineDash()); // [30, 10, 5, 10] ctxD.beginPath(); ctxD.moveTo(50, 100); ctxD.lineTo(350, 100); ctxD.stroke(); //3-я линия со смещением штриха ctxD.setLineDash([20, 15]); ctxD.strokeStyle = '#1371ce'; ctxD.lineDashOffset = 10.5; ctxD.beginPath(); ctxD.moveTo(50, 150); ctxD.lineTo(350, 150); ctxD.stroke(); </script> |
При создании пунктирных линий имеет смысл использовать в массиве, передаваемом в метод setLineDash()
2, 4, 6 параметров (т.е. четное их количество), чтобы линия получилась красивой.
В этом примере для последней линии было используется свойство lineDashOffset
, которое смещает начало линии на указанное количество пикселей так, как это показано на картинке:
С помощью свойства lineDashOffset
можно создать "марширующих муравьев", которые в Photoshop, например, показывают область выделения. Код для их создания таков:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <canvas id="marching_ants" class="grid" width="300" height="250"></canvas> <script> const ants = document.getElementById('marching_ants'); const myCtx = ants.getContext('2d'); let offset = 0 myCtx.lineWidth = 3; const drawAnts = () => { myCtx.clearRect(0, 0, canvas.width, canvas.height); myCtx.setLineDash([6, 3]); myCtx.lineDashOffset = -offset; myCtx.strokeRect(50, 50, 150, 150); } const marchingAnts = () => { offset++; if (offset > 18) { offset = 0; } drawAnts(); setTimeout(marchingAnts, 20); } marchingAnts(); </script> |
Результат:
Рисуем дуги вместо круга в canvas
К сожалению, с кругом (овалом) далеко не так все просто, как в графических программах или в svg
. В canvas не получится нарисовать circle
, т.к. здесь доступно создание дуги (arc
), которая имеет центр с координатами в x,y
, начало, заданное углом startAngle
, и конец, заданный углом endAngle
(оба угла задаются в радианах), а также логический параметр true|false
, который говорит о том, создается ли дуга против или по ходу часовой стрелки (по умолчанию используется параметр по ходу часовой стрелки):
1 | arc(x,y, radius, startAngle, endAngle, anticlockwise); |
Стоит остановится на углах. В отличие от css-свойства tranform: rotate(45deg)
, в котором, в основном, используются градусы, для arc используются радианы. Один радиан - это значение π, которое равно примерно 3.14. Ему соответствует угол в 180 ° и константа математического класса Math
в JavaScript - Math.PI
. Поэтому, мы можем использовать эту константу, чтобы рассчитывать углы. Придется вспомнить слегка курс школьной алгебры или геометрии или использовать такую формулу для перевода градусов в радианы:
1 | let radians = (Math.PI/180)*degrees. |
Пробуем на примере нарисовать 2 круга с заливкой и с контуром. Последний параметр является необязательным, поэтому во втором случае мы его опускаем - и тоже получаем круг.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <canvas width="500" height="250" id="for_arc" class="grid"></canvas> <script> var canvas = document.getElementById('for_arc'); var context = canvas.getContext('2d'); context.fillStyle = "blue"; context.arc(100, 100, 50, 0, 2 * Math.PI, false); context.fill(); context.strokeStyle = "#7278ff"; context.beginPath(); context.arc(300, 150, 100, 0, 2 * Math.PI); context.stroke(); </script> |
Пример вживую:
Не всегда нужно нарисовать полный круг, поэтому рассмотрим пример, где дугу можно нарисовать размером в 120°. Разница между двумя дугами заключается в рисовании по (120°) и против часовой стрелки(240°) , а также в заливке полученной фигуры и заливке только контура.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <canvas width="500" height="250" id="myArc" class="grid"></canvas> <script> var canvas = document.getElementById('myArc'); var context = canvas.getContext('2d'); context.fillStyle = "red"; context.lineWidth = 3; context.arc(100, 100, 100, 0, 2 * Math.PI/3, false); context.fill(); context.strokeStyle = "#7278ff"; context.beginPath(); context.arc(350, 100, 100, 0, 2 * Math.PI/3, true); context.stroke(); </script> |
Рисуем дугу с заданными контрольными точками и радиусом, соединяя эти точки прямой линией.
1 | context.arcTo(x1, y1, x2, y2, radius); |
В этом случае нам понадобятся касательные линии.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | <canvas id="arcTo" class="grid" width="500" height="300"></canvas> <script> const arcToCanvas = document.getElementById('arcTo'); const ctxArcTo = arcToCanvas.getContext('2d'); // Касательные линия ctxArcTo.beginPath(); ctxArcTo.strokeStyle = 'gray'; ctxArcTo.moveTo(200, 120); ctxArcTo.lineTo(200, 230); ctxArcTo.lineTo(50, 120); ctxArcTo.lineTo(200, 230); ctxArcTo.lineTo(350, 120); ctxArcTo.stroke(); // Левая дуга ctxArcTo.beginPath(); ctxArcTo.strokeStyle = 'black'; ctxArcTo.lineWidth = 5; ctxArcTo.moveTo(200, 120); ctxArcTo.arcTo(200, 230, 50, 120, 40); ctxArcTo.stroke(); // Начальная точка ctxArcTo.beginPath(); ctxArcTo.fillStyle = 'blue'; ctxArcTo.arc(200, 120, 5, 0, 2 * Math.PI); ctxArcTo.fill(); // Правая дуга ctxArcTo.beginPath(); ctxArcTo.strokeStyle = 'black'; ctxArcTo.lineWidth = 5; ctxArcTo.moveTo(200, 120); ctxArcTo.arcTo(200, 230, 350, 120, 40); // Большой круг ctxArcTo.stroke(); ctxArcTo.beginPath(); ctxArcTo.arc(200, 120, 120, 0, 2 * Math.PI); ctxArcTo.stroke(); //Глаза ctxArcTo.beginPath(); ctxArcTo.fillStyle = 'black'; ctxArcTo.arc(150, 100, 10, 0, 2 * Math.PI); ctxArcTo.arc(250, 100, 10, 0, 2 * Math.PI); ctxArcTo.fill(); // Контрольные точки ctxArcTo.beginPath(); ctxArcTo.fillStyle = 'red'; ctxArcTo.arc(200, 230, 5, 0, 2 * Math.PI); // Первая контрольная точка ctxArcTo.closePath(); ctxArcTo.arc(50, 120, 5, 0, 2 * Math.PI); // Вторая контрольная точка ctxArcTo.closePath(); ctxArcTo.arc(350, 120, 5, 0, 2 * Math.PI); // Третья контрольная точка ctxArcTo.fill(); </script> |
Выглядит этот код, как голова животного
Для того чтобы создавать изображения и даже использовать видео в canvas, вам понадобится функция drawImage()
. О ней подробнее можно почитать в статье " Рисуем изображения в canvas".
Много уроков с пошаговой прорисовкой в canvas на generativeartistry.com