HTML5: Рисовалка на JavaScript Canvas. Основы.
HTML5, Программирование 20 Фев 2011
HTML5 — несомненный тренд. Причем, не столько новая спецификация с новыми тегами, сколько гармоничное и могучее сочетание HTML + CSS + JS, изящно утирающее нос Flash.
Уже сейчас можно наблюдать красивые HTML-5 сайты без мегабайтов изображений и без сотен килобайт скриптов. Например, фон вот этого сайта сделан при помощи JavaScript Canvas.
Как видите, JS Canvas действительно хорошая вещь. Именно ей я хочу посвятить первый пост свежеоткрытой рубрики «HTML5″. В нем я опишу процесс создания простенькой рисовалки на HTML5 с единственным инструментом «Pencil». Вот такую.
Общие сведения
Canvas позволяет рисовать в специальном элементе документа <canvas> линии, дуги, прямоугольники, окружности, заливать пространство, манипулировать текстом и картинками. Всё это прекрасно описано в «Canvas Tutorial« Mozilla Developers Center с примерами и картинками, прям как мне нравится.
Мы же напишем рисовалку, управлять которой будем при помощи специального объекта Canva, а сам инструмент «карандаш» (ну или «кисть») будет задан объектом Pencil.
Canva
Canva — управляющий объект. С его помощью мы переопределим реакцию страницы на события <canvas>, будем выбирать нужный инструмент, будем задавать свойства типа цвета заливки и толщины линий и пр. Вот его реализация:
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 57 58 59 60 61 62 63 64 65 66 67 | var Canva = {}; // Инициализация объекта Canva.init = function(id, width, height) { var canv = document.getElementById(id); canv.width = width; canv.height = height; this.canvasId = id; this.ctx = canv.getContext("2d"); // Свойства this.selectedColor = '#000000'; this.selectedFillColor = '#FFFFFF'; this.selectedWidth = 1; this.tool = Pencil; // Выбранный инструмент this.drawing = false; // true - если зажата кнопка мыши // Кнопка мыши зажата, рисуем canv.onmousedown = function(e) { var evnt = ie_event(e); Canva.tool.start(evnt); }; // Кнопка мыши отпущена, рисование прекращаем canv.onmouseup = function(e) { if (Canva.drawing) { var evnt = ie_event(e); Canva.tool.finish(evnt); } }; // процесс рисования canv.onmousemove = function(e) { if (Canva.drawing) { var evnt = ie_event(e); Canva.tool.move(evnt); } }; }; Canva.setTool = function(t) // Задать инструмент { Canva.tool = t; }; Canva.setWidth = function(width) // Задать толщину линий { Canvas.selectedWidth = width; }; Canva.setColor = function(color) // Задать текущий цвет { Canva.selectedColor = color; }; Canva.clear = function() // Очистить рисовалку { var canv = document.getElementById(Canva.canvasId); Canva.ctx.clearRect(0, 0, canvas.width, canvas.height); }; |
При инициализации объекта ему передается в качестве параметров id canvas, необходимые ширина и высота рисовалки.
функция ie_event — специальный костыль для IE (см. эту запись), вот ее реализация:
1 2 3 4 5 6 | function ie_event(e) { if (e === undefined) { return window.event; }; return e; } |
Pencil
Наш инструмент, которым мы будем рисовать, должен иметь функции move, start и finish. Принцип полиморфизма в действии.
Все нарисованное этим инструментом будет представлять собой последовательность прямых линий, коротких и не очень. Тут уже зависит от того, насколько быстр браузер пользователя.
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 | var Pencil = {}; // Начинаем рисование Pencil.start = function(evnt) { // Текущее положение мыши - начальные координаты Pencil.x = evnt.clientX; Pencil.y = evnt.clientY; Canva.ctx.beginPath(); // Свойства рисования Canva.ctx.strokeStyle = Canva.selectedColor; Canva.ctx.lineWidth = Canva.selectedWidth; Canva.ctx.moveTo(Pencil.x, Pencil.y); // Курсор на начальную позицию Canva.drawing = true; // Начато рисование }; // Рисование закончили Pencil.finish = function(evnt) { Pencil.x = evnt.clientX; Pencil.y = evnt.clientY; Canva.ctx.lineTo(Pencil.x, Pencil.y); // Дорисовываем последнюю линию Canva.drawing = false; }; // Рисование в разгаре Pencil.move = function(evnt) { Pencil.x = evnt.clientX; Pencil.y = evnt.clientY; Canva.ctx.lineTo(Pencil.x, Pencil.y); // Дорисовываем начатую линию Canva.ctx.stroke(); // Начинаем рисованть новую линию из той же точки. Canva.ctx.moveTo(Pencil.x, Pencil.y); }; |
Принцип такой: на нажатие кнопки мыши начинаем линию. На событие onmousemove (движение мыши) начатую линию дорисовываем и тут же начинаем новую. Так повторяется до тех пор, пока кнопка мыши не будет отпущена.
Использование
В header документа подключаем файл, содержащий весь приведенный выше код. В теле документа (после <canvas>!) вставляем JS-код с инициализацией объекта Canva:
1 2 3 4 | <script type="text/javascript"> // Сделаем рисовалку во всю страницу Canva.init('canvas', window.innerWidth, window.innerHeight); </script> |
Можно посмотреть рисовалку в действии: демо.
Internet Explorer
Поддержки canvas в IE 8 и младше нет. Но у доброго Google есть проект explorercanvas, который должен подружить IE с canvas. К сожалению, в моем случае этот костыль почему-то не работает, так что пользователи IE ничего порисовать не смогут. Сами виноваты.
Сохранение изображения на стороне клиента
Чтобы сохранить нарисованное в canvas как изображение на стороне клиента, можно сделать специальную кнопку и на onclick ей повесить событие
window.open(document.getElementById("canvas").toDataURL("image/png"), "new_window_name");
При нажатии на кнопку будет открыто новое окно (вкладка) с PNG-изображением.
Сохранение изображения на сервере
Так как предыдущий вариант работает через GET, зачастую он не применим для сохранения изображения на сервере из-за ограничения на длину URL. Тогда на помощь приходит вот эта библиотека.
Метки: html5, javascriptЕсть вопросы? Пишите в комментарии.

