Привет, Хабр! Меня зовут Анна, я 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.
ссылка на оригинал статьи https://habr.com/ru/articles/926864/
Добавить комментарий