Клик в Canvas попадает мимо: как согласовать координаты
Если клик в Canvas попадает мимо объекта после масштабирования, проверьте всю цепочку координат: окно, отображаемый размер элемента, внутреннее разрешение и пространство сцены. Координату указателя нужно преобразовать в ту систему, где проверяются границы объекта. Универсальное деление на devicePixelRatio не решает задачу, потому что CSS-масштаб и преобразования сцены могут быть независимыми.
Сначала назовите систему каждой точки
В описании clientX MDN координата относится к области просмотра, а не к началу документа или холста. Для проверки попадания недостаточно сравнить её с положением объекта внутри сцены. Одинаково называющиеся x и y в разных частях программы могут обозначать совершенно разные величины.
getBoundingClientRect даёт прямоугольник относительно области просмотра, учитывающий прокрутку. Его размеры включают рамку и внутренние отступы. Поэтому формула из простого примера без рамки не должна автоматически применяться к элементу с другой геометрией.
Ограниченный учебный пример
Предположим, холст расположен без CSS-поворота, рамки и внутренних отступов. Его отображаемый размер —400×200 CSS-пикселей, внутренний —800×400. Левый верхний угол относительно окна находится в точке (100; 50). Контекст рисования имеет только масштаб 2×2; иных преобразований сцены нет. Это вымышленные данные для проверки арифметики, не измерения реального устройства.
Пользователь кликает в точке (300; 150) относительно окна. После вычитания положения холста получаем 200,100 в отображаемых CSS-координатах. Для внутреннего разрешения умножаем каждую ось на отношение соответствующих размеров: получаем 400,200. Затем обращаем заданный масштаб контекста и получаем 200,100 в координатах сцены.
| Пространство | Источник | Преобразование в примере | Полученная точка | Ошибка при смешении |
|---|---|---|---|---|
| Область просмотра | clientX и соответствующая вертикальная координата | Исходное событие | 300; 150 | Сравнение с объектом сцены без вычитания положения элемента |
| Отображаемый холст | Свежие границы элемента | Вычесть 100 по X и 50 по Y | 200; 100 | Повторно добавить прокрутку к уже согласованной системе окна |
| Внутреннее разрешение | Размер буфера 800×400 и CSS-размер 400×200 | Умножить обе оси на 2 | 400; 200 | Считать CSS-точку готовой координатой буфера |
| Сцена | Заданный масштаб контекста 2×2 | Применить обратный масштаб | 200; 100 | Проверять границы сцены координатой внутреннего буфера |
В этом примере результат сцены совпал с локальной CSS-точкой, потому что два выбранных преобразования компенсировали друг друга. Это следствие условий, а не универсальное равенство. Если код привык к такому совпадению, ошибка обнаружится после изменения размера или камеры.
Уменьшим CSS-размер, сохранив внутреннюю сцену
Оставим внутренний размер 800×400 и масштаб контекста 2×2, но отобразим холст в 200×100 CSS-пикселях. Клик в центре теперь будет иметь локальную CSS-точку 100,50. Отношение внутреннего и отображаемого размера стало 4, поэтому координата буфера снова 400,200, а сцены —200,100. Объект в центре остаётся тем же.
Простое деление локальной точки на 2 дало бы 50,25 и попало в другое место. Здесь нельзя заменить отношение размеров предполагаемым значением плотности экрана. В документации devicePixelRatio оно описывает отношение физических и CSS-пикселей устройства. Внутренний размер вашего Canvas задаётся отдельно принятой схемой рисования.
Где заканчивается простая формула
Для описанного случая без рамки и поворота путь к буферу можно записать словами: из координаты окна вычесть начало отображаемой области и умножить на отношение внутреннего размера к отображаемому по каждой оси. Но сначала нужно убедиться, что эти размеры относятся именно к одной области. Нулевая или скрытая область не должна становиться делителем без проверки.
Если есть рамка, отступы, CSS-поворот или преобразованные предки, требуется соответствующая модель геометрии. Окружающий прямоугольник повёрнутой фигуры не является готовой системой её локальных осей. Не распространяйте сокращённую формулу на такие условия без вывода и испытаний. Для интерактивного веб-приложения эти ограничения нужно зафиксировать в задаче.
Камера и масштаб сцены — ещё один этап
Объект может рисоваться после сдвига камеры, масштабирования и других преобразований. Проверка попадания должна использовать согласованный обратный путь к его системе. Метод getTransform позволяет получить текущую матрицу контекста. Само получение матрицы не преобразует точку автоматически: её нужно правильно применить по принятой архитектуре.
Различайте преобразование контекста и собственную модель сцены. Если игра хранит камеру отдельно и вручную рассчитывает позиции, одна матрица контекста может не описывать весь путь. При разработке браузерной игры полезно иметь единые согласованные преобразования для рисования и проверки попадания, вместо независимых формул в каждом обработчике.
Изменение окна должно обновлять согласованные величины
После изменения CSS-размера, прокрутки или размещения элемента проверьте актуальность используемых границ. Кешировать их можно только с понятным правилом обновления. Иначе расчёт верен для вчерашнего прямоугольника, а объект уже находится в другом месте. При изменении внутреннего разрешения отдельно проверьте восстановление нужного состояния рисования.
Плотность экрана и масштаб просмотра тоже включите в согласованный набор условий. Не выводите из одного измеренного значения устройство пользователя и не обещайте одинаковый результат без проверки. В разработке сайта расположение игрового или редакторского блока должно быть согласовано с его обработкой ввода, особенно при адаптивном размере.
Проверять центр недостаточно
- Зафиксируйте размеры отображаемой области, внутреннего буфера и системы сцены. Отдельно перечислите рамки, отступы и все преобразования.
- На учебном объекте выберите центр, точки у краёв и точку снаружи. Рассчитайте ожидаемую принадлежность до обработки события.
- Проследите каждую точку по этапам, не записывая только итог. Найдите первый шаг, где фактическая величина отличается от ожидаемой.
- Измените CSS-размер, прокрутите страницу и повторите проверку после перемещения блока. Используемые границы должны оставаться актуальными.
- Добавьте только те преобразования камеры и условия экрана, которые входят в поддержку продукта. Проверьте обратный путь отдельно для них.
- Сверьте выделенный объект с нарисованным на конечном экране. Не скрывайте ошибку произвольным расширением области попадания без согласованного смысла.
В результате у команды должен остаться понятный путь от события к объекту и ограниченный проверочный набор. По нему можно разбирать новое смещение без случайного умножения на плотность экрана. Корректная формула ценна именно тем, что её условия известны и проверяются при изменении макета.
Вопросы и ответы
Почему Canvas mouse coordinates не совпадают с объектом?
Координаты события и границы объекта могут относиться к разным пространствам. Нужно учесть положение элемента, отношение размеров и преобразование сцены, а не сравнивать значения напрямую.
Нужно ли делить координаты Canvas на devicePixelRatio?
Только если это действительно следует из вашей схемы размеров и преобразований. Универсального правила нет: CSS-масштаб, внутренний буфер и камера могут задаваться независимо.
Что возвращает getBoundingClientRect для этой проверки?
Прямоугольник элемента относительно области просмотра, включая рамку и внутренние отступы. Для перевода к содержимому нужно учитывать эти условия и актуальность границ после изменения макета.