I have here a text animation that is working perfect. What I want to add now is an Intersection Observer so that the animation only starts once I scroll down to the Box.
So what I did to achieve this is:
I used the react hook useRef
to use as reference to the element I want to observe and applied it to my Box with ref={containerRef}
. Then declared a callback function that receives an array of IntersectionObserverEntries as a parameter, inside this function I take the first and only entry and check if it is intersecting with the viewport and if it is then it calls setIsVisible with the value of entry.isIntersecting (true/false). After that I added the react hook useEffect and created an observer contructor using the callback function and the options I just created before. I implemented the logic in a new hook that I called useElementOnscreen
But Typescript is telling me an error at containerRef?.current
:
Argument of type 'IntersectionObserver' is not assignable to parameter of type 'Element'.
Type 'IntersectionObserver' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 160 more.
And I am not sure how to solve this error. I think this is also the reason that my ref={containerRef}
is throwing an error too
The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; component?: ElementType<...> | undefined; ref?: Ref<...> | undefined; sx?: SxProps<...> | undefined; } & CommonProps & Omit<...>'
The animation: So, TopAnimateBlock and BottomAnimateBlock have numOfLine property hence how many lines is inside the block. The second property in BottomAnimateBlock is delayTopLine, it should have the same numbers as a numOfLine in TopAnimateBlock, because we need to wait for top lines to play.
TextAnimation.tsx
import { Box, Stack, Typography } from '@mui/material';
import React, { useRef, useEffect, useState } from 'react';
import styled, { keyframes } from 'styled-components';
const showTopText = keyframes`
0% { transform: translate3d(0, 100% , 0); }
40%, 60% { transform: translate3d(0, 50%, 0); }
100% { transform: translate3d(0, 0, 0); }
`;
const showBottomText = keyframes`
0% { transform: translate3d(0, -100%, 0); }
100% { transform: translate3d(0, 0, 0); }
`;
const Section = styled.section`
width: calc(100% + 10vmin);
display: flex;
flex-flow: column;
padding: 2vmin 0;
overflow: hidden;
&:last-child {
border-top: 1vmin solid white;
}
`;
const Block = styled.div<{ numOfLine: number }>`
position: relative;
`;
const TopAnimateBlock = styled(Block)`
animation: ${showTopText} calc(0.5s * ${props => props.numOfLine}) forwards;
animation-delay: 0.5s;
transform: translateY(calc(100% * ${props => props.numOfLine}));
`;
const BottomAnimateBlock = styled(Block)<{ delayTopLine: number }>`
animation: ${showBottomText} calc(0.5s * ${props => props.numOfLine}) forwards;
animation-delay: calc(0.7s * ${props => props.delayTopLine});
transform: translateY(calc(-100% * ${props => props.numOfLine}));
`;
const TextStyle = styled.p<{ color: string }>`
font-family: Roboto, Arial, sans-serif;
font-size: 12vmin;
color: ${props => props.color};
`;
const useElementOnScreen = (options) => {
const containerRef = useRef<IntersectionObserver | null>(null);
const [isVisible, setIsVisible] = useState(false);
const callbackFunction = (entries) => {
const [entry] = entries;
setIsVisible(entry.isIntersecting);
};
useEffect(() => {
const observer = new IntersectionObserver(callbackFunction, options);
if (containerRef.current) observer.observe(containerRef?.current);
return () => {
if (containerRef.current) observer.unobserve(containerRef?.current);
};
}, [containerRef, options]);
return [containerRef, isVisible];
};
export function Details() {
const [containerRef, isVisible] = useElementOnScreen({
root: null,
rootMargin: '0px',
threshold: 1.0,
});
return (
<>
<Typography>Scroll Down</Typography>
<Box ref={containerRef}>
<Section>
<TopAnimateBlock numOfLine={2}>
<TextStyle color="grey">mimicking</TextStyle>
<TextStyle color="white">apple's design</TextStyle>
</TopAnimateBlock>
</Section>
<Section>
<BottomAnimateBlock numOfLine={1} delayTopLine={2}>
<TextStyle color="white">for the win!</TextStyle>
</BottomAnimateBlock>
</Section>
</Box>
</>
);
};