Все, що варто знати про геометрію в DOM
Сьогодні переважна більшість з нас, фронтенд-інженерів, створюють UI за допомогою React та подібних бібліотек чи фреймворків. Вони надають нам спектр інструментів і підходів для вирішення більшості повсякденних задач, як от побудова та оновлення DOM-дерева, підписки на події, стилізація. І в принципі все працює доволі непогано. Але часом задача вимагає зробити крок вліво або вправо від цього звичного процесу, і заімплементити дещо, що не підтримується нативно нашим фреймворком. І тоді з’являється дискомфорт, в когось більше, в когось менше. Сумбурна спроба загуглити і знайти потрібний метод.
Яскравий приклад такого кроку в сторону — прорахунок геометрії. Це коли нам треба спозиціонувати tooltip чи dropdown, провести якусь лінію між елементами, накинути певну анімацію на скрол і так далі.
Не берусь стверджувати, що це проблема для всіх. Але мій особистий досвід наступний: я закінчив фронтенд-курс, де нас навчили якісно верстати, стилізувати, вирішувати задачі на чистому JS, і звісно, створювати вже щось реальне і цілісне на React. А ось DOM... Тут матеріал був поданий настільки зжато і швидко, що я взагалі не встиг вникнути, що це і як працює. Такий підхід просто пояснити логічно, адже ціль курсів — засунути тебе в ІТ в найкоротші терміни, і без «пазлинки DOM» твій пазл фронденд-знань не розвалиться, принаймні одразу.
Так чи інакше, фундаментальних знань по тому, як JavaScript інтегрується в браузер, я не отримав, і вищезгаданий дискомфорт я відчував пару років, поки не розібрав для себе Document Object Model на гвинтики. Допускаю, що подібний сценарій міг статись і в інших, тож тема має бути корисною.
Переходимо від лірики до конкретики. В цій статті пропоную лаконічно структурувати знання про DOM-геометрію.
Глобальні розміри
window.innerWidth— ширина viewport. e.g.1040window.innerHeight— висота viewport
viewport це вікно браузера, де сторінка зарендерена. Тобто ці властивості — це певний еквівалент CSS vw & vh
Обидві включають в себе скролбар.
screen.width— ширина всього дисплеюscreen.height— висота дисплею
Ці значення можна використати для логіки responsive design. Вони залишаються сталими при появі тулбара або клавіатури в мобільному браузері.
Важливо зазначити, що розміри завжди відображаються в CSS-пікселях. Тобто навіть якщо ваш MacBook має заявлене розширення 3024×1964, screen.width буде 1512, тому що MacBook DPR = 2.
Розміри елемента
offsetWidth— загальна ширина. Включно з бордерами та скролбаромoffsetHeight— загальна висота
Результат ніколи не буде float значенням. А якщо елемент відсутній в DOM або має display: none, результат — 0.
clientWidth— ширина, не враховуючи бордера та скролбаруclientHeight— аналогічна висота
Для інлайнових елементів результат 0.
Для елемента, що скролиться, результат може різнитись між браузерами, тому що ширина скролбару різна поміж браузерами.
scrollWidth— ширина всього контенту. Тобто для елемента, що скролиться, це видима частина (clientWidth) + невидима частинаscrollHeight— аналогічна висота
Іншими словами, властивості вираховують clientWidth & clientHeight необхідні для того, аби відобразити весь контент без скролбарів. Все працює аналогічно для елемента з overflow: hidden.
Якщо ж елемент не містить контенту, що вилазить за його межі, то scrollWidth & scrollHeight будуть рівними clientWidth & clientHeight.
e.g. document.documentElement.scrollHeight — отримати повну висоту сторінки
В якості шпаргалки намалював ось таку ілюстрацію.

