- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
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) внутренней области круга (например, по неравенству круга (x − x0)2 + (y − y0)2 ≤ r2). Можно анимировать фигуры. Круг задач неограничен – берите свою и наслаждайтесь поиском ее решений.
Проверим попадание
По мере добавления новых типов фигур может возникнуть проблема с вычислением попадания произвольной точки во внутреннюю область (метод 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 последних версий.