Logo

dev-resources.site

for different kinds of informations.

How to improve the Frontend part of the project using one button as an example :))))

Published at
1/14/2025
Categories
frontend
gsap
react
ts
Author
sadbatya
Categories
4 categories in total
frontend
open
gsap
open
react
open
ts
open
Author
8 person written this
sadbatya
open
How to improve the Frontend part of the project using one button as an example :))))

Hello everyone! I am a junior frontend developer. And I would like to tell you how sometimes using libraries in a project is an excess that should be avoided.

Let's start in order. The project is written in NextJs, TS, TailwindCSS. And there is an animated button on the site that appears beautifully when scrolling and opens a modal when clicked.

Image description

All the logic is written in Gsap. And it would seem that everything is fine, the library does the job for us, draws animations, and we calmly drink tea and send PR to GitHub.
BUT. The problem was that sometimes the animation froze, twitched, and on weak PCs it refused to work at all.
In the end, I decided to take on the animation button and see what's inside it :)
And, oh, MIRACLE! If you look at the component, it seems very scary to me. What do you think?

`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();
  };
}
Enter fullscreen mode Exit fullscreen mode

}, [btnShadow, maxWidth, tl]);

return (
<>

    ref={buttonRef}<br>
    className={cn(<br>
      'relative flex h-[56px] w-[56px] items-center justify-between gap-[16px] rounded-[10px] px-[10px] py-[8px] opacity-0 shadow-[inset_0px_1px_0px_0px_rgba(0,0,0,0.11)] backdrop-blur-[3.5px]',<br>
      {<br>
        'bg-[#D8D8D8]/30': !isDark,<br>
        'bg-[#424245]/70': isDark,<br>
      }<br>
    )}<br>
  &gt;<br>

      href="https://a6b9d8dc-b142-4b92-b1d0-dfbfd2230471.selstorage.ru/assets/edu-program.pdf"<br>
      target="_blank"<br>
      className={cn(<br>
        'm-0 text-[17px] font-medium leading-[27.2px] opacity-0 after:absolute after:inset-0',<br>
        {<br>
          'text-black': !isDark,<br>
          'text-[#F5F5F7]': isDark,<br>
        }<br>
      )}<br>
    &gt;<br>
      Подробнее о программе<br>
    <br>

      className="h-[40px] w-[40px] min-w-[40px] rounded-[10px] p-[8px] opacity-0"<br>
      style={{ backgroundColor: btnColor }}<br>
    &gt;<br>
      <br>
    <br>
  <br>
&lt;/&gt;<br>
Enter fullscreen mode Exit fullscreen mode

);

};

`

We have logic on GSAP and a trigger at what point of scrolling it should appear.

If you look at the animation, it is based on the size and opacity.

Do we need GSAP for such a simple animation??? NO. And let me try to explain why.
1 - Library weight. For the sake of the simplest animation, you pull a library weighing 4 MB into the project.
2 - This animation can be done in pure CSS

Image description

We will write the animation, but for the trigger, so that everything works, we only need Observer. All this can be implemented using hooks, but for such a situation there is a library much lighter and easier to use and under the hood it has React hooks.

Image description
As you can see, the package is much lighter and more popular in terms of downloads, as it covers simple needs for triggers and animations:)

