Наверно каждый WEB-разработчик когда-либо сталкивался с проблемой отслеживания события resize на странице. И если для window это сделать сможет любой новичок, то для остальных элементов эта задача принесет немало головной боли. Если вы относитесь к этой категории людей, то добро пожаловать под кат.
В интернете есть много статей где эта проблема решается с помощью setInterval или, в лучшем случае, рекурсивного setTimeout, который постоянно через определенные промежутки времени проверяет не изменились ли размеры указанного элемента. Но в большинстве случаев размеры элемента изменяются очень редко, поэтому такой способ является неприемлемым и к тому же может приводить к излишним тратам памяти.
На Хабре я нашел решение для отслеживания «onresize» на элементе путем добавления внутрь него фрейма. Этот способ рабочий и в большинстве случаев наверняка будет полезен. Но что делать если по какой-то причине не хочется добавлять лишний фрейм внутрь элемента? Я хочу предложить еще пару способов для отслеживания изменения размеров элемента.
Для начала я разобью случаи в которых изменяются размеры элемента на категории:
- Изменение содержимого элемента
- Изменение стилей элемента
Первый способ: DOMSubtreeModified
Чаще всего необходимость отслеживания размера элемента возникает для того, чтобы обновить состояние какого-либо плагина при изменении контента в определенном блоке. Например обновить плагин карусели после того как изменилась высота контента внутри него.
Для этого нет необходимости наблюдать за изменением размеров элемента средствами CSS, а достаточно лишь отследить изменение структуры DOM внутри элемента.
Это можно легко осуществить используя событие DOMSubtreeModified, которое срабатывает всякий раз когда структура или текст внутри элемента изменяется:
el.addEventListener("DOMSubtreeModified", function (e) {
console.log("Размеры элемента: " + el.clientWidth + " X " + el.clientHeight);
}, false);
> Поддержка браузерами
Второй способ: transitionend
Для того чтобы отследить изменение размеров через CSS придется добавить следующие стили:
#el, #el * {
/* Свойства изменение которых необходимо отслеживать */
transition-property: width, height, padding, margin;
/* Устанавливаем "незаметную для глаза" длительность перехода */
transition-duration: 1ms;
}
Список свойств, как и длительность перехода можно подобрать под себя. Вполне возможно, что какие-то из этих свойств у элемента уже заданы или нет надобности применять их ко всем вложенным элементам.
Получается, что мы с помощью CSS-селекторов можем гибко указать на каких элементах требуется отслеживать изменение размеров (или любых других анимируемых свойств).
Таким образом у целевого блока и у всех элементов внутри него будет установлена мгновенная трансформация и так, как событие всплывает, то теперь мы можем отследить событие transitionend, которое сработает, когда CSS transition закончит свое выполнение на любом дочернем элементе:
el.addEventListener("transitionend", function () {
console.log("Размеры элемента: " + el.clientWidth + " X " + el.clientHeight);
}, false);
> Поддержка браузерами
К сожалению нет свойства transitionstart и transition или transitioninterval, поэтому событие срабатывает только по окончании перехода, так что значение transition-duration следует выбирать небольшое. Но если установить transition-duration равное 0, то событие не будет срабатывать вовсе.
Заключение
Конечно эти способы не смогут помочь в любой ситуации, но думаю в большинстве случаев первого или второго способа (а может и обоих сразу) будет более чем достаточно.
Надеюсь эта статья окажется полезной. Буду рад обоснованной критике, так как это мой первый опыт на Хабре.
> Демо на JSFiddle
UPD: RubaXa подсказал про Resize Observers.
UPD: Обновил демо, сделав его более наглядным и дополнил описание второго способа.
Комментарии (8)
RubaXa
11.04.2017 12:39+1Ещё надо было упомянуть про ResizeObserver (скоро появится, а пока полифилы)
XAHTEP26
11.04.2017 13:31Про ResizeObserver не знал. Спасибо — добавлю.
Бегло просмотрел полифиллы. Один из них работает по принципу добавления фрейма, а все остальные через setTimeout или requestAnimationFrame (для которого, кстати, тоже подключается полифилл с setTimeout).
Значит будем ждать ResizeObserver, хотя врядли он появится скоро, так как находится в стадии Editors Draft.
И еще в ResizeObserver есть одно «но» — сейчас в спецификации есть такая строчка:observations will not be triggered by CSS transforms
RubaXa
11.04.2017 13:35Ну, посмотрим, что будет к релизу, а так, уже давно есть https://github.com/wnr/element-resize-detector
postrel
18.04.2017 18:13DOMSubtreeModified числится как deprecated
https://www.w3.org/TR/DOM-Level-3-Events/#event-type-DOMSubtreeModifiedXAHTEP26
18.04.2017 18:17+1Верно. Но поддерживается уже давно и для старых браузеров самое то.
А для новых можно использовать MutationObserver, о котором я решил написать отдельную небольшую статью.
alexwerser
А что мешает привязать отслеживание размеров элемента к тем действиям, которые приводят к изменению его размеров? С window понятно, там действия внешние, но элементы сами по себе не меняются в размере.
XAHTEP26
В статье речь как раз и идет о том, чтобы отслеживать действия которые приводят к изменению размеров элемента.
Тут приводится лишь 2 дополнительных способа. Если вы точно знаете когда размеры элемента могут измениться, то вы можете отслеживать это другим способом.
Например если вы делаете плагин слайдера, то можно проверять высоту при переключении слайдов.
Но если пользователь вашего плагина поместит на слайд какой-нибудь опрос, в котором при нажатии на кнопку будут раскрываться результаты (и соответственно изменяться высота контента), то ему надо будет еще раз вручную инициировать пересчет высоты слайдера.
MaxKur
Как правило, мешает то, что отслеживание этих действий может быть более трудозатратным. Я столкнулся с проблемой встраивания слайдера в командную панель — размеры свободного места в ней могли меняться по изменению размеров окна, по ховеру на один элемент, по загрузке элементов и сокрытию их через некоторое время. Конечно же можно установить слушатель на все эти события, учитывать асинхронность, — но лично я предпочту всего одно простое событие transitionend