Skip to content

Instantly share code, notes, and snippets.

@evertjr
Created July 31, 2024 13:20
Show Gist options
  • Save evertjr/d7c2a0a670cf92231bf519719a19225f to your computer and use it in GitHub Desktop.
Save evertjr/d7c2a0a670cf92231bf519719a19225f to your computer and use it in GitHub Desktop.
Feedback face animation with Framer Motion and Tailwind
"use client";
import { AnimationProps, motion } from "framer-motion";
import { useState } from "react";
type Feedbacks = "bad" | "not bad" | "good";
const feedbackMapping: { [key: number]: Feedbacks } = {
0: "bad",
1: "not bad",
2: "good",
};
const transition: AnimationProps["transition"] = {
type: "spring",
stiffness: 200,
damping: 17,
};
export function ExperienceAnimation() {
const [feedback, setFeedback] = useState<Feedbacks>("good");
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseInt(e.currentTarget.value, 10);
setFeedback(feedbackMapping[value]);
};
const backgroundColor =
feedback === "good"
? "#AFC358"
: feedback === "not bad"
? "#E1AB47"
: "#FD8464";
return (
<motion.div
animate={{
backgroundColor,
}}
className="h-screen w-full flex items-center flex-col gap-32 justify-center"
>
<div className="flex h-64 flex-col gap-2 justify-center items-center">
<Eyes state={feedback} />
<Mouth state={feedback} />
</div>
<input
value={Object.keys(feedbackMapping).find(
(key) => feedbackMapping[parseInt(key)] === feedback
)}
onChange={handleInputChange}
type="range"
step={1}
min={0}
max={2}
/>
</motion.div>
);
}
function Mouth({ state }: { state: Feedbacks }) {
const rotate = state === "good" ? 0 : 180;
return (
<motion.svg
animate={{ rotate }}
transition={transition}
xmlns="http://www.w3.org/2000/svg"
width={64}
height={27}
fill="none"
className="stroke-black/80"
>
<path
strokeLinecap="round"
strokeWidth={14}
d="M7.017 7.547C15.865 19.935 41.23 25.834 56.273 9.02"
/>
</motion.svg>
);
}
function Eyes({ state }: { state?: Feedbacks }) {
const height =
state === "good" ? "8rem" : state === "not bad" ? "3rem" : "4rem";
const width = state === "bad" ? "4rem" : "8rem";
const rotate = state === "bad";
return (
<div className="flex gap-5 items-center">
<motion.div
animate={{
height,
rotate: rotate ? -90 : 0,
width,
}}
transition={transition}
className="size-32 bg-black/80 rounded-full"
/>
<motion.div
animate={{
height,
rotate: rotate ? 90 : 0,
width,
}}
transition={transition}
className="size-32 bg-black/80 rounded-full"
/>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment