6 января 2011 в 15:08

CANVAS шаг за шагом: Основы из песочницы tutorial

CANVAS шаг за шагом:
  1. Основы
  2. Изображения
  3. Понг
  4. Пятнашки

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

Предварительная «настройка» нашего холста


У нашего подопытного тега есть всего два атрибута — height и width, высота и ширина соответственно, по умолчанию размер холста 150х300 пикселей.
Стоит отметить что canvas создает область фиксированного размера содержимым которого управляют контексты.
Элементарный пример:
<!doctype html>
<html>
	<head>
		<title>canvasExample</title>
        <meta charset='utf-8' />
	</head>
	<body>
		<canvas height='320' width='480' id='example'>Обновите браузер</canvas>
		<script>
			var example = document.getElementById("example"),
			    ctx     = example.getContext('2d');
			ctx.fillRect(0, 0, example.width, example.height);
		</script>
	</body>
</html>

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

Прямоугольники


Самой элементарной фигурой которую можно рисовать является прямоугольник. Предусмотрено три функции для отрисовки прямоугольников.
strokeRect(x, y, ширина, высота) // Рисует прямоугольник
fillRect(x, y, ширина, высота)   // Рисует закрашенный прямоугольник
clearRect(x, y, ширина, высота)  // Очищает область на холсте размер с прямоугольник заданного размера

Пример иллюстрирующий работу этих функций:
<!doctype html>
<html>
    <head>
        <title>rectExample</title>
        <meta charset='utf-8' />
    </head>
    <body>
        <canvas id='example'>Обновите браузер</canvas>
        <script>
            var example = document.getElementById("example"),
			    ctx     = example.getContext('2d');
            example.width  = 640;
            example.height = 480;
            ctx.strokeRect(15, 15, 266, 266);
            ctx.strokeRect(18, 18, 260, 260);
            ctx.fillRect(20, 20, 256, 256);
            for (i = 0; i < 8; i += 2)
                for (j = 0; j < 8; j += 2) {
                    ctx.clearRect(20 + i * 32, 20 + j * 32, 32, 32);
                    ctx.clearRect(20 + (i + 1) * 32, 20 + (j + 1) * 32, 32, 32);
                }
        </script>
    </body>
</html>

А теперь краткий построчный разбор:
в строках 10 и 11 мы изменили размер холста — чтоб бы задуманное нами изображение полностью отобразилось,
в строках 12 и 13 мы нарисовали два не закрашенных прямоугольника которые будут символизировать своеобразную рамку нашей «шахматной доски»,
в строке 14 отрисовываем закрашенный прямоугольник размеры которого бы позволил вместить в себе 64 квадрата с шириной стороны 32 пикселя,
в строках с 15 по 19 у нас работает два цикла которые очищают на чёрном прямоугольнике квадратные области в таком порядке что бы в итоге полученное изображение было похоже на шахматную доску

Линии и дуги


Рисование фигур составленных из линий выполняется последовательно в несколько шагов:
beginPath()
closePath()
stroke()
fill()

beginPath используется что бы «начать» серию действий описывающих отрисовку фигуры. Каждый новый вызов этого метода сбрасывает все действия предыдущего и начинает «рисовать» занова.
closePath является не обязательным действием и по сути оно пытается завершить рисование проведя линию от текущей позиции к позиции с которой начали рисовать.
Завершающий шаг это вызовом метода stroke или fill. Собственно первый обводит фигуру линиями, а второй заливает фигуру сплошным цветом.
Те кто когда-то на школьных 486х в былые годы рисовал в бейсике домик, забор и деревце по задумке учителя тот сразу поймёт часть ниже. Итак, существуют такие методы как,
moveTo(x, y) // перемещает "курсор" в позицию x, y и делает её текущей
lineTo(x, y) // ведёт линию из текущей позиции в указанную, и делает в последствии указанную текущей
arc(x, y, radius, startAngle, endAngle, anticlockwise) // рисование дуги, где x и y центр окружности, далее начальный и конечный угол, последний параметр указывает направление

Пример ниже показывает действие всего описанного выше:
<!doctype html>
<html>
	<head>
		<title>pathExample</title>
		<meta charset='utf-8' />
	</head>
	<body>
		<canvas id='example'>Обновите браузер</canvas>
		<script>
			var example = document.getElementById("example"),
			    ctx     = example.getContext('2d');
			example.height = 480;
			example.width  = 640;
			ctx.beginPath();
			ctx.arc(80, 100, 56, 3/4 * Math.PI, 1/4 * Math.PI, true);
			ctx.fill(); // *14
			ctx.moveTo(40, 140);
			ctx.lineTo(20, 40);
			ctx.lineTo(60, 100);
			ctx.lineTo(80, 20);
			ctx.lineTo(100, 100);
			ctx.lineTo(140, 40);
			ctx.lineTo(120, 140);
			ctx.stroke(); // *22
		</script>
	</body>
