Журнал LinuxFormat - перейти на главную

LXF133:canvas

Материал из Linuxformat
Перейти к: навигация, поиск
Canvas Продвинутые возможности HTML для ваших web-приложений

Содержание

Canvas: Добавим Drag’n’drop

Добавив к элементу <canvas> способность реагировать на движение мышью, Иван Травкин сделает динамическую web-графику по-настоящему динамичной.

Для изучения многих аспектов программирования сегодня достаточно текстового редактора и web-браузера: ряд вспомогательных инструментов перечислен во врезке внизу. Так, несколько лет назад в активе web-разработчиков появился новый элемент HTML – Canvas, позволяющий рисовать изображения прямо на web-странице, используя сценарии JavaScript. Ранее динамическая графика либо загружалась с сервера в готовом виде, либо требовала модулей расширения Flash или Java.

В LXF94 (июль 2007) была опубликована отличная вводная статья Дэна Фроста «Canvas: Холст для web-картин »; в ней подробно описан элемент Canvas и показаны примеры рисования фигур разной степени сложности: от контуров прямоугольников до фигур с градиентной заливкой. Мы пойдем несколько дальше: сделаем так, чтобы любую фигуру на поверхности Canvas можно было перетаскивать мышью.

Изначально Canvas предлагает разработчику лишь набор команд для рисования примитивов (линии, квадраты, окружности) и для простых преобразований плоскости, таких как поворот. Нам придется самим научить его определять нажатие кнопки мыши и, при попадании в одну из фигур, обрабатывать ее перемещение до тех пор, пока пользователь не отпустит кнопку. Мы также введем несколько упрощений, о которых поговорим по ходу дела.


Объектная модель

Чтобы облегчить себе решение поставленной задачи, создадим простенькую объектную модель, похожую на предложенную в LXF94. Ведь в случае сложных изображений оперировать абстрактными понятиями вроде «фигура» гораздо удобнее, чем 10–15 строками кода: «измени цвет пера на красный», «проведи линию в точку (x,y)», «закрась прямоугольную область» и так далее.

Для начала создадим объектную «обертку» (иногда называемую «декоратором») для самого элемента Canvas. Объект Canvas (не путайте его с элементом!) станет основой нашей модели. Он будет «помнить» набор изображенных фигур, отвечать за их перерисовку и обрабатывать перемещение фигур курсором мыши.

 var Canvas = new Object();
 Canvas.shapes = [];

Перво-наперво нужно связать наш объект с неким элементом Canvas, на поверхности которого и будут изображаться фигуры.

 Canvas.setCanvas = function ( aCanvas ) {
 	 this.canvas = aCanvas;
 	 this.context = aCanvas.getContext( '2d' );
 	 /* …пропуск… */
 }

Метод setCanvas() отвечает за «запоминание» рабочего элемента Canvas, получение его контекста (нужного для рисования) и, что особо для нас важно, связывание событий мыши элемента с обработчиками, которые мы далее определим в объекте Canvas.

Для запоминания набора фигур объект Canvas использует коллекцию shapes. Для добавления и удаления фигур в коллекцию, а также последующей перерисовки, применяются методы addShape() и removeShape(), соответственно.

 Canvas.addShape = function ( aShape ) {
 	 this.shapes.push ( aShape );
 	 this.draw ();
 	 return this;
 }
 Canvas.removeShape = function ( aShape ) {
 	 /* …пропуск… */
 	 this.draw ();
 	 return this;
 }

Шарм объектно-ориентированного программирования в том, что, распределив обязанности между объектами, можно затем просто раздавать им указания, почти не напрягая драгоценный мозг.

Фигуры не рисуются непосредственно. Следуя общим принципам ООП, изложенным Аланом Найтом [Alan Knight] в прекрасной статье [ОО дизайна, или Всему, что я знаю о программировании, я научился у Дилберта»], объект Canvas, когда надо, просит каждую свою фигуру «нарисоваться» на холсте.

 Canvas.draw = function () {
 	 // очищаем поверхность элемента Canvas (маленький хак)
 	 this.canvas.width = this.canvas.width;
 	 // рисуем фигуры
 	 this.shapesDo ( function ( shape ) {
 		 shape.draw(); } );
 	 return this;
 }

Для упрощения работы с фигурами наш объект Canvas имеет метод-итератор shapesDo(), позволяющий легко выполнять действия со всей коллекцией фигур разом, что мы и делаем во время прорисовки изображения.

Начинаем упрощать


Теперь скажем пару слов о самих фигурах. Здесь мы ограничимся одним-единственным типом – прямоугольниками. Для их рисования потребуется всего один примитив элемента Canvas. Не станем даже добавлять возможность закраски каким-либо цветом: вместо этого будем рисовать полупрозрачные (чтобы видеть места перекрытия фигур при перетаскивании) прямоугольники светло-синегоцвета. Наши прямоугольники будут отличаться друг от друга только начальным положением и размером, и уметь только рисовать себя на поверхности рабочего элемента Canvas (метод draw()) да отвечать на вопрос, лежит ли точка с координатами (x,y) внутри данного прямоугольника (метод includesPoint()) – чтобы определять при нажатии кнопки мыши, не попали ли мы курсором в данную фигуру.

