Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
На первый взгляд ничего не предвещало “грозы”, в метро все также у впереди проходящего через турникеты человеке осталось 0 поездок и я снова задумался о том что Матрица вот уже 10 дней работает без сбоев. Или все же это совпадение и у всех кто прикладывает проездной это была последняя поездка? Вчера я уже был близок к разгадке. Я специально пропустил пожилого мужчину вперед таким образом, чтобы между мной и турникетом было два человека, в предкушении я потирал руки: первый ноль поездок, второй… Упс у него пенсионный. Да, я живу в отменной симуляции и мне не нужна метавселенная от Меты и Маска.
Зайдя в вагон метро привычно достал смартфон и проверил почту. В одном из писем пользователь хабра в ответ на мой опус Создаем приложение Art-pixel на Angular и Nest.js. Часть 1, писал, что в демоприложении ошибка и при щелчке в одном месте,точка появляется на 12 клеточек правее и все это в Firefox последней версии.
Ошибка крылась в этом куске кода:
this.rendererRef = this.renderer.listen(this.canvasRef.nativeElement, 'click', (event) => {
let cX = event.layerX;
let cY = event.layerY;
const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
const offsetTop = this.canvasRef.nativeElement.offsetTop;
this.canvasX = cX - offsetLeft;
this.canvasY = cY - offsetTop;
this.matr.data.map(data => {
if (cX >= data.x && cX < data.x + this.numberOf && cY >= data.y && cY < data.y + this.numberOf) {
this.ctx.fillStyle = this.colorRect;
this.ctx.fillRect(data.x, data.y, this.numberOf, this.numberOf);
data.color = this.colorRect;
}
})
localStorage.setItem('matr', JSON.stringify(this.matr));
const data = this.canvas?.toDataURL();
})
}
Решил я разобраться. Первое, что пришло в голову это сравнить значения window.innerHeight и window.innerWidth
Мои значения получились следующие Хром 948х1485 против 602, 1920 значений Мозилы. Разрешение экрана 1920х1080, масштаб 100%. Выставил масшт 200%, цифры соответственно поменялись Хром 408х525, Мозила 62, 960
Я задумался, вспомнил, что физические свойства отличаются от свойств браузера и есть devicePixelRatio - свойство глобального объекта window, которое содержит отношение разрешения дисплея текущего устройства в физических пикселях к разрешению в логических (CSS) пикселях. Также это значение можно интерпретировать как отношение размера одного физического пикселя к размеру одного логического (CSS) пикселя. Что ж значение window.devicePixelRatio при 100% в обоих браузерах составило 1, при 200% значение равно 2. Я подумал о том, что возможно я недостаточно осведомлен и для вычисления координат я использую не те значения. Но все же согласно документации innerWidth возвращает внутреннюю ширину окна в пикселях, включая в себя ширину вертикальной полосы прокрутки, если она присутствует. innerHeight, в отличие от innerWidth предназначено соответственно для возвращения внутренней высоты окна в пикселях.
Думаю дай взгляну на значения window.outerWidth и window.outerHeight которые предназначены для получения соответственно ширины и высоты всего окна браузера (включая границы самого окна, панель закладок и т.д.). В дополнение также решил посмотреть на window.screenX, window.screenY которые предназначены для получения положения окна браузера (т.е. его x и y координат) относительно экрана. Ну и глянул также на scrollX и scrollY которые используются, когда нужно получить количество пикселей, на которые документ пролистали в данный момент соответственно по горизонтали и вертикали. Если уж смотреть, то нужно и на screen.height и screen.width взглянуть которые возвращают высоту и ширину экрана в пикселях.
Результаты были следующие:
Хром
window.devicePixelRatio: 1; window.innerHeight: 948; window.innerWidth: 1423; window.screenX: 0; window.screenY: 27; screen.height: 1080; screen.width: 1920; window.outerHeight: 1053; window.innerHeight: 948; window.scrollX: 0; window.scrollY: 0
Мозила
window.devicePixelRatio: 1; window.innerHeight: 589; window.innerWidth: 1920; window.screenX: 0; window.screenY: 27; screen.height: 1080; screen.width: 1920; window.outerHeight: 1053; window.innerHeight: 589; window.scrollX: 0; window.scrollY: 0
Ок, я убедился, что у браузеров разные размеры моего окна.
Я задумался о том, что если свойство offsetLeft содержит левое смещение элемента относительно offsetParent, а именно расстояние от offsetParent до границы элемента, то возможно необходимо поискать информацию об этом методе. После некоторых поисков, нагуглил, что вместо offsetLeft применим getBoundingClientRect.
Напомню, что getBoundingClientRect() возвращает объект DOMRect, который содержит размеры элемента и его положение относительно видимой области просмотра где left и top возвращают координаты X и Y верхнего левого угла элемента, а свойства right и bottom возвращают координаты правого нижнего угла элемента, width и height соответствуют ширине и высоте элемента (включают границы элемента border и внутренние отступы элемента padding, за исключением внешних отступов margin), свойства x и y соответствуют координате левой (x) и правой (y) границы прямоугольника относительно видимой области.
Что ж заряжаю document.body.getBoundingClientRect() в console.log и
хром - bottom: 1394.90625; height: 1394.90625; left: 0; right: 1408; top: 0; width: 1408; x: 0;y: 0;
мозила - bottom: 1399.75; height: 1399.75; left: 0; right: 1908; top: 0; width: 1908; x: 0;y: 0
На всякий пожарный, проверил значение this.canvasRef.nativeElement.getBoundingClientRect()
хром - bottom: 774.703125;height: 550;left: 659.65625;right: 1209.65625;top: 224.703125;width: 550; x: 659.65625; y: 224.703125
мозила - bottom: 779.75; height: 550; left: 909.63330078125; right: 1459.63330078125; top: 229.75; width: 550;x: 909.63330078125; y: 229.75
Как видим это ничего не прояснило. При том что в хроме canvasRef.nativeElement.offsetLeft -660; canvasRef.nativeElement.offsetTop -225; у мозилы canvasRef.nativeElement.offsetLeft - 910; canvasRef.nativeElement.offsetTop - 230;
Решил поскролить координаты, думаю вдруг вложенность контейнеров, canvas или другое что влияет, соорудил такое
<label for="">координаты курсора относительно текущего окна</label>
<input onmousemove="this.value = event.clientX+':'+event.clientY">
<label for="">координаты курсора относительно документа</label>
<input onmousemove="this.value = event.pageX+':'+event.pageY">
Эксперимент показал, что координаты, относительно окна: clientX/clientY и координаты, относительно документа: pageX/pageY у браузеров одинаковые.
Я не скажу, что я прозрел, но в какой то момент мне показалось что я понял очень важную вещь - я протупил.
И это оказалось действительно так, я упустил из виду, что все дело в layerX/layerY которые я использовал.
Ведь LayerX и LayerY извлекает x, y координаты указателя мыши относительно верхнего левого угла ближайшего расположенного элемента предка элемента, который запускает событие. Рядом с ними существуют также OffsetX, OffsetY которые извлекают x, y координаты указателя мыши по отношению к верхнему левому углу элемента offsetParent элемента, запускающего событие. Элемент Offset Parent возвращает ссылку на ближайший элемент-предшественник в иерархии DOM, из которой вычисляется позиция текущего элемента. Вот так вот, все оказалось относительно просто, ошибка в этих двух строчках:
let cX = event.layerX;
let cY = event.layerY;
Заменяю на:
let cX = event.offsetX;
let cY = event.offsetY;
И все работает.
P.S. На самом деле все было не так. Я все это придумал или почти все, но письмо все таки было. А квадратики в Хроме в art-pixel так криво и кликаются. Проснись Нео, проснись...