Привет, Хабр! Меня зовут Анна, я JS-разработчик в компании SimbirSoft и занимаюсь разработкой веб-приложений на React. Эту статью я посвящаю тем, кто занимается разработкой, сталкивается с нестандартными задачами и переживает, что нашу профессию может вскоре заменить искусственный интеллект (ИИ). Я поделюсь решением задачи, связанной с динамическими размерами блока, — проблемой, с которой наверняка может столкнуться в своей работе любой frontend-разработчик.
Почему же я назвала статью именно «Долгий путь к ResizeObserver»?
Возможно, я и слышала раньше про этот API, но когда передо мной встала конкретная задача (описанная ниже), я о нем даже не вспомнила. Мне пришлось пробовать сначала одно решение, потом другое — и лишь в третью очередь я пришла к нужному инструменту. Таков мой путь — из трех шагов. Я человек, поэтому могу честно рассказать, как именно искала решения, в отличие от ИИ. Надеюсь, моя статья поможет вам быстро и эффективно справиться с похожей задачей, а заодно придаст уверенности в собственных силах. Я убеждена: ответы на вопросы находятся не только в нашей голове, но и в окружающем мире, а человек, в отличие от ИИ, способен чувствовать, искать и находить их самым неожиданным для себя образом.
Итак, приступим ?
Перед нами лейаут, который состоит из трех блоков:

Высота первого и второго блоков может меняться, пока пользователь взаимодействует со страницей, а высота третьего должна подстраиваться динамически. Если высота второго блока небольшая, тогда третий блок будет иметь фиксированную высоту (200 px). Все три блока состоят из разных частей, данные в которые поступают изолированно.
Основная задача состоит в том, чтобы отслеживать высоту первого и второго блоков и, в зависимости от их значений, задавать высоту третьему блоку. Задавать ее нужно обязательно (и здесь лучше не полагаться на вычисление CSS), поскольку в третьем блоке несколько табов с контентом разной высоты, и при переключении между ними высота не должна «прыгать». Высота третьего блока не может быть меньше 200 px.
Итак, у моей статьи две цели:
Рассказать, каким способом решила эту задачу — может быть полезно тем, кто столкнется с подобной ситуацией.
Рассказать, как именно я решала эту задачу — то есть описать процесс поиска решения с помощью естественного интеллекта (части статьи, посвященные этой цели, будут выделены курсивом).
До сих пор я решала задачи, связанные с установкой высоты элементов в зависимости от загружаемого контента, с помощью JS-функций. Поэтому первый вариант решения был таким:
// создаем функцию “получить высоту табов”
export const getHeightTabs = () => {
// с помощью нативного js получаем высоты блоков 1 и 2 (чтобы это работало, // надо задать id для этих блоков ‘block1’ и ‘block2’ соответственно)
const heightBlock1 = document.getElementById('block1')?.clientHeight || 0;
const heightBlock2 = document.getElementById('block2')?.clientHeight || 0;
// вычисляем высоту 3-го блока: если разница между 2-ым и 1-ым блоком
// больше 200px, то 3-ий блок равен разнице, иначе - 200px
const heightTabs = heightBlock2 - heightBlock1 >= 200
? heightBlock2 - heightBlock1 : 200;
return heightTabs;
};
Вызываем эту функцию при загрузке страницы, присваиваем высоте третьего блока полученное значение.
В моей ситуации такой подход не работал с самого начала, т.к. контент первого и второго блоков загружается постепенно и зависит от множества данных. Я не придумала, как вызвать эту функцию после окончательной загрузки всех данных – это первая проблема. Вторая связана с обновлением высоты: пользователь может изменять высоту первого и второго блоков, взаимодействуя с разными частями приложения (а их 10 или 12). Поэтому вызывать функцию после обновления каждой из этих частей выглядит излишне громоздким решением.
Эта задача находилась в бэклоге: хотя «прыгающая» высота табов раздражала и меня, и дизайнера, высокой приоритетности ей никто не присваивал — было много других проблем. Поэтому я отложила ее решение и на следующий день взялась за другую, более крупную и не менее важную задачу — изменение дизайна другой части нашего приложения (приложение большое, существует уже 7 лет, и мы его не переписываем с нуля, а поддерживаем и постепенно обновляем по частям).
Тут наступает момент моей истории, где я покажу, в чем человек превосходит ИИ, ведь я собиралась утешить тех, кто тревожится, что ИИ сможет заменить разработчиков ?
Открываю одну из папок нашего легаси и что я там вижу?
import {useResizeDetector} from 'react-resize-detector';
Как потом выяснилось, это единственное место в нашем приложении, где использовалась эта либа. И я наткнулась на него на следующий день после того, как предприняла свою первую попытку решить проблему с высотой табов! Для меня, как для человека, это стало чудесным знаком от окружающей реальности — указанием на то, в каком направлении нужно двигаться. То есть решение «пришло» извне, а не из моих знаний. И я абсолютно уверена: человек способен находить ответы в самых неожиданных местах. А ИИ — лишь в пределах своей обучающей базы. Никто нас не заменит ?
Чудесная часть моей статьи завершена — дальше будет скучнее, но, возможно, кому-то пригодится ?
Второй способ, который я попробовала, написала кастомный хук с использованием библиотеки react-resize-detector:
// создаем хук “определитель высоты табов”
export const useTabsHeightDetect = () => {
// создаем хранилища состояний для высот 1-го и 2-го блоков
const [hb1, setHB1] = useState(0);
const [hb2, setHB2] = useState(0);
// создаем колбэк-функции onResize для хука useResizeDetector, где получаем
// значение высоты нужного блока и сохраняем его в хранилище
const onResizeBlock1 = (payload) => {
const {height} = payload;
setHB1(height);
};
const onResizeBlock2 = (payload) => {
const {height} = payload;
setHB2(height);
};
// посредством хука useResizeDetector получаем ссылку на блок,
// к которому привязано отслеживание высоты и соответствующий колбэк
const {ref: block1Ref} = useResizeDetector({onResize: onResizeBlock1});
const {ref: block2Ref} = useResizeDetector({onResize: onResizeBlock2});
// возвращаем обе ссылки, которые раздадим нужным блокам, и высоты блоков
// для расчета высоты третьего
return {block1Ref , block2Ref , hb1, hb2}
}
Ссылки на первый и второй блоки раздаем в родительском компоненте, а в компоненте табов (третьем блоке) мы получаем их высоты и рассчитываем нужную.
Почему-то информация о высоте блоков не обновлялась сразу, а только при следующем рендере. Напишите в комментариях ниже почему, если знаете.
Я попыталась разобраться, но раньше чем успела это сделать, наткнулась на замечательную статью про ResizeObserver, которая решила мою задачу так, что либа react-resize-detector мне больше не понадобилась. Внимательный читатель наверняка понял, что я снова намекаю на основную мысль своей статьи: решения приходят к человеку разными путями, и сложность этих путей, на мой взгляд, все же выше, чем у любой искусственной нейросети ?
В начале статьи авторы оптимистично дают гарантии, что после прочтения мы будем иметь четкое представление о том, как создавать отзывчивые и адаптивные веб-интерфейсы с помощью ResizeObserver API. Лично со мной так не работает, я не буду иметь четкое представление о чем-нибудь после прочтения статьи. Если я просто прочитаю статью, даже если все пойму, информация испарится из головы через 15–20 минут ? Но простим авторам такой маркетинговый ход — это их работа, зато пишут отличные статьи ?
Далее нам объясняют, что ResizeObserver — это JavaScript API, которое, как следует из названия, «наблюдает» за изменением размеров элементов веб-страницы. В статье его сравнивают с тренером, который наблюдает за командой на поле и может заметить даже усталость игрока или его возможные травмы. На мой взгляд, аналогия не совсем удачная — я бы не стала сравнивать API с тренером. Observer — это все-таки обозреватель, хотя, возможно, имелось в виду, что никто не следит за игроками так внимательно, как тренер, а наше API такое же внимательное и заботливое. Может быть, но мне кажется, что это не совсем верно и довольно абстрактное сравнение. Тем не менее, заметив изменения элемента, ResizeObserver может вызвать колбэк, который разработчик может задать.
Далее в статье приводится сравнение ResizeObserver с медиа- и контейнер-запросами, которые разработчики обычно применяют для создания адаптивного дизайна, затем перечисляются ключевые преимущества использования ResizeObserver:
Мониторинг элементов: в отличие от resize events, которое отслеживает изменение размеров всего окна, ResizeObserver фокусируется на конкретных элементах страницы.
Улучшение производительности: позволяет избежать излишних дорогостоящих вычислений.
Динамическая адаптация контента: ResizeObserver — бесценный инструмент для задач, где верстка или функциональность зависят от размеров элементов, например динамические гриды, коллапсирующий сайдбар или адаптивные шрифты.
На основе приведенных примеров использования ResizeObserver я и написала хук, который отлично решил мою задачу:
// создаем хук “наблюдатель за высотой табов”, который получает ссылки
// на блоки, за которыми будет наблюдать
export const useTabsHeightObserver = (refs) => {
// создаем хранилища для высот 1-го и 2-го блоков
const [hb1, setHB1] = useState(0);
const [hb2, setHB2] = useState(0);
// вызываем useLayoutEffect для отслеживания изменений до того,
// как браузер перерисует экран
useLayoutEffect(() => {
// декомпозируем аргумент хука, получаем ссылки на блоки
const { block1Ref, block2Ref } = refs;
// получаем элементы из DOM за которыми наблюдаем
const observeBlock1 = block1Ref.current;
const observeBlock2 = block2Ref.current;
// если элементов нет, то прекращаем наблюдение
if (!observeBlock1 || !observeBlock2) return;
// создаем экземпляр ResizeObserver -
// “наблюдателя” за конкретным блоком, передаем ему колбэк для вызова
// при изменении размеров блока (сохраняем высоту блока в хранилище)
const resizeObserverBlock1 = new ResizeObserver((entries) => {
for (let entry of entries) {
const { height } = entry.contentRect;
setHB1(height);
}
});
const resizeObserverBlock2 = new ResizeObserver((entries) => {
for (let entry of entries) {
const { height } = entry.contentRect;
setHB2(height);
}
});
// призываем наблюдателей наблюдать за конкретными элементами
resizeObserverBlock1.observe(observeBlock1);
resizeObserverBlock2.observe(observeBlock2);
// увольняем наблюдателей после исчезновения элементов наблюдения
return () => {
resizeObserverBlock1.unobserve(observeBlock1);
resizeObserverBlock2.unobserve(observeBlock2);
};
}, [refs]);
// вычисляем и возвращаем высоту третьего блока
const tabsHeight = hb2 - hb1;
return tabsHeight;
};
Увидеть работу моего хука можно в песочнице.
Заключение
В заключение хочу еще раз подчеркнуть основную идею своей статьи. Конечно, мне хотелось поделиться тем, какой замечательный хук я написала, вдохновившись ResizeObserver. Но за этим стоит нечто более важное. Когда мы погружаемся в обдумывание решения той или иной задачи, то оно уже существует в окружающем нас мире — в том, что мы видим ежедневно, просто живя своей обычной жизнью. Поэтому если перед вами стоит задача, просто дайте себе время подумать и оглянитесь вокруг. Решение рядом. Мы лучше ИИ, поскольку наша база данных — это весь мир ? Так мы победим ?
Очень жду ваших комментариев. Спасибо за внимание!
Больше авторских материалов для frontend-разработчиков от моих коллег читайте в соцсетях SimbirSoft – ВКонтакте и Telegram.
Комментарии (3)
adminNiochen
21.07.2025 10:36Формулировка задачи (особенно картинки) выглядит, как будто её можно решить через css без обсерверов, просто надо базово знать гриды
lolenko
21.07.2025 10:36ага, в следующий раз автору предлагаю начать именно с этого способа) кажется на флексах минут 10 https://codesandbox.io/p/sandbox/z9lgd6
Vitaly_js
Я бы на вашем месте пример все таки отредактировал. Вдруг кто-то подумает, что у вас что-то такое в продакшене.
Тут два момента. Первый, когда работаешь с рефами нет никакого смысла прокидывать их в зависимости (у вас этого конечно нет, но ваше решение вдохновлено чем-то подобным). Линтеры об этом знают, поэтому "кричать" не будут. И второй, когда работаешь с хуками нельзя конфигурировать их объектными литералами. В вашем примере, каждый рендер приводит к отработке useLayoutEffect, что вредно.
И есть еще один момент. Вы сделали вот такую штуку:
Это означает, что при каждом изменении размера у вас перерисовывается весь компонент. Это, конечно, мелочь, если размеры меняются редко, но все таки смысла для ряда случаев в этом нет никакого.
п.с.: за оптимизм пальчик вверх я вам поставлю.