Lightweight React AoS
Hossam A.Menem
Posted on May 6, 2023
In today's modern websites, having animation in your website is a must, and some simple animations can add a lot to your website, that's why AoS library is so popular, but what about creating an alternative for it on your own?
Our starting point
I didn't write the code for this component from scratch, the code I started from was by Bret Cameron, and from the next section in this blog, I'll assume that you went through his code and got an idea about what this component does.
Note
When I copied Bret's code from his codepen, I got an issue because of how he imported the types, I got this error 'X' refers to a value, but is being used as a type here. Did you mean 'typeof X'?
, with X being any type I'm importing like CSSProperties
or RefObject
, I solved this error by using import { ... } from "react"
instead of const {...} = React
.
Issues with this code
overriding default values
there is no way to change the values for the defaultStyles
in the AnimateIn
function, which is quite problematic because you might want to add a delay for some sections, change the easing option...etc. To solve this issue let's first create an interface for our animation components.
interface IAnimation {
children: ReactNode;
transitionProps?: CSSProperties;
}
Now let's use this interface in our animation components, and let's just try the FadeIn.
const FadeIn = (props: IAnimation) => (
<Animate
from={{ opacity: 0 }}
to={{ opacity: 1 }}
transitionProps={props.transitionProps}
>
{props.children}
</Animate>
);
Note: I changed the name of AnimateIn
function to Animate
.
Since we have now passed our transitionProps we should handle them in our Animate
function, creating an interface for that is not necessary but I just like to use interfaces.
interface IAnimate extends IAnimation {
from: CSSProperties;
to: CSSProperties;
}
Let's add a condition to handle when to use the default props and when to use the passed props like this:
const styleProps = props.transitionProps
? props.transitionProps
: defaultStyles;
And we just use styleProps
as we did with the defaultStyles
.
A "run once" option
Now, Everything runs perfectly, but there is one issue, that I'd consider a huge one, which is when you scroll down and one of the sections that you are observing is now on the screen and its animation has started then you scroll down and go back up the animation will start again, in most websites you don’t see that, because it’s kinda annoying to have animations that start every time you scroll to a section, so for that reason, we should add a "run-once" option, but I'm not gonna make it the only option, because there might be some use cases where the animation have to start every time the user scrolls to the section. First of all, we update our IAnimation
interface to have a runOnce prop.
interface IAnimation {
children: ReactNode;
runOnce?: boolean;
transitionProps?: CSSProperties;
}
Also, add a parameter to the useElementOnScreen
hook to handle the run once mode.
const useElementOnScreen = (
ref: RefObject<Element>,
runOnce: boolean | undefined
) => { ... }
And the logic for it is fairly simple, everything runs as normal, we keep observing the section that we want, and we check if it has been on the screen, if it does ( the animation has started ), then we check if runOnce
is true, if it is we unobserve this section, otherwise, we just keep observing and triggering the animation again and again when the element enters the screen. So let's take the process for this one step by step.
-
First we specify the
runOnce
argument in one of our animation divs like this.
<AoS.FadeUp runOnce> <AnySection/> </AoS.FadeUp>
Note: the default for this option is true, so you don't have
to specify it unless you want to set it to false. -
since we have its value, we can now access it via the
useRef
hook in theAnimate
, then pass it to theuseElementOnScreen
hook like this.
const { current: runOnce } = useRef(props.runOnce); const ref = useRef<HTMLDivElement>(null); const onScreen = useElementOnScreen(ref, runOnce);
Inside the
useElementOnScreen
hook we will add an if statement for that option like this.
const useElementOnScreen = (
ref: RefObject<Element>,
runOnce: boolean | undefined
) => {
const [isIntersecting, setIsIntersecting] = useState(true);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (runOnce && ref.current && entry.isIntersecting) {
observer.unobserve(ref.current);
}
...
In this if statement, first we check if the runOnce
option is true, then we check if the element that we are passing exists, and last with entry.isIntersecting
we check if the element has transitioned into the intersection state, which means that It has been on the screen and its animation has started.
For more information about the intersection observer API visit the MDN docs.
And that's it...
Now you can just copy this file, add any your changes and start using it in your next project.
If you wanna see some of those animations on a live website, I'm already using them in my portfolio, so feel free to check it out.
Posted on May 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.