</html>

В строке 14 заливается цветом дуга, в строке 22 обводится контур нашей короны.

Кривые Бернштейна-Безье


Что такое кривые Безье я думаю лучше объяснит Википедия.
Нам доступно две функции, для построения кубической кривой Бизье и квадратичной, соотвестствено:
quadraticCurveTo(Px, Py, x, y) 
bezierCurveTo(P1x, P1y, P2x, P2y, x, y)

x и y это точки в которые необходимо перейти, а координаты P(Px, Py) в квадратичной кривой это дополнительные точки которые используются для построения кривой. В кубическо кривой соответственно две дополнительные точки.
Пример двух кривых:
<!doctype html>
<html>
	<head>
		<title>curveExample</title>
		<meta charset='utf-8' />
	</head>
	<body>
		<canvas id='example'>Обновите браузер</canvas>
		<script>
			var example = document.getElementById("example"),
			    ctx     = example.getContext('2d');
			example.height = 480;
			example.width  = 640;
			ctx.beginPath();
			ctx.moveTo(10, 15);
			ctx.bezierCurveTo(75, 55, 175, 20, 250, 15);
			ctx.moveTo(10, 15);
			ctx.quadraticCurveTo(100, 100, 250, 15);
			ctx.stroke();
		</script>
	</body>
</html>

Добавим цвета


Что бы наше изображение было не только двух цветов, а любого цвета предусмотрено, два свойства
fillStyle = color   // определяет цвет заливки 
strokeStyle = color // цвет линий цвет задается точно так же как и css, на примере все четыре способа задания цвета

Цвет задается точно так же как и css, на примере все четыре способа задания цвета
// все четыре строки задают оранжевый цвет заливки
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)"

Аналогично задаётся и цвет для линий.
Возьмём пример с шахматной доской и добавим в него немного цвета:
<!doctype html>
<html>
	<head>
		<title>rectExample</title>
		<meta charset='utf-8' />
	</head>
	<body>
		<canvas id='example'>Обновите браузер</canvas>
		<script>
			var example = document.getElementById("example"),
			    ctx     = example.getContext('2d');
			example.height = 480;
			example.width  = 640;
			ctx.strokeStyle = '#B70A02'; // меняем цвет рамки
			ctx.strokeRect(15, 15, 266, 266);
			ctx.strokeRect(18, 18, 260, 260);
			ctx.fillStyle = '#AF5200'; // меняем цвет клеток
			ctx.fillRect(20, 20, 256, 256);
			for (i = 0; i < 8; i += 2)
				for (j = 0; j < 8; j += 2) {
					ctx.clearRect(20 + i * 32, 20 + j * 32, 32, 32);
					ctx.clearRect(20 + (i + 1) * 32, 20 + (j + 1) * 32, 32, 32);
				}
		</script>
	</body>
</html>

Задача


Что бы усвоить информацию и закрепить прочитанное на практике я всегда ставлю перед собой не большую цель которая бы одновременно охватывала всё прочитанное и одновременно процесс достижения которой было бы интересен мне самому. В данном случае я попытаюсь отрисовать уровень одной из моих самых любимых в детстве игр. Собственно за не имением времени — добавлять жизнь на него я не буду, а сделаю максимально понятный код охватывающий практически всё то что сегодня здесь описал.
Я воспроизвел один из уровней игры BattleCity известную у нас как Танчики, а вот и ссылка на pastebin на случай если не будет откликаться дропбокс.
На последок комментарий по примеру. В спецификациях картинки которую может выдавать Денди разрешение экрана должно быть 256×240 пикселей.
Поле боя в общеизвестнных Танчиках размером 13х13 больших блоков. Каждый из которых нарисован 4мя повторяющимися спрайтами (коих по общему подсчёту выходит на карте 26х26=676). Итак прикинем как было в оригинале по пикселам и как это правильно масштабировать. Если поделить 240 на 26 то выйдет что целая часть от деления будет 8. Получается что размерность текстуры была 8х8 пиксела т.е. размер поля боя 208х208, а большого блока 16х16. Ширина должна быть 256 пикселов. Сейчас вычислим размер правого столбца с дополнительной информацией и размер полей сверху/снизу. Справа если присмотреться ширина составляет размерность в два блока, итого 2*16=32. У нас уже 32+208=240 слева поле 16, а снизу и сверху соответственно так же по 16 пикселов. Собственно в моём примере размерность большого блока заключена в переменной cellSize, собственно все вычисления делаются иходя из её размеров. Можете по экспериментировать и поменять её значение, настоятельно рекомендую делать его кратным степеням двойки (16, 32, 64, 128...), если хотите чтоб всё выглядело так как на старом добром денди то устанавливайте её значение равным 16. Хотя и при любых других значениях всё выглядит нормально. Если то как я пишу понравится кому-то кроме меня, то напишу продолжение, а то что в нём будет пока утаю

