Долгий путь к ResizeObserver

от автора

Привет, Хабр! Меня зовут Анна, я JS-разработчик в компании SimbirSoft и занимаюсь разработкой веб-приложений на React. Эту статью я посвящаю тем, кто занимается разработкой, сталкивается с нестандартными задачами и переживает, что нашу профессию может вскоре заменить искусственный интеллект (ИИ). Я поделюсь решением задачи, связанной с динамическими размерами блока, — проблемой, с которой наверняка может столкнуться в своей работе любой frontend-разработчик.

Почему же я назвала статью именно «Долгий путь к ResizeObserver»?

Возможно, я и слышала раньше про этот API, но когда передо мной встала конкретная задача (описанная ниже), я о нем даже не вспомнила. Мне пришлось пробовать сначала одно решение, потом другое — и лишь в третью очередь я пришла к нужному инструменту. Таков мой путь — из трех шагов. Я человек, поэтому могу честно рассказать, как именно искала решения, в отличие от ИИ. Надеюсь, моя статья поможет вам быстро и эффективно справиться с похожей задачей, а заодно придаст уверенности в собственных силах. Я убеждена: ответы на вопросы находятся не только в нашей голове, но и в окружающем мире, а человек, в отличие от ИИ, способен чувствовать, искать и находить их самым неожиданным для себя образом.

Итак, приступим 🙂

Перед нами лейаут, который состоит из трех блоков:

Высота первого и второго блоков может меняться, пока пользователь взаимодействует со страницей, а высота третьего должна подстраиваться динамически. Если высота второго блока небольшая, тогда третий блок будет иметь фиксированную высоту (200 px). Все три блока состоят из разных частей, данные в которые поступают изолированно.

Основная задача состоит в том, чтобы отслеживать высоту первого и второго блоков и, в зависимости от их значений, задавать высоту третьему блоку. Задавать ее нужно обязательно (и здесь лучше не полагаться на вычисление CSS), поскольку в третьем блоке несколько табов с контентом разной высоты, и при переключении между ними высота не должна «прыгать». Высота третьего блока не может быть меньше 200 px.

Итак, у моей статьи две цели:

  1. Рассказать, каким способом решила эту задачу — может быть полезно тем, кто столкнется с подобной ситуацией. 

  2. Рассказать, как именно я решала эту задачу — то есть описать процесс поиска решения с помощью естественного интеллекта (части статьи, посвященные этой цели, будут выделены курсивом). 

До сих пор я решала задачи, связанные с установкой высоты элементов в зависимости от загружаемого контента, с помощью 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *