От автора: слово «адаптивный» — это то, что мы часто даже не упоминаем, потому что в наши дни это уже стандарт в веб-разработке. Существует огромное количество различных экранов, и их число постоянно растет. Мы хотим иметь возможность поддерживать все возможные размеры и при этом сохранять хороший пользовательский опыт. И медиа-запросы CSS — отличное решение данной проблемы. Но как насчет адаптивных компонентов?
Современная веб-разработка связана с компонентами, и нам также нужен способ сделать их адаптивными. Сегодня я хочу поговорить о ResizeObserver API, новом мощном инструменте для адаптивного Веб, который, в отличие от медиа-запросов, позволяет обнаруживать изменение размера определенного элемента, а не всего окна просмотра.
Краткая история
В прошлом у нас были только медиа-запросы — решение CSS основывалось на размере медиа, типе медиа или разрешении экрана (под медиа мы подразумеваем настольный компьютер, смартфон или планшет). Медиа-запросы являются гибкими и простыми в использовании. Долгое время это решение было доступно только для CSS, пока не стало доступно и в JS через window.matchMedia(mediaQueryString). Теперь мы можем видеть, соответствует ли документ медиа и даже больше — наблюдать документ, чтобы определить, когда его медиа-запросы меняются.
Запросов элементов, что насчет них?
Чего нам еще не хватает в наборе инструментов, так это способа обнаружения изменений размера в одном элементе DOM, а не в окне просмотра. Разработчики говорили об этом инструменте уже много лет. Это одна из самых востребованных функций. И есть даже предложение от 2015 года — запросы контейнера.
Учитывая сложную адаптивную компоновку, разработчикам часто требуется детальный контроль над элементами стиля относительно размера их родительского контейнера, а не размера области просмотра. Запросы контейнера позволяют автору управлять стилем на основе размера содержащего элемента, а не размера области просмотра пользователя. Пример использования в CSS:
Учитывая сложную адаптивную компоновку, разработчикам часто требуется детальный контроль над элементами стиля относительно размера их родительского контейнера, а не размера области просмотра. Контейнерные запросы позволяют автору управлять стилем на основе размера содержащего элемента, а не размера области просмотра пользователя. Пример использования в CSS: .element:media(min-width: 30em) screen {***}
Звучит замечательно, но, к сожалению, у браузеров была причина не спешить с реализацией — циклическая зависимость (когда одно событие изменения размера вызывает другое, приводящее к бесконечному циклу), поэтому эта спецификация никогда не переходила на стадию черновика W3C. Так какие варианты? Мы могли бы использовать window.resize(callback), но это затратное решение — обратный вызов вызывается каждый раз, когда происходит событие, и нам все еще нужно много вычислений, чтобы определить, действительно ли наш компонент изменил размер…
Наблюдение за изменениями размера элемента с помощью ResizeObserver API
К счастью, команда Chrome приходит нам на помощь — встречайте ResizeObserver API: ResizeObserver API — это интерфейс для наблюдения за изменениями размера элемента. Это аналог для элемента события window.resize.
ResizeObserver API — это текущий черновик. Он уже работает в Chrome 64, Firefox и Safari для настольных компьютеров. Мобильная поддержка меньше — его поддерживают только Chrome на Android и Samsung Internet. Есть еще плохие новости: он не на 100% заменяется полифиллом. Доступные полифилы обычно имеют ограничения (например, медленный ответ, отсутствие поддержки для отложенного перехода и т. д.), поэтому имейте это в виду. Однако в любом случае, это не должно помешать нам рассмотреть ResizeObserver API в действии, так что давайте сделаем это!
Пример: изменение текста элемента на основе его размера
Давайте рассмотрим следующую ситуацию — текст внутри элемента должен меняться в зависимости от размера элемента. ResizeObserver API предоставляет нам два интерфейса:
ResizeObserver и ResizeObserverEntry. Интерфейс ResizeObserver используется для наблюдения за изменениями размера элемента и ResizeObserverEntry описывает фактически измененный размер элемента. Код очень прост (кликабельная версия здесь):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<h1> Header </h1> <h2> boring text </h2>const ro = new ResizeObserver( entries => { for (let entry of entries) { const width = entry.contentBoxSize ? entry.contentBoxSize.inlineSize : entry.contentRect.width; if (entry.target.tagName === 'H1') { entry.target.textContent = width < 1000 ? 'width < 1000' : 'width >= 1000'; } if (entry.target.tagName === 'H2' && width < 500) { entry.target.textContent = 'I won"t change anymore'; ro.unobserve(entry.target); // stop observing this element when it's size will reach 500px } } }); // we can add more than one element to observe ro.observe(document.querySelector("h1")); ro.observe(document.querySelector("h2")); |
Мы создали объект ResizeObserver и передали обратный вызов его конструктору:
1 2 3 4 5 |
const resizeObserver = new ResizeObserver((entries, observer) => { for (let entry of entries) { // check entry dimensions and perform needed logic } }) |
Эта функция вызывается всякий раз, когда один из целевых элементов ResizeObserverEntries изменит свой размер. Вторым параметром функции обратного вызова является сам Observer. Вы можете использовать его, например, для прекращения наблюдения за элементом, когда достигнуто определенное условие.
Обратный вызов получает массив ResizeObserverEntry. Каждая запись содержит новые измерения для наблюдаемого элемента и самого элемента (target).
1 2 3 4 5 6 7 8 9 |
for (let entry of entries) { const width = entry.contentBoxSize ? entry.contentBoxSize.inlineSize : entry.contentRect.width; if (entry.target.tagName === 'H1') { entry.target.textContent = width < 1000 ? 'width < 1000' : 'width >= 1000'; } ... } |
Есть три свойства, описывающие новые измерения элемента — borderBoxSize, contentBoxSizeи contentRect. Они представляют собой блочную модель элемента, о которой мы чуть ниже расскажем. Пока пару слов о поддержке. В основном я вижу использование свойства contentRect, так как оно лучше всего поддерживается браузерами, но помните, что это свойство может быть устаревшим:
contentRect относится к фазе инкубации ResizeObserver и включается только для текущих целей веб-совместимости. На будущих уровнях оно может быть признано устаревшим.
Поэтому я бы посоветовал использовать borderBoxSize или contentBoxSize с contentRect в качестве резервного варианта. Интерфейс ResizeObserverSize предоставляет два свойства: inlineSize и blockSize, которые вы можете рассматривать как width и height (при условии, что мы работаем в горизонтальном writing-mode).
Отслеживание элемента
Последнее, что нужно сделать — это начать отслеживать элемент. Для этого мы вызываем ResizeObserver.observe(), который добавляет элемент в качестве новой цели в список наблюдаемых элементов. Мы можем передать один элемент или несколько:
1 2 3 4 |
// resizeObserver.observe(target, options); ro.observe(document.querySelector("h1")); ro.observe(document.querySelector("h2")); |
Второй параметр options является необязательным. В настоящее время доступна только одна опция box, определяющая блочную модель, в которой мы хотим наблюдать изменения. Возможные значения: content-box (по умолчанию), border-box и device-pixel-content-box (только для Chrome). Вы можете отслеживать только один блок одним наблюдателем, поэтому вам нужно будет использовать несколько ResizeObservers, если вы хотите наблюдать несколько блоков для одного и того же элемента.
Для того, чтобы перестать отслеживать определенный элемент, мы можем использовать ResizeObserver.unobserve(target). Чтобы перестать наблюдать за всеми целями, используйте ResizeObserver.disconnect().
Разница между блочными моделями
Окно контента — это блок с контентом, без отступов, границ и полей. Окно границы, напротив, включает в себя и ширину отступа и границы, но не полей:
Блок содержимого пикселей устройства — это блок содержимого элемента в пикселях устройства. Я еще не встречал использования этой опции, но она может быть полезна для элемента canvas. На Github есть интересное обсуждение, если вы хотите лучше понять, где и для чего его можно использовать.
Когда наблюдатель уведомляет об изменениях?
Обратный вызов срабатывает всякий раз, когда целевой элемент меняет свой размер. Спецификация предоставляет информацию об этом:
Наблюдение сработает, когда отслеживаемый элемент будет вставлен / удален из DOM;
Наблюдение сработает, когда для свойства display наблюдаемого элемента будет установлено значение none;
Наблюдения не запускаются для незаменяемых встроенных элементов;
Наблюдения не будут вызваны преобразованиями CSS;
Наблюдение сработает при начале наблюдения, если элемент визуализируется, и размер элемента не равен 0,0.
Исходя из первого пункта, теперь мы можем обнаружить изменение размера родительского контейнера, когда изменились его дочерние элементы. Хорошим примером этого является прокрутка до нижней части окна чата при добавлении нового сообщения. С ResizeObsever это очень просто! Смотрите пример здесь.
Теперь помните предложение Element Queries, о котором я упоминал ранее? И проблему циклической зависимости? ResizeObserver API имеет встроенное решение для предотвращения бесконечных циклов изменения размера.
Хорошо, я надеюсь, что вы рады работать с ResizeObserver API! Спасибо, что оставались со мной на протяжении этой статьи. Удачи и счастливого кодирования!
Автор: Khrystyna Skvarok
Источник: //medium.com
Редакция: Команда webformyself.