Skip to content

Commit eaf1570

Browse files
committed
Add WaveSection animation and improve AnimateWhenInViewport component
1 parent ff059b5 commit eaf1570

File tree

3 files changed

+81
-44
lines changed

3 files changed

+81
-44
lines changed
Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,61 @@
1-
import { Ref, ReactElement, cloneElement, DetailedReactHTMLElement, MutableRefObject } from "react";
2-
import { useRef, useEffect, useState } from "react";
1+
import { useRef, useEffect, useState, cloneElement } from "react";
32

43
export interface Animation {
54
initDelay?: number;
65
threshold?: number;
6+
noHide?: boolean;
7+
type?: "slideUp" | "slideDown" | "growYUp" | "growYDown";
78
}
89

910
interface AnimateWhenInViewportProps<T> extends Animation {
1011
children: JSX.Element;
12+
wrapperClassName?: string;
1113
}
1214

1315
export default function AnimateWhenInViewport<T extends HTMLElement>({
1416
children,
17+
wrapperClassName = "",
1518
initDelay = 0,
1619
threshold = 0,
20+
noHide,
21+
type,
1722
}: AnimateWhenInViewportProps<T>) {
1823
const ref = useRef<T | null>(null);
1924
const [initTime, setInitTime] = useState(performance.now());
2025

26+
// Wait for element to be in viewport, then start the animation.
2127
useEffect(() => {
2228
if (!ref.current) return;
2329

24-
const hideElement = (elem: HTMLElement | null) => {
25-
if (elem && !elem.classList.contains("hidden")) {
30+
/**
31+
* Add `hidden` css class to `elem` HTMLElement.
32+
*/
33+
const hideElement = (elem: HTMLElement) => {
34+
if (!noHide && !elem.classList.contains("hidden")) {
2635
elem.classList.add("hidden");
2736
}
2837
};
2938

30-
const applyAnimation = (elem: HTMLElement | null) => {
31-
if (!elem) return;
32-
39+
/**
40+
* Removes `hidden` and adds `animation` class to `elem` HTMLElement.
41+
* Disconnects observer afterwards.
42+
*/
43+
const applyAnimation = (elem: HTMLElement) => {
3344
elem.classList.add("animation");
3445
elem.classList.remove("hidden");
3546
observer.disconnect();
3647
};
3748

38-
const options = {
49+
/**
50+
* Applies a delayed animation. {@see applyAnimation}.
51+
*/
52+
const delayAnimation = (elem: HTMLElement, wait: number) => {
53+
hideElement(elem);
54+
elem.style.animationDelay = `${Math.round(wait)}ms`;
55+
applyAnimation(elem);
56+
};
57+
58+
const observerOptions = {
3959
threshold,
4060
};
4161

@@ -45,26 +65,25 @@ export default function AnimateWhenInViewport<T extends HTMLElement>({
4565
if (entries[0].isIntersecting && entries[0].intersectionRatio >= threshold) {
4666
const timeSinceInit = performance.now() - initTime;
4767
const wait = initDelay - timeSinceInit;
48-
console.log("wait", Math.round(wait));
4968

5069
if (!initDelay || wait <= 0) {
5170
applyAnimation(ref.current);
5271
} else {
53-
hideElement(ref.current);
54-
setTimeout(() => applyAnimation(ref.current), wait);
72+
delayAnimation(ref.current, wait);
5573
}
5674
return;
5775
}
5876

77+
// Hide element on page load.
5978
hideElement(ref.current);
60-
}, options);
79+
}, observerOptions);
6180

6281
observer.observe(ref.current);
6382

6483
return () => {
6584
observer.disconnect();
6685
};
67-
}, [initDelay, initTime, ref, threshold]);
86+
}, [initDelay, initTime, noHide, ref, threshold]);
6887

6988
/**
7089
* Set `ref.current` to `current`, if not null.
@@ -75,5 +94,9 @@ export default function AnimateWhenInViewport<T extends HTMLElement>({
7594
}
7695
};
7796

78-
return <div className="animation-wrapper">{cloneElement(children, { ref: updateRef })}</div>;
97+
return (
98+
<div className={`${wrapperClassName} animation-wrapper`}>
99+
{cloneElement(children, { ref: updateRef, className: `${children.props.className} ${type ?? ""}` })}
100+
</div>
101+
);
79102
}
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import Image from "next/image";
2-
import { createStyles, Transition, useMantineColorScheme } from "@mantine/core";
1+
import Image, { ImageProps } from "next/image";
2+
import { createStyles, useMantineColorScheme } from "@mantine/core";
33

44
// SVG's
55
import wavesLight from "../../../public/images/waves-light.svg";
66
import wavesDark from "../../../public/images/waves-dark.svg";
7+
import AnimateWhenInViewport from "../../Atoms/AnimateWhenInViewport/AnimateWhenInViewport";
78

89
interface WaveSpacerProps {
910
children: JSX.Element | JSX.Element[];
@@ -19,7 +20,7 @@ const useStyles = createStyles((theme, _params) => ({
1920
rotate: {
2021
transform: "rotate(180deg)",
2122
},
22-
children: {
23+
background: {
2324
backgroundColor: theme.colorScheme === "dark" ? theme.colors.dark[5] : theme.colors.gray[2],
2425
},
2526
}));
@@ -28,18 +29,22 @@ export default function WaveSection({ children }: WaveSpacerProps) {
2829
const { colorScheme } = useMantineColorScheme();
2930
const { classes } = useStyles();
3031

31-
const img =
32-
colorScheme === "dark" ? (
33-
<Image priority src={wavesDark} alt="Background" layout="fill" objectFit="cover" />
34-
) : (
35-
<Image priority src={wavesLight} alt="Background" layout="fill" objectFit="cover" />
36-
);
32+
const imgSrc = colorScheme === "dark" ? wavesDark : wavesLight;
33+
34+
const imgTop = <Image priority src={imgSrc} alt="Background" layout="fill" objectFit="cover" />;
35+
const imgBottom = (
36+
<Image priority src={imgSrc} alt="Background" layout="fill" objectFit="cover" className={classes.rotate} />
37+
);
3738

3839
return (
3940
<>
40-
<div className={classes.waves}>{img}</div>
41-
<div className={classes.children}>{children}</div>
42-
<div className={`${classes.waves} ${classes.rotate}`}>{img}</div>
41+
<AnimateWhenInViewport type="growYUp" noHide>
42+
<div className={`${classes.waves} ${classes.background}`}>{imgTop}</div>
43+
</AnimateWhenInViewport>
44+
<div className={classes.background}>{children}</div>
45+
<AnimateWhenInViewport type="growYDown" noHide>
46+
<div className={`${classes.waves} ${classes.background}`}>{imgBottom}</div>
47+
</AnimateWhenInViewport>
4348
</>
4449
);
4550
}

styles/globals.css

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ a {
5050
animation: slideUp .7s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
5151
}
5252

53-
.animation-down {
54-
animation: slideDown .7s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
55-
}
56-
5753
.animation.inf {
5854
animation-iteration-count: infinite;
5955
}
@@ -98,14 +94,13 @@ a {
9894
animation-delay: .8s;
9995
}
10096

101-
/* ===============================
102-
* Global Animations - Keyframes
103-
* =============================== */
104-
97+
/* ===============================================================
98+
* Global Animations - Keyframes & CSS Specific to each Keyframe
99+
* =============================================================== */
105100

106101
@keyframes slideUp {
107102
0% {
108-
transform: translateY(150px) scale(.5);
103+
transform: translateY(100px) scale(.5);
109104
opacity: 0;
110105
}
111106

@@ -119,20 +114,34 @@ a {
119114
}
120115
}
121116

122-
@keyframes slideDown {
117+
.animation.growYUp {
118+
animation-name: growYUp;
119+
transform-origin: bottom;
120+
animation-duration: 1s;
121+
}
122+
123+
@keyframes growYUp {
123124
0% {
124-
transform: translateY(200px);
125-
transform-origin: 50% 50%;
126-
opacity: 0;
125+
transform: scaleY(.5);
127126
}
128127

129-
10% {
130-
opacity: .3;
128+
100% {
129+
transform: scaleY(1);
130+
}
131+
}
132+
133+
.animation.growYDown {
134+
animation-name: growYDown;
135+
transform-origin: top;
136+
animation-duration: 1s;
137+
}
138+
139+
@keyframes growYDown {
140+
0% {
141+
transform: scaleY(.5);
131142
}
132143

133144
100% {
134-
transform: translateY(0);
135-
transform-origin: 100% 0%;
136-
opacity: 1;
145+
transform: scaleY(1);
137146
}
138147
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy