Pull to refresh

Полноценные события мыши на графических элементах Canvas

Reading time4 min
Views20K

Проблема


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

Решений проблемы несколько:
  1. Не обрабатывать их совсем, то есть ваша графика неинтерактивна и вам это ни к чему
  2. Вычислять прямоугольник для каждой фигуры, хранить его в памяти, и вызывать события при попадании курсора в эти прямоугольники
  3. Подходить к каждому элементу графики индивидуально, применяя различные математические формулы для прямоугольников, окружностей, линий, и т.п.


Все эти способы имеют право на жизнь в определенных обстоятельствах, но когда события обнаруживать нужно (отметаем вариант 1), когда фигуры зачастую не являются прямоугольными, имеют повороты, и прочие трансформации (вариант 2 тоже не подходит), когда фигуры не являются геометрически правильными, как например, сглаженые сплайнами линии, многоугольники с вогнутими гранями (вариант 3 тоже забыли), а самое главное, когда этих фигур становится бесчисленное множество, и хранить координаты каждой, перебирая их на каждый MouseMove становится накладным, на помощь приходит другой способ.

Подход к решению


Нам нужно до пикселя точно знать попал курсор на нашу фигуру или нет. Для этого предпримем следующее:
При создании фигуры присвоим ей уникальный числовой идентификатор, например, 1. Теперь преобразуем этот идентификатор цвет в RGB HEX нотации — #000001.

Создадим новый элемент canvas для того слоя, на котором находится фигура, но не будем добавлять его в DOM, это наш фоновый холст.
Во время отрисовки фигуры на основном холсте, отрисуем ее же на фоновом, но тем цветом, что получился из ее идентификатора (в том числе и линии границ).
Теперь становится понятно, что при соблюдении порядка отрисовки, фигуры на фоновом холсте будут перекрывать друг друга в том порядке, что и на реальном, и при движении курсора мыши по основному холсту, можно по координатам узнать цвет пикселя фонового холста, который является идентификатором фигуры, что позволяет однозначно сказать что курсор зашел на фигуру или ушел с нее.

Подводные камни


При движении мыши мы определяем цвет пикселя под ней, и если он непрозрачный (Alpha = 255), определяем идентификатор фигуры и работаем с событиями.

Но не все так радужно. Есть 3 основных проблемы:

1. Сглаживание

Дело в том, что все браузеры по-умолчанию используют сглаживание при отрисовке, а значит, не все пиксели фигуры будут полностью непрозрачны и того цвета, что нам нужно.
image

Решение:
Нужно отрисовывать границы фигуры на 2 пикселя толще (или просто 2 пикселя при отсутствии этой самой границы). Это создаст дополнительный шум в окрестных пикселях, но так как для нас они не являются значимыми, а также не видны пользователю, можно этим пренебречь.

2. Слияние цветов

При пересечении 2 фигур, то же самое сглаживание может создать полностью непрозрачные пиксели, цвет которых, будет переходным от цвета одной фигуры к цвету другой. При этом есть небольшая вероятность того, что этот цвет укажет на совершенно другую фигуру, которая находится в совершенно другом месте. В реально работающем приложении такая вероятность достаточно низка, но мы ведь хотим все предусмотреть?
image

Решение:
Для проверки достоверности идентификатора нужно узнать цвет 4 пикселей — сверху, снизу, справа и слева. Если каждый из них того же цвета или не полностью прозрачен, идентификатор достоверен.

3. Изображения

В случае, когда мы работаем с изображениями, очевидно что необходимо создать некий обработчик изображения, который итеративно обрабатывает его пиксельные данные, полученые через getImageData, и заменяет каждый непрозрачный пиксель цветом идентификатора фигуры, а каждый полупрозрачный заменяет прозрачным.

В этом случае нет четко поставленой прблемы, есть лишь 3 нюанса на которые стоит обратить внимание.

  • Первый – для обработки изображения нам потребуется создать еще один элемент Canvas, совпадающий по размерам с изображением для вывода функции обработки и использования его в качестве источника изображения в фоновом холсте. Стоит либо удалять этот элемент каждый раз после использования, либо создать один общий, и менять его размеры под каждую новую картинку.
  • Второй – время обработки изображения пропорционально его размерам, так как приходится обходить каждый пиксель. Здесь однозначно ничего сказать нельзя, все зависит от функции обработки.
  • Третий – в случае изображений, полученых не из того домена, на котором находится страница, содержащая элемент Canvas, сработает защита браузера. Придется обрабатывать ошибку SECURITY_ERR, и работать с изображением как с прямоугольником.


Реализация


Итак, мы уже имеем фоновый холст с отрисоваными нужными цветами фигурами. Как получить цвет пикселя под курсором? Ниже приведен абстрактный код, демонстрирующий как зная координаты курсора получить пиксель под ним:

//x, y - координаты мыши, полученые ранее
var ctx = bgCanvas.getContext("2d");
var pixel = ctx.getImageData(x, y, 1, 1).data; //массив из 4 элементов, каналы R, G, B и A, соответственно
var isOpaque = pixel[3] === 255; //Четвертый пиксель содержит альфа канал


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

Источник


В качестве источника использован следующий ресурс:
http://tschaub.net/blog/2011/03/31/canvas-hit-detection.html

Статья не претендует на право быть переводом, однако в ней использована большая часть материалов исходного ресурса.
Tags:
Hubs:
+21
Comments12

Articles

Change theme settings