Как отслеживать события мыши на canvas во frontend
Что проверяют
Понимаете ли вы, что <canvas> для DOM это один элемент, а нарисованные фигуры как объекты не существуют и событий не получают. Значит, координаты и попадание в фигуру вы считаете сами. Следом обычно спрашивают про перетаскивание фигуры и поддержку тачскрина.
Как отвечать
Разложите задачу на части:
- события: слушатель вешается на сам canvas. Удобнее Pointer Events (
pointerdown,pointermove,pointerup): они покрывают мышь, палец и стилус одним кодом. Для тача добавьте в CSStouch-action: none, чтобы браузер не прокручивал страницу; - координаты:
event.clientX - rect.left, гдеrect = canvas.getBoundingClientRect(). Если CSS-размер холста не совпадает с атрибутамиwidthиheight, умножайте наcanvas.width / rect.width. Для чётких экранов холст делают больше вdevicePixelRatioраз и масштабируют контекст; - попадание в фигуру: для прямоугольников и кругов хватает геометрии, для произвольных контуров есть
ctx.isPointInPath(path, x, y)иisPointInStrokeс объектамиPath2D. При тысячах фигур используют пространственный индекс или скрытый холст, где каждая фигура залита своим цветом, и цвет пикселя под курсором говорит, в кого попали; - перетаскивание: на
pointerdownвызываютcanvas.setPointerCapture(e.pointerId), иpointermoveпродолжает приходить, даже когда курсор ушёл за пределы холста; - перерисовка: обработчик только обновляет состояние, а рисование идёт в
requestAnimationFrameраз за кадр.
Готовую модель событий на отдельных фигурах дают Konva, Fabric.js и PixiJS.
Где спотыкаются
Берут event.clientX без вычитания позиции холста или offsetX без учёта масштаба CSS. Пока холст в левом верхнем углу и не растянут, всё работает, а в реальной вёрстке клики уезжают.
Заканчивают рисование только по mouseup на самом canvas. Если отпустить кнопку за пределами холста, событие туда не придёт, и линия продолжит тянуться при следующем наведении. Спасает захват указателя или слушатель pointerup на window.
Перерисовывают всю сцену прямо в обработчике движения. На сложной сцене это лишние полные перерисовки и заметные лаги, особенно на слабых телефонах.