DEV Community

Kode Navigator
Kode Navigator

Posted on

Implementing a read more or less feature in a React app with GSAP.

I’ve been playing with GSAP (GreenSock Animation Platform) while learning its many uses for quite some time and I’m excited to share what I achieved with it recently. If you are wondering, GSAP is a set of JavaScript tools for building animation on the web while giving you unprecedented levels of control and flexibility.
Have you ever wondered how you would go about the read more or less feature in your React app and have it animate smoothly when you try to render the rest of the text on clicking a colored text or button? Good then, let's begin.

First of all, this is just an exercise, if you are just starting your React journey, the React team has prepared this tutorial for you. Likewise, for those starting with GSAP, the GSAP team got you covered with this detailed guide.
Start your project using create react app by running npx create-react-app read-more-or-less and cd read-more-or-less in your cli, once the project is set up, run npm i gsap. For this exercise, we would create a reusable component, ShowContent.js, and destructure ‘children’, ‘vars’ (an object of properties you would be passing to the gsap timeline), and ‘visible’ for the text you want to have visible from the props object.

Import useState, useLayoutEffect (a hook similar to useEffect but it fires synchronously after all DOM mutations, for more info read the React doc) from ‘react’ and gsap from ‘gsap’. Next, return a wrapping p tag and pass in a span for the visible text, a span for the children, and a button for read more or less which will be toggled and styled to your preference but for this post, you can use the styles below.

At the top of the component, initialize a toggle state as "showMore" and its updating callback as "setShowMore" from useState and set its initial value to false. Also, initialize the ref variable "hiddenTextRef" from useRef with an initial value of null which would be used to reference the hidden element in gsap and pass it to the ref attribute of the span wrapping the ‘children’ props which is hidden. Furthermore, invoke the useLayoutEffect hook beneath the initialized ref variable, and pass it a callback function. Between the initialized "textRef" variable and useLayoutEffect call, initialize a ref called tl with "const" and update its "current" property in the useLayoutEffect callback function with gsap timeline set to a default of paused and reversed, true.

Variables initialization

Finally, I thought we won't be done with all these initializations, I'm stepping out a bit to get my magic wand because we are going to need it for the next phase.
Hah, I’m back, so with the initial setup out of the way let's get our hands dirty. Within the useLayoutEffect callback function, just beneath the updated current property of the tl ref, put

tl.current.from(hiddenTextRef.current, { 
height: 0, width: 0, autoAlpha: 0, ...vars, ease: "power2.in" })
Enter fullscreen mode Exit fullscreen mode

and pass an empty array of dependencies as a second argument of useLayoutEffect (try putting variables and see gsap behave funny). Next, create a click event handler function beneath useLayoutEffect to toggle "showMore" state from false to true and back, just above the state update still within the handler, set a condition to check if the timeline is reversed in this format tl.current.reversed() ? tl.current.play() : tl.current.reverse();. Pass the handler function to the onClick attribute of the button, and set a condition to change the text within the button to "read more" or "show less" depending on the state of "showMore".

In addition, we want the hidden text to be animated, but that's not possible because spans are inline and cannot be animated by default. So create a class with the property of display: inline-block and overflow: hidden or an inline style with such properties and remove those properties when the state becomes "true", in this way our hidden text animates as expected but returns to being on the same line as the visible text when the state is "true". Oh! before I forget, return a cleanup function to kill the timeline progress after every render.

The complete code

Finally, import this component where you want it, wrap it around the text you want hidden, and pass in the necessary props. As for the vars props, either give it an empty object or pass in an object of property you want gsap to animate. For example, vars={{ duration: 1 }} or vars={{ }}.

Image description

That was not so hard, right? Well, you can have a glimpse of it in effect right here. I'm open to suggestions as to ways to achieve it differently.

Discussion (0)