На первый взгляд ничего не предвещало “грозы”, в метро все также у впереди проходящего через турникеты человека осталось 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 против 602x1920 значений Мозилы. Разрешение экрана 1920х1080, масштаб 100%. Выставил масштаб 200%, цифры соответственно поменялись Chrome 408х525, Firefox 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 взглянуть которые возвращают высоту и ширину экрана в пикселях.
Результаты были следующие:
Chrome
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
Firefox
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 и
Chrome - bottom: 1394.90625; height: 1394.90625; left: 0; right: 1408; top: 0; width: 1408; x: 0;y: 0;
Firefox - bottom: 1399.75; height: 1399.75; left: 0; right: 1908; top: 0; width: 1908; x: 0;y: 0
На всякий пожарный, проверил значение this.canvasRef.nativeElement.getBoundingClientRect()
Chrome - bottom: 774.703125;height: 550;left: 659.65625;right: 1209.65625;top: 224.703125;width: 550; x: 659.65625; y: 224.703125
Firefox - 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 так криво и кликаются. Проснись Нео, проснись...
Постскриптум. В личку я получил несколько сообщений о том, что после прочтения ни чего не было понятно и надо бы как то резюмировать текст.
Понимаю, резюмирую. Так вот, вывод который я сделал заключается в том, что разница значений window.innerHeight, window.innerWidth которая отображает размер окна сайта в браузерах Chrome Firefox существенная и это нужно учитывать, а вот screen.height, screen.width которая отображает размер экрана одинаковая. При вычислении координат мышки лучше брать значения из event.offsetX, event.offsetY, они в обоих браузерах одинаковые. Америку я не открыл, да и ошибку я нашел быстро, так как с похожим сталкивался еще в JQuery только тогда там был IE, но решил описать проблему в таком формате, думаю это поможет новичкам лучше понять такие простые на первый взгляд истины.
resintegra Автор
Думалось в таком формате читать интереснее - ошибся. Не дорос ещё статьи для Хабра писать). Пойду в другом месте тренироваться. Текст удалять не буду, мне статья нравится.
Svyat0y
Сам написал, сам сделал выводы и принял решение, красава)