Похідні виміри
offsetLeft— відстань від лівого бордера елемента до лівого бордераoffsetParentoffsetTop— від верху до верху
Ці властивості насамперед корисні для position: absolute елементів.
element.offsetParent — найближчий спозиціонований елемент. i.e. Той, в якого position не є static.
clientLeft— ширина лівого бордераclientTop— висота верхнього бордера
Для інлайнових елементів результат — 0, не зважаючи на їхній фактичний бордер.
scrollLeft— скільки контенту вже проскролено горизонтальноscrollTop— вертикально
Результат може бути float.
scrollTop = 50 — на відміну від інших властивостей, ці дві можуть бути модифіковані. Щоб проскролити сторінку до самого верху або самого низу, можна засетити від’ємне число або велике позитивне число (e.g. Infinity).
Корисний трюк: el.scrollTop / (el.scrollHeight - el.clientHeight) * 100 — визначити скільки відсотків (0 — 100%) вже проскролено. По суті це співвідношення проскроленої частини до усієї невидимої частини.
Абослютно всі вищезгадані властовості містять тип number.
Позиція елемента
element.getBoundingClientRect() — отримати координати елемента відносно viewport
Цей метод важливий і використовується, щоб вирахувати відстань між елементами. Повертається спеціальний об’єкт, що називається DOMRect.
document.elementFromPoint(left, top) — знайти елемент, що лежить на конкретних координатах, відносно viewport
Це є реверс до getBoundingClientRect. Він повертає найглибший елемент. Якщо відбувається конфлікт, ліва та верхня сторони мають пріоритет.
DOMRect об’єкт
x— координата X початкової точки. Початкова точка це верхній лівий кутy— координата Y початкової точки
Обидві властивості повертають float за потреби.
e.g. element.getBoundingClientRect().y + window.scrollY — вирахувати координату Y відносно сторінки.
width— загальна ширина елемента (включно з бордером)height— висота елемента
Значення цих двох властивостей можуть бути від’ємними.
top— відстань від верху viewport до верхньої грані елементаbottom— відстань від верху viewport до нижньої грані елементаleft— відстань від лівої сторони viewport до лівої грані елементаright— відстань від лівої сторони viewport до правої грані елемента
Ці 4 властивості додані лише для додаткової зручності, оскільки їхні значення можуть бути легко вирахувані. e.g. rect.right === rect.x + rect.width.
top & left це JS еквівалент CSS властивостей top і left в парі з position: fixed.
Приклад використання getBoundingClientRect():
function isElementInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.right <= window.innerWidth &&
rect.bottom <= window.innerHeight &&
rect.left >= 0
);
}
Скролінг
window.scrollTo(left, top)— проскролити сторінку до певних координатelement.scrollTo(left, top)— еквівалент для елемента. Це є функціональна альтернатива до того, щоб засетитиscrollLeftіscrollTopелемента
window.scroll()— аліасelement.scroll()— аліас
window.scrollBy(left, top)— проскролити сторінку відносно поточної позиціїelement.scrollBy(left, top)— еквівалент для елемента
scrollTo(options) & scrollBy(options) — обидва методи можуть приймати об’єкт замість двох аргументів left & top. Цей об’єкт містить:
lefttopbehavior — визначає чи скрол миттєвий (instant) чи плавний (smooth). За замовчуванням він миттєвий. Фактично потреба встановити плавний скрол, це єдина причина використати об’єкт замість звичайних top & left
element.scrollIntoView(alignToTop/options?) — проскролити елемент, щоб він відобразився у viewport
alignToTop — boolean. якщо true верх елемента буде вирівняно з верхом viewport; якщо false низ елемента буде вирівняно з низом viewport. scrollIntoView також може приймати обʼєкт options замість boolean. Він містить:
behavior — така ж сама логіка як в тому, що передається в scrollTo.block — більш тонке (напротивагу alignToTop) налаштування позиції того де саме зупиниться скрол. Можливі варіанти: start, center, end, nearest.inline — те саме що block, але по горизонтальній осі. Можливі варіанти такі ж: start, center, end, nearest.
scrollIntoView це JS-альтернатива до посилання на хеш. Також scrollIntoView здатен проскролити не лише вікно браузера, а й будь-який контейнер, що скролиться.
Важливо знати: щоб проскролити сторінку будь-яким із методів, DOM повинен бути повністю збудований.
window.scrollX— скільки контенту сторінки вже проскролено горизонтальноwindow.scrollY— те саме вертикально
window.pageXOffset— аліас доscrollXwindow.pageYOffset— аліас доscrollY
Це глобальні відповідники до element.scrollLeft & element.scrollTop. Проте вони read-only.
e.g. document.documentElement.scrollTop — те саме що і window.scrollY, але scrollTop можна модифікувати.
Intersection Observer
Є ще така штука як Intersection Observer API, але це вже заходить на територію DOM-подій, і, крім того, є досить об’ємною темою для окремої статті.
Але якщо коротко, то дане API — це сучасний спосіб виявити, коли елемент входить в або покидає viewport чи будь-яку іншу ділянку. Це альтернатива до scroll події, але з увагою до продуктивності сторінки. Intersection Observer буде корисним для імплементації lazy-loading, infinite scroll, анімацій на scroll.
Висновки
Ось це і є основне, що стосується геометрії. Так, є певні заплутані моменти з назвами властивостей елемента і їх глобальних відповідників, також є декілька аліасів. Але якщо розкласти всі карти на стіл, здається, все не так і страшно. Сподіваюсь, я зміг допомогти структурувати частину знань цією статтею.
Якщо вам було цікаво, запрошую на свій вебсайт, де я структурую в подібному стилі усі фронтенд-знання.
Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.

1 коментар
Додати коментар Підписатись на коментаріВідписатись від коментарів