26 Мар 2011 в 19:21
Очень познавательно! Спасибо! Хотелось бы увидеть как динамически менять цвет и размер кисти.
26 Мар 2011 в 21:02
Например, можно задать разворачивающийся список (select) с цветами, а событие onChange для него определить следующим образом:
<select id=»colors» onchange=»Canva.setColor(this.options[this.selectedIndex].value);»>
<option value=»#FF0000″>красный</option>
<option value=»#00FF00″>синий</option>
</select>
27 Мар 2011 в 15:55
Спасиб большое!
01 Апр 2011 в 16:54
Играясь с данным кодом решил добавить дополнительные инструменты вроде рисование линий, прямоугольников и тд…
Но чтоб все это красиво выглядело необходимо сделать еще один канвас и разместить один под другим, не подскажите как правильно это сделать? или может где поподробнее об этом почитать?
02 Апр 2011 в 22:34
Я тоже столкнулся с данной проблемой. Решить ее у меня не было времени, до сих пор руки никак не доходят, так что увы, я вам ничем помочь не могу.
08 Май 2011 в 12:31
Исходник скрипта сам писал?
08 Май 2011 в 12:52
Да, сам.
09 Май 2011 в 00:45
Круто…Можно воспользоватся?…конечно со ссылкой на автора
09 Май 2011 в 13:58
Да, конечно, вы можете свободно использовать, модифицировать и совершенствовать код. Именно для этого я выложил исходники и описал принципы построения.
09 Июл 2011 в 22:57
Большое спасибо за эту статью! У меня только один вопрос — возможно ли каким-то образом сохранять результы?
09 Июл 2011 в 23:09
Влад, можно. Делаете кнопку и вешаете на нее следующее событие:
window.open(document.getElementById("canvas").toDataURL("image/png"),"new_window_name");При нажатии на кнопку в браузере открывается новое окно с png-изображением того, что было нарисовано в канве с id «canvas».
Сохранение изображения на сервере — совсем не тривиальная задача, в которой вам может помочь эта библиотека.
01 Окт 2011 в 01:41
Здравствуйте! Спасибо огромное за исходники, но я не могу понять как сделать так чтоб они работали). Я сохраняю текст исходника в файле paint.js, а потом в файле paint.html пишу:
Canva.init(‘canvas’, window.innerWidth, window.innerHeight);
Что я делаю не так?))
01 Окт 2011 в 01:43
Canva.init('canvas', window.innerWidth, window.innerHeight);01 Окт 2011 в 01:44
01 Окт 2011 в 01:44
Блин) не отображает теги
01 Окт 2011 в 19:42
Для начала нужно убедиться в правильности подключения скрипта. Затем в том, что Canva.init вызывается ниже объекта .
Если после этого все равно не работает, смотрите отладочную консоль JavaScript в браузере Chrome.
17 Окт 2011 в 17:42
Очень полезная статья, но вы бы не могли написать стутью о рисовалке, но не с инструментом «карандашом», а с кистью? Просто сколько уже голову ламаю, понять не могу как сделать кисть
25 Окт 2011 в 22:28
А чем кисть отличается от карандаша? Можно, например, толщину линий увеличить — и вы получите обычную кисть.
03 Ноя 2011 в 23:14
Спасибо ОГРОМНОЕ, очень полезная инфа!!!)
А можно ли использовать фон, например векторное изображение???
04 Ноя 2011 в 20:44
кисть круглая, а карандаш квадратный, но с этим разобрался, что насчёт прозрачности линий? просто с ней что-то не получается
06 Ноя 2011 в 10:03
Закругленность линий осуществляется с помощью свойств lineCap и lineJoin.
Для прозрачности линий укажите в strokeStyle значение типа ‘rgba(0, 0, 0, 0.5)’;
06 Ноя 2011 в 10:06
PALADII. Насчет вектора не знаю, никогда не работал, но обычное изображение можно поставить на фон канвы с помощью css.
08 Ноя 2011 в 21:33
modusponens, увы, не срабатывает, не хочет брать такой цвет, просто берёт чёрный вместо rgba()
09 Ноя 2011 в 17:48
Мне помнится, я делал полупрозрачные линии, но вот сейчас почему-то это действительно не получается. Увы, ничем не могу помочь. Если вы сможете решить эту проблему, прошу вас написать мне.
22 Ноя 2011 в 13:14
Прозрачность тут не отображается, потому что при рисовании куча линий накладывается друг на друга. Перед рисованием надо поставить globalCompositeOperation, например, на «xor» чтобы пересечения отсекались
26 Дек 2011 в 09:16
Спасибо за исходник! Немного переделал под свои нужды и заработало. Но, очень не хватает обработки колеса прокрутки мыши — нужно отловить события прокрутки вверх и вниз и повесить на них определённые функции. Помогите пожалуйста.
26 Дек 2011 в 16:51
Ну как мне известно, встроенных событий на прокрутку колесика мыши нет. Создать их можно самому, вот пример: http://www.adomas.org/javascript-mouse-wheel/ .
После создания события на него можно аналогично описанным выше способам повесить любую функцию.
26 Дек 2011 в 22:19
Код, аналогичный тому, что дан в примере на http://www.adomas.org у меня есть и работает при прокрутке над обычной картинкой jpg.
Проблемы как раз с созданием события для canvas — я не понимаю как свести концы с концами… Там просто листинг, на работающем примере было бы проще.
01 Май 2012 в 21:44
Спасибо вам большое за подробное описание! Очень познавательно, ведь я только начал изучать HTML5 и как раз с помощью таких примеров легче всего освоить его возможности.
А можно спросить, каким образом можно реализовать ластик или очистку экрана от нарисованного без перезагрузки страницы?
01 Май 2012 в 21:50
Ну очистка канвы в примере присутствует:
Инструмент «ластик» можно сделать аналогично «карандашу», только будет рисоваться не линия, а белый прямоугольник. Я, по-моему, реализовывал нечто подобное. Найду — выложу.
02 Окт 2012 в 17:35
Очень полезная статья, спасибо. Захотелось почитать Canvas Tutorial, который Вы привели, но там к сожалению, некоторые примеры, в частности https://developer.mozilla.org/en-US/docs/Drawing_text_using_a_canvas — aditional examples не работают — не понятно почему, Вы не планируете написать статью по этому направлению?
04 Окт 2012 в 22:26
Даша, все примеры замечательно работают в Firefox, но почему-то напрочь отказываются работать в моем Chrome.
С текстом в canvas я работал совсем немного, а времени более подробно изучать эту тему, увы, пока что нет. Но если руки дойдут, то обязательно напишу о своих успехах.
09 Окт 2012 в 14:45
Хорошая статья, спасибо! :)
Подскажите, можно ли подгрузить изображение на фон, нарисовать что-то поверх при помощи канвас, а потом сохранить исходное изображение с приклеенными к нему канвасовскими каляками?
Нужно это реализовать, не очень представляю с чего начать…
26 Окт 2012 в 17:22
Подскажите а как приделать кнопку с ластиком и чтоб можно было выбирать карандаш или стиратильную резинку ?
29 Окт 2012 в 00:01
Если очень быстро рисовать круги, то линия получается угловатая…в хроме
12 Фев 2013 в 01:20
А как быть с проблемой, когда быстро мышкой рисуешь якобы окружности, и вместо них получаются заметные многогранники? (при достаточно быстой обводке), а мне нужны именно ровные кривые без углов, целый день голову ломаю, уже думаю как кривые безье приделать
03 Апр 2013 в 14:24
Простите, никак не могу сделать сохранение изображения, пробовал и событие присвоить(он вообще ничего не открывает при нажатии на кнопку), и использовать библиотечки, может потому что я в хроме работаю? или руки кривые тоже не исключение :)