Смотрите в следующей серии: CANVAS шаг за шагом: изображения
+80
65999
396
shpaker 10,3

Комментарии (48)

+9
vnmukhin, #
Чем больше статей про canvas, тем лучше. Простые статьи, подвигающие таких неспецов, как я «на попробовать» это очень круто.
0
shpaker, #
Я хотел ещё в формате статьи разжевать основы JavaScript'а, но потом решил что это было бы излишним, хотя если надо могу по ходу действия вставлять комментарии к циклам, условиям и т.д.
+3
vnmukhin, #
Мне кажется, перенасыщать информацией тоже не стоит. Само собой, если применяете какую-то особенную js-магию, то пояснять и разжевывать нужно, а для стандартных вещей — нет. И да, комментарии привычнее читать все же в коде (но это так, брюзжание) :)
0
freeozone, #
Вы постепенно пишите. Маленькими, но непрекращающимися шажками получится отличный цикл статей, если не надоест.
0
shpaker, #
Так и делаю. Благо есть опыт ведения блогов, и мысль о том что один пост можно писать в несколько присестов перед тем как его выкинуть в сеть меня не смущает
0
GoodwinNK, #
Картиночки бы :) Хотя может быть Вы так и задумывали — хочешь картинку, изволь скопировать и попробовать?
+2
shpaker, #
Да, в точку. С картинками мало бы кто попробовал хотя б обвести код или на него глянуть, просто посмотрев результаты. А тут вот так стимулирую народ. На последний пример хотел скрин вставить, но решил обойтись ссылкой на дропбокс
+1
guyfawkes, #
Очень хотелось бы увидеть реализацию чего-то «3D», вроде вращения объектов вокруг оси, с пояснением математики этого дела :)
+1
shpaker, #
3д возможно, но пока 2д и только 2д
+1
babarun, #
Пожалуйте babarun.ru/content/canvas/, а пояснения в коде в виде комментариев.
+1
guyfawkes, #
Вы знаете, в свое время читал вашу статью, но так и не смог все осилить. Все эти нормали для меня — темный лес :) Все-таки для таких тугодумов, как я, надо проще и подробнее.
0
babarun, #
Тогда для начала очень рекомендую Вот эту литературу
0
Aliance, #
В коде очень много переменных с непонятными названиями, некоторые из них можно было бы назвать согласно их сути.
0
qmax, #
меня вот тоже заинтриговала цифра 2 в вызове var ctx = example.getContext('2d'); :))

а математика вращений пояснена чуть более чем дофига везде.
0
guyfawkes, #
Будьте добры, поделитесь одной хорошей ссылкой для таких, как я (хотя, может, я и в единственном числе такой).
+1
shpaker, #
Я думаю тебе будет интересен вот этот пример. Там правда код без комментариев, но сделано достаточно просто
0
guyfawkes, #
Благодарю.
0
guyfawkes, #
Да уж, вам просто, а код не так уж прост :)
0
TheShock, #
0
shpaker, #
ох щи их тайлы оч сильно напоминают графику из вольфинштейна 3д
0
TheShock, #
I wanted to implement for some time was a psuedo-3D engine such as the one used in the old Wolfenstein 3D game by iD Software
0
guyfawkes, #
Черт побери, реально отличная статья! Спасибо)
0
TheShock, #
You are welcome =)
+1
qmax, #
в стародавние времена, когда не было ни гугла, ни википедии, и почти не было интернетов,
источником знаний по алгоритмама компьютерной графики была usenet конфа comp.graphics.algorithms
ответы на все нубовские вопросы оттуда собраны в вики www.cgafaq.info/

