Проблема
Те, кто занимается разработкой графики с использованием JavaScript + Canvas давно заметили проблему обработки мышиных событий на каких-либо элементах графики.
Решений проблемы несколько:
- Не обрабатывать их совсем, то есть ваша графика неинтерактивна и вам это ни к чему
- Вычислять прямоугольник для каждой фигуры, хранить его в памяти, и вызывать события при попадании курсора в эти прямоугольники
- Подходить к каждому элементу графики индивидуально, применяя различные математические формулы для прямоугольников, окружностей, линий, и т.п.
Все эти способы имеют право на жизнь в определенных обстоятельствах, но когда события обнаруживать нужно (отметаем вариант 1), когда фигуры зачастую не являются прямоугольными, имеют повороты, и прочие трансформации (вариант 2 тоже не подходит), когда фигуры не являются геометрически правильными, как например, сглаженые сплайнами линии, многоугольники с вогнутими гранями (вариант 3 тоже забыли), а самое главное, когда этих фигур становится бесчисленное множество, и хранить координаты каждой, перебирая их на каждый MouseMove становится накладным, на помощь приходит другой способ.
Подход к решению
Нам нужно до пикселя точно знать попал курсор на нашу фигуру или нет. Для этого предпримем следующее:
При создании фигуры присвоим ей уникальный числовой идентификатор, например, 1. Теперь преобразуем этот идентификатор цвет в RGB HEX нотации — #000001
.
Создадим новый элемент canvas для того слоя, на котором находится фигура, но не будем добавлять его в DOM, это наш фоновый холст.
Во время отрисовки фигуры на основном холсте, отрисуем ее же на фоновом, но тем цветом, что получился из ее идентификатора (в том числе и линии границ).
Теперь становится понятно, что при соблюдении порядка отрисовки, фигуры на фоновом холсте будут перекрывать друг друга в том порядке, что и на реальном, и при движении курсора мыши по основному холсту, можно по координатам узнать цвет пикселя фонового холста, который является идентификатором фигуры, что позволяет однозначно сказать что курсор зашел на фигуру или ушел с нее.
Подводные камни
При движении мыши мы определяем цвет пикселя под ней, и если он непрозрачный (Alpha = 255)
, определяем идентификатор фигуры и работаем с событиями.
Но не все так радужно. Есть 3 основных проблемы:
1. Сглаживание
Дело в том, что все браузеры по-умолчанию используют сглаживание при отрисовке, а значит, не все пиксели фигуры будут полностью непрозрачны и того цвета, что нам нужно.
Решение:
Нужно отрисовывать границы фигуры на 2 пикселя толще (или просто 2 пикселя при отсутствии этой самой границы). Это создаст дополнительный шум в окрестных пикселях, но так как для нас они не являются значимыми, а также не видны пользователю, можно этим пренебречь.
2. Слияние цветов
При пересечении 2 фигур, то же самое сглаживание может создать полностью непрозраные пиксели, цвет которых, будет переходным от цвета одной фигуры к цвету другой. При этом есть небольшая вероятность того, что этот цвет укажет на совершенно другую фигуру, которая находится в совершенно другом месте. В реально работающем приложении такая вероятность достаточно низка, но мы ведь хотим все предусмотреть?
Решение:
Для проверки достоверности идентификатора нужно узнать цвет 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
Статья не претендует на право быть переводом, однако в ней использована большая часть материалов исходного ресурса.
Автор: Xnoyer