And what did I end up with?
`export const NewAnimatedButtonWithLinkUtm = ({
isDark = false,
btnColor = '#0A85D1',
}: IAnimatedButtonProps) => {
const { ref, inView } = useInView();

return (

  ref={ref}<br>
  className={twMerge(<br>
    'sticky bottom-8 top-[80vh] mx-auto mt-8 flex h-[56px] cursor-pointer items-center justify-between gap-[16px] rounded-[10px] px-4 py-[8px] shadow-md backdrop-blur-[3.5px] transition-all',<br>
    isDark ? 'bg-[#424245]/70' : 'bg-[#D8D8D8]/30',<br>
    inView ? `opacity-1 w-[315px] delay-500 duration-1000` : 'w-[65px] opacity-0'<br>
  )}<br>
&gt;<br>

    href={linkHref}<br>
    className={twJoin(<br>
      'm-0 text-[17px] font-medium transition-all after:absolute after:inset-0',<br>
      isDark ? 'bg-[#424245]/70 text-[#F5F5F7]' : 'bg-transparent text-black',<br>
      inView ? 'opacity-1 delay-1000 duration-1000' : 'opacity-0'<br>
    )}<br>
  &gt;<br>
    Начать учиться бесплатно<br>
  <br>

    className={twJoin(<br>
      `absolute right-3 h-[40px] w-[40px] min-w-[40px] animate-pulse rounded-[10px] p-2 transition-all bg-[${btnColor}]`,<br>
      inView ? 'opacity-1' : 'opacity-0'<br>
    )}<br>
  &gt;<br>
    <br>
  <br>
<br>
Enter fullscreen mode Exit fullscreen mode

);

};`

As you can see, instead of 85 lines of code and complex logic, I have 36 lines of code and the styles are applied via a logical value. I think it will be much easier to refactor such code)

BUT. That's not all. The problem was that it appeared immediately as the section entered the user's viewport, and we need the user to scroll 30% of the section and only after that the animation would work. And here, honestly, I slowed down a little and started googling, although after a couple of hours the logic turned out to be very simple.

I created a container in which the button is located and through children it receives the section along which the user will scroll and this is what happened))

`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}
className={twMerge(
'sticky bottom-8 top-[80vh] mx-auto mt-8 flex h-[56px] cursor-pointer items-center justify-between gap-[16px] rounded-[10px] px-4 py-[8px] shadow-md backdrop-blur-[3.5px] transition-all',
isDark ? 'bg-[#424245]/70' : 'bg-[#D8D8D8]/30',
inView
? `opacity-1 w-[315px] delay-500 duration-1000`
: 'w-[65px] opacity-0 duration-1000'
)}
>
href={linkHref}
className={twJoin(
'm-0 text-[17px] font-medium transition-all after:absolute after:inset-0',
isDark ? 'bg-[#424245]/70 text-[#F5F5F7]' : 'bg-transparent text-black',
inView ? 'opacity-1 delay-1000 duration-200' : 'opacity-0'
)}
>
{text}

className={twMerge(
`absolute right-3 h-[40px] w-[40px] min-w-[40px] animate-pulse rounded-[10px] bg-[#0A85D1] p-2 transition-all`,
`${btnClassName}`,
inView ? 'opacity-1' : 'opacity-0'
)}
>




);
};`

10 lines of code have been added, but that's okay. The code is still clear)

It seems that's all, you can open the PR and wait for a response from the Team Lead and merge into the main branch.

NOOOO)))))) We can still pump up our component and follow one of the OOP principles - Open/Closed and hide all the logic under the hood so that the frontend developer in the team uses the component and does not have to get into the guts.

And then I decided to link the container and the button component through a regular context in React)
First of all, we create a context and a container and pass the main value for the animation

`export const AnimatedButtonContext = createContext(null);

