Все, що варто знати про геометрію в DOM

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Сьогодні переважна більшість з нас, фронтенд-інженерів, створюють UI за допомогою React та подібних бібліотек чи фреймворків. Вони надають нам спектр інструментів і підходів для вирішення більшості повсякденних задач, як от побудова та оновлення DOM-дерева, підписки на події, стилізація. І в принципі все працює доволі непогано. Але часом задача вимагає зробити крок вліво або вправо від цього звичного процесу, і заімплементити дещо, що не підтримується нативно нашим фреймворком. І тоді з’являється дискомфорт, в когось більше, в когось менше. Сумбурна спроба загуглити і знайти потрібний метод.

Яскравий приклад такого кроку в сторону — прорахунок геометрії. Це коли нам треба спозиціонувати tooltip чи dropdown, провести якусь лінію між елементами, накинути певну анімацію на скрол і так далі.

Не берусь стверджувати, що це проблема для всіх. Але мій особистий досвід наступний: я закінчив фронтенд-курс, де нас навчили якісно верстати, стилізувати, вирішувати задачі на чистому JS, і звісно, створювати вже щось реальне і цілісне на React. А ось DOM... Тут матеріал був поданий настільки зжато і швидко, що я взагалі не встиг вникнути, що це і як працює. Такий підхід просто пояснити логічно, адже ціль курсів — засунути тебе в ІТ в найкоротші терміни, і без «пазлинки DOM» твій пазл фронденд-знань не розвалиться, принаймні одразу.

Так чи інакше, фундаментальних знань по тому, як JavaScript інтегрується в браузер, я не отримав, і вищезгаданий дискомфорт я відчував пару років, поки не розібрав для себе Document Object Model на гвинтики. Допускаю, що подібний сценарій міг статись і в інших, тож тема має бути корисною.

Переходимо від лірики до конкретики. В цій статті пропоную лаконічно структурувати знання про DOM-геометрію.

Глобальні розміри

window.innerWidth — ширина viewport. e.g. 1040
window.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 — отримати повну висоту сторінки

В якості шпаргалки намалював ось таку ілюстрацію.

Порівняння DOM властивостей clientWidth, offsetWidth, scrollWidth

Похідні виміри

offsetLeft — відстань від лівого бордера елемента до лівого бордера offsetParent
offsetTop — від верху до верху

Ці властивості насамперед корисні для 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. Цей об’єкт містить:

left
top
behavior — визначає чи скрол миттєвий (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 — аліас до scrollX
window.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.

Висновки

Ось це і є основне, що стосується геометрії. Так, є певні заплутані моменти з назвами властивостей елемента і їх глобальних відповідників, також є декілька аліасів. Але якщо розкласти всі карти на стіл, здається, все не так і страшно. Сподіваюсь, я зміг допомогти структурувати частину знань цією статтею.

Якщо вам було цікаво, запрошую на свій вебсайт, де я структурую в подібному стилі усі фронтенд-знання.

Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.

👍ПодобаєтьсяСподобалось7
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Для тих, хто має бажання заглибитись в тему: Document Object Model (DOM) Geometry: A Beginner’s Introduction And Guide

Підписатись на коментарі