0
guyfawkes, #
Спасибо. Информации много, буду потихоньку разбираться.
+1
fls_welvet, #
Спасибо. Продолжайте цикл статей. Хотелось бы увидеть пример создания мини-игрушки, хотя бы тех же Танчиков.
0
shpaker, #
Всё будет, не всё сразу ) Было бы время. Пример игрушки рабочей появится только после того как все основы протопчем.
0
freeozone, #
Есть идея: если будете продолжать статьи писать такие обучающие, то давайте в конце небольшое, но интересное задание для самостоятельного выполнения, как «домашку».
0
shpaker, #
Да клёвая идея. Сейчас подкорректирую немного пост и допишу «домашку», благо она сама там напрашивается. А кстати такой метод ведения постов (с заданиями) недавно видел у художника Антона Карлова
0
kirsan_vlz, #
А ведь есть же для браузеров что-то вроде openGL, где часть математического аппарата по прорисовке скрыта?
+2
shpaker, #
Есть WebGL который сейчас активно выпиливается для отображения 3д графики в браузерах. Я до него ещё не доходил в силу таинственной привязанности к 2д картинке
0
kirsan_vlz, #
Ну я так, на будущее.
На хабре пошли статьи про OpenGL, про Canvas, стало интересно =)
В будущих статьях, после основ, не планируете про него писать?
+1
shpaker, #
Да я вообще особо не планировал. Просто хотелось написать пост разжевывающий основы, но вот всё то что хотелось написать в один не вместилось, да и у людей столько интереса что грех не продолжить. Пока в планах 3д явно нету, но если интерес и дальше не утухнет, и я доделаю то что запланировал, то и до него доберусь. В январе не обещаю особой активности ибо с понедельника сессия, а я даже не знаю какие предметы будут)
+1
guyfawkes, #
А можно такой вопрос: если я хочу, чтобы, скажем, верхняя кривая была одного цвета, а нижняя — другого, то мне придется вызывать после отрисовки кривой stroke(), а затем beginPath() для рисования нижней кривой? Спасибо.
+2
shpaker, #
Да, я так делаю
    ctx.strokeStyle = '#f00';
    ctx.beginPath();
    ctx.moveTo(10, 15);
    ctx.bezierCurveTo(75, 55, 175, 20, 250, 15);
    ctx.stroke();
    ctx.strokeStyle = '#0f0';
    ctx.beginPath();
    ctx.moveTo(10, 15);
    ctx.quadraticCurveTo(100, 100, 250, 15);
    ctx.stroke();
0
guyfawkes, #
Благодарю. Конечно, не очень удобно, на мой взгляд, с другой стороны, как я понял в результате эксперимента, позволяет одномоментно изменить цвет линий внутри begin...endPath()
0
shpaker, #
на самом деле всё правильно и логично, stroke() обрисовывает всё то что было после beginPath() и если добавить ещё один stroke(), то он последний обрисует все действия предыдущего + те которые после него шли. Т.е. некоторые линии нарисуются дважды и если менялся цвет то произойдет своеобразное наложение доминирующим цветом где будет последний примененный.
endPath на моей памяти вообще не существует, а closePath не обязателен к применению ))
0
guyfawkes, #
Простите, ошибся, closePath, конечно :) Спасибо за пояснение, теперь все стало ясно.
0
shpaker, #
перенёс в блог html5
0
Nutochka, #
Дело хозяйское, но обычно статьи про Canvas бросают в блог JavaScript ;)
0
shpaker, #
Ну я делаю акцент на канвас апи, а не на замысловатые алгоритмы. Поэтому тут я думаю уместней, тем более что никто прежде ничего не писал
+1
Nutochka, #
Ну, тогда с открытием!
Все-таки, думаю, Canvas заслуживает отдельного блога, пусть он и называется HTML5 =)
Один недостаток — люди, которые следят за блогом Javascript не получат этой статьи.
0
shpaker, #
Привыкнут. Я его сначала вообще в вэбдев кинул. Да и в дж.с. пост бы быстро утонул в море записей о джКвэри и прочих хитростей программирования на этом замечательном языке))
0
TheShock, #
0
Xumepa, #
Интересно, а вот как со всеми этими штуками строить процесс взаимодействия дизайнера и технолога? То есть с обычным растром все просто: нарисовали в фотошопе, обговорили, нарезали, отдали. А тут допустим вот я придумал картинку и, например, анимацию, даже нарисовал что-то в иллюстраторе (вектор все-таки), как мне потом отдать это разработке? Не по координатам же диктовать где какая дуга преломляется? Может есть какой-то инструментарий?

За статью спасибо, буду ждать продолжения.
+1
Nutochka, #
Ну, в Canvas обычно используется набор спрайтов. То есть ключевые кадры уже отрендерены.
Да, не так, как в Flash, но тут просто свой подход. =) Вот пример с «Asteroids» на LibCanvas:


НЛО прилетело и опубликовало эту надпись здесь
0
HomoLuden, #
и жаль, что производительность оставляет желать много лучшего

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