Как на примере одной кнопки можно улучшить Frontend часть проекта
Всем привет! Я джуниор фронтенд разработчик. И хотел бы рассказать как иногда применение библиотек в проекте — это излишества, которые стоит избегать.
Давай начнем по порядку. Проект написан на NextJs, TS, TailwindCSS. И есть на сайте анимационная кнопка, которые при скролле красиво появляется и при клике открывает модалку.
Вся логика, запилена на Gsap. И казалось бы все хорошо, библиотека делает за нас дело, рисует анимации, а мы спокойно пьем чаек и отправляем ПР в гитхаб.
НО. Проблема была в том, что иногда анимация подвисала, дергалась, а на слабых ПК вообще отказывалась работать.
В итоге решился взяться за анимационную кнопку и посмотреть что же там ее кишках :-)
И, о ЧУДО! Если посмотреть на компонент, то мне кажется он очень страшным. Как вам кажется?
export const AnimatedButtonLinkWitmUtm: React.FC = ({
isDark = false,
btnColor = '#0A85D1',
btnShadow = '0 0 0 0px rgb(157,52,218)',
maxWidth = 315,
}) => {
const buttonRef = useRef();
const tl = gsap.timeline();
useEffect(() => {
if (buttonRef.current) {
const trigger = ScrollTrigger.create({
trigger: buttonRef.current,
start: `top bottom-=400px`,
onEnter: () => {
tl.to(buttonRef.current, { opacity: 1, duration: 0.3 })
.to(buttonRef.current, {
duration: 0.3,
boxShadow: btnShadow,
ease: 'circ.in',
})
.to(buttonRef.current, {
duration: 0.3,
boxShadow: btnShadow,
ease: 'circ.out',
})
.to(buttonRef.current, {
maxWidth: `${maxWidth}px`,
width: '100%',
paddingLeft: 24,
duration: 1,
})
.to(buttonRef.current.children[1], { opacity: 1, duration: 2 }, '<0.4')
.to(buttonRef.current.children[0], { opacity: 1, duration: 2 }, '<0.5');
},
onLeaveBack: () => {
tl.to(buttonRef.current, { opacity: 0 })
.to(buttonRef.current.children[1], { opacity: 0 })
.to(buttonRef.current, { width: '55px', paddingLeft: 10 })
.to(buttonRef.current.children[0], { opacity: 0, delay: 0, duration: 0 });
},
});
return () => {
trigger.kill();
};
}
}, [btnShadow, maxWidth, tl]);
return (
<>
Подробнее о программе
>
);
};
У нас есть логика на GSAP и триггер на каком моменте скрола она должна появляться.
Если посмотреть на анимацию, она основана на размерах и opacity.
А нужен ли нам GSAP для такой простой анимации??? НЕТ. И давайте я постараюсь объяснить почему.
1 — Вес библиотеки. Ради простейшей анимации вы подтягиваете в проект библиотеку весом 4 мб.
2 — Эту анимацию можно сделать на чистом CSS
Анимацию мы напишем, но для триггера, чтобы все срабатывало нам нужен лишь Observer. Все это можно реализовать с помощью хуков, но для такой ситуации есть библиотека намного легче и проще в использование и под капотом у нее лежат реактовские хуки.
React-Intersection-Observer
Как вы видите пакет намного легче и популярнее по скачиваниям, так как он перекрывает простые потребности по триггерам и анимациям :-)
И что в итоге у меня получилось
export const NewAnimatedButtonWithLinkUtm = ({
isDark = false,
btnColor = '#0A85D1',
}: IAnimatedButtonProps) => {
const { ref, inView } = useInView();
return (
Начать учиться бесплатно
);
};
Как вы видите вместо 85 строк кода и сложной логикой, у меня 36 строк кода и стили применяется через логическое значение. Думаю будет намного проще рефакторить такой код :-)
НО. На этом еще не все. Проблема была в том, что он появлялся сразу, как секция попадала во вьюпорт пользователя, а нам надо чтобы пользователь проскролил 30% секции и только после этого анимация срабатывала. И тут честно я немного затормозил и начал гуглить, хотя спустя пару часов логика оказалась очень простой.
Я создал контейнер в котором лежит кнопка и через children принимает секцию, по которой будет скролит пользователь и вот что получилось :-)
export const SectionNewAnimatedButtonWithLinkUtm = ({
isDark = false,
btnClassName,
children,
className = 'relative mx-auto mb-40 max-w-[1024px] px-5 lg:px-0',
text = 'Начать учиться бесплатно',
}: IAnimatedButtonProps) => {
const { ref, inView } = useInView({
threshold: 0.2,
});
return (
{children}
{text}
);
};
Прибавилось 10 строк кода, но ничего страшного. Код все также остается понятным :-)
Вроде бы все, можно открывать ПР и ждать ответа от Тимлида и мержить в основную ветку.
НОООО… Мы можем еще прокачать свой компонент и соблюдать один из принципов ООП — Open/Closed и спрятать всю логику под капот, чтобы фронтенд разработчик в команде пользовался компонентом и ему не приходилось залазить в кишки.
И тут решил я связать контейнер и компонент кнопки через обычный контекст в реакте:-)
Впервую очередь мы создаем контекст и контейнер и передаем основное значение для анимации
export const AnimatedButtonContext = createContext(null);
export const SectionAnimatedButton = ({ children }) => {
const { ref, inView } = useInView({
threshold: 0.2,
});
return (
{children}
);
};
Затем в самой кнопке и используем хук useContext и привязываемся к контексту
export const AnimatedBtn = ({
isDark = false,
btnClassName,
text = 'Начать учиться бесплатно',
link = '',
}: IAnimatedButtonProps) => {
const { inView } = useContext(AnimatedButtonContext);
return (
{text}
);
};
Теперь компонент ГОТОВ :-)
Что в итоге этой работы мы добились:
1 — Убрали тяжелую библиотеку
2 — Сделаю логику кнопки проще и следовательно рефакторить станет гораздо проще
3 — Спрятали логику и создали контейнер, в который мы заворачиваем наш контент и все работает :-)
4 — Анимация стала работать гладко и без зависаний, в том числе и на слабых ПК
Статья получилась немного сумбурная, но этим хотел сказать, что применение библиотек не всегда хорошо и иногда полезно залезть в ту самую БАЗУ и посмотреть простое решение:-)
Спасибо всем кто дочитал! Был рад рассказать про парт-тайм проект, на котором работаю и поэтому хочу сказать что я открыт для предложений к офферам и если вам нужен активный и амбициозный разработчик — буду рад с вами пообщаться и пофлексить вашим кодом :-)
https://t.me/sadbatya
А с вами был Владимир! Хорошего дня, вечера и ночи :-)