И еще одно упрощение. Определяя координаты курсора мыши, перемещаемого над элементом Canvas, мы столкнемся вот с чем: координаты курсора отсчитываются от левого верхнего угла документа и смещены относительно координатной системы нашего холста. Решение – добавлять смещение, равное координатам самого элемента Canvas. Но мы поступим проще, совместив координаты документа с координатами элемента Canvas: с помощью таблицы стилей уберем внутренние и внешние поля элемента Body, поместив тем самым холст в верхний левый угол документа.

Хватай и тащи

Итак, все, что требовалось для решения поставленной задачи, готово; теперь начинается самое интересное. Выше мы указали, что обработкой событий мыши, связанных с рабочим элементом Canvas, занимается объект-«обертка». Нам наиболее важны следующие три события: «нажатие» onmousedown, «отпускание» onmouseup и «перемещение» onmousemove. В свете этих трех событий, объекту Canvas необходимо помнить две вещи: флажок isDragging, равный true в тот момент, когда фигура «подцеплена», и draggingShape – ссылку на «подцепленную» фигуру.

Далее все просто. При нажатии кнопки мыши определим, не находится ли в этот момент курсор мыши над одной из фигур (спросим каждую фигуру: попали координаты курсора в ее внутреннюю область?), и если да, поставим флажок isDragging=true и запомним текущие координаты мыши (mouseX, mouseY) и фигуру, в которую мы «ткнули» (draggingShape). При отпускании кнопки мыши достаточно просто поставить флаг isDragging=false.

За перемещение «подцепленной» фигуры отвечает событие onmousemove. При перемещении мыши достаточно проверить, стоит ли флажок isDraging=true. Если да – вычислим приращение координат курсора (используя последние запомненные координаты), придадим это приращение фигуре draggingShape, снова запомним координаты и перерисуем изображение.

 Canvas.onmousemove = function ( event ) {
 	 if ( this.isDragging ) {
 		 this.dragginShape.x = this.dragginShape.x + (event.clientX - this.mouseX);
 		 this.dragginShape.y = this.dragginShape.y + (event.clientY - this.mouseY);
 		 this.mouseX = event.clientX;
 		 this.mouseY = event.clientY;
 		 this.draw ();
 	 }
 	 return this;
 }

Вот и все! Осталось лишь посмотреть, как это работает. Код HTML-документа будет выглядеть следующим образом:

 <html>
 <head>
 	 <title>Canvas и Drag'n'drop</title>
 	 <script type=text/javascript” src=”canvas.js”></script>
 	 <script type=text/javascript” src=”rect.js”></script>
 </head>
 <body style=”padding: 0; margin: 0;”>
 	 <canvas id=”canvas-test” width=”300” height=”300”
 		 style=background: #afa;”></canvas>
 	 <script type=text/javascript”>
 		 Canvas
 				 .setCanvas ( document.getElementById('canvas-test') )
 				 .addShape( new Rect (10, 10, 30, 30) )
 				 .addShape( new Rect (50, 10, 50, 50) );
 	 </script>
 </body>
 </html>

Что дальше?

Мы решили небольшую, но занятную задачу. Что дальше? Можно добавить цвет заливки прямоугольников. Или создать новые объекты для прочих фигур – скажем, для круга; для этого понадобится описать метод draw(), который будет рисовать круг, а также метод includesPoint(), определяющий принадлежность точки (x,y) внутренней области круга (например, по неравенству круга <math>(x-x_0)^2+(y-y_0)^2</math> ≤ <math>r^2)</math>. Можно анимировать фигуры. Круг задач неограничен – берите свою и наслаждайтесь поиском ее решений.

Проверим попадание

По мере добавления новых типов фигур может возникнуть проблема с вычислением попадания произвольной точки во внутреннюю область (метод includesPoint()). Для произвольных полигонов и фигур с кривыми можно использовать следующий метод: рисовать однотонную фигуру в невидимом буфере, и если цвет интересующей точки совпадет с цветом фигуры, информировать о попадании.

Инструментарий разработчика

  • Firebug http://getfirebug.com/ Инструментарий в помощь web-разработчику, предпочитающему Firefox. Упрощает отладку скриптов, таблиц стилей и даже AJAX-запросов.
  • MozillaLabs https://mozillalabs.com Сайт, посвященный экспериментальным проектам, которые разрабатываются сообществом Mozilla. Передний край открытых web-технологий.
  • Bespin http://mozillalabs.com/bespin Редактор кода, реализованный с использованием HTML5 и JavaScript. Работать с Bespin можно прямо в облаке Mozilla (https://bespin.mozillalabs.com/). Bespin прекрасно работает в Firefox, Chrome и Safari последних версий.
Персональные инструменты
купить
подписаться
Яндекс.Метрика