export const SectionAnimatedButton = ({ children }) => {
const { ref, inView } = useInView({
threshold: 0.2,
});

return (


{children}



);
};`

Then in the button itself we use the useContext hook and bind to the context

`export const AnimatedBtn = ({
isDark = false,
btnClassName,
text = 'Начать учиться бесплатно',
link = '',
}: IAnimatedButtonProps) => {
const { inView } = useContext(AnimatedButtonContext);

return (

  className={twMerge(<br>
    'sticky bottom-8 top-[80vh] mx-auto mt-8 flex h-[56px] cursor-pointer items-center justify-between gap-[16px] rounded-[10px] px-4 py-[8px] shadow-md backdrop-blur-[3.5px] transition-all',<br>
    isDark ? 'bg-[#424245]/70' : 'bg-[#D8D8D8]/30',<br>
    inView ? `opacity-1 w-[315px] delay-500 duration-1000` : 'w-[65px] opacity-0 duration-1000'<br>
  )}<br>
&gt;<br>

    href={link}<br>
    className={twJoin(<br>
      'm-0 text-[17px] font-medium transition-all after:absolute after:inset-0',<br>
      isDark ? 'bg-[#424245]/70 text-[#F5F5F7]' : 'bg-transparent text-black',<br>
      inView ? 'opacity-1 delay-1000 duration-200' : 'opacity-0'<br>
    )}<br>
  &gt;<br>
    {text}<br>
  <br>

    className={twMerge(<br>
      `absolute right-3 h-[40px] w-[40px] min-w-[40px] animate-pulse rounded-[10px] bg-[#0A85D1] p-2 transition-all`,<br>
      `${btnClassName}`,<br>
      inView ? 'opacity-1' : 'opacity-0'<br>
    )}<br>
  &gt;<br>
    <br>
  <br>
<br>
Enter fullscreen mode Exit fullscreen mode

);

};`

Now the component is READY)

What we have achieved as a result of this work:

1 - Removed a heavy library
2 - I will make the button logic simpler and therefore it will be much easier to refactor
3 - Hid the logic and created a container in which we wrap our content and everything works))
4 - Animation began to work smoothly and without freezing, including on weak PCs

The article turned out to be a little chaotic, but I wanted to say that using libraries is not always good and sometimes it is useful to get into that very DATABASE and look at a simple solution)

Thanks to everyone who read it to the end! I was glad to tell you about the part-time project I work on and therefore I want to say that I am open to offers to offers and if you need an active and ambitious developer, I will be glad to talk to you and flex your code :)
https://t.me/sadbatya

And with you was Vladimir! Have a nice day, evening and night :)

ts Article's
30 articles in total
Favicon
How to improve the Frontend part of the project using one button as an example :))))
Favicon
Azure App Service doesn't returned compressed (gzip) files for Angular application?
Favicon
Deploying a Simple Static Website on AWS with CDK and TypeScript
Favicon
Understanding Next.js and TypeScript Tutorial
Favicon
🛠️📚 Classes with TypeScript - a cheat sheet
Favicon
Event Loop in 2 Minutes
Favicon
How Boring It Is to Write Articles or Talk to Certain Types of Developers These Days.
Favicon
How ts-pattern can improve your code readability?
Favicon
isNaN vs Number.isNaN
Favicon
🛠️ 📦 TypeScript Generics - a cheat sheet
Favicon
TypeScript Generics: How to Write Flexible and Type-Safe Code
Favicon
Introduction to TypeScript
Favicon
Dominando os Utility Types do TypeScript
Favicon
Nega aynan Vue.js ?
Favicon
TS-BoilerplateX
Favicon
What Are Standalone Components Angular 17 ?
Favicon
Navigating the JS TS Limbo
Favicon
Demystifying TypeScript Generics with Simple Examples
Favicon
Typescript enum vs. "as const"
Favicon
Mastering TypeScript: A Comprehensive Tutorial
Favicon
m3u8 to mp4 with merged audio
Favicon
Shouldn't an NPM package that helps hundreds of thousands of programmers increase their productivity tenfold be open-sourced?
Favicon
ChatGPT: how I used it to convert HTTP requests to OpenAPI document
Favicon
Cleaning up unused Typescript Files with `ts-prune`
Favicon
Intern's Guide Chat GPT Full Stack: Nest, React, Typescript
Favicon
Using LiteFS with Bun on Fly.io
Favicon
Know your users' location in React Ts
Favicon
Regex #1: Understanding Regex, Regex-Based String Methods and Regex Flags
Favicon
The Witcher Card Game Live
Favicon
Powerful File System manager for Nodejs

Featured ones: