I’ve been desperate to create one thing with the Gooey impact for some time. Whereas a lot of our friends on X have been experimenting with it, I wished to use it to a extra sensible element. Not too long ago, I stumbled upon a video showcasing dynamic island animations, which sparked my inspiration. Impressed by this, I made a decision to create a search bar—a small but gratifying interplay.
The Gooey Impact
First, we create the element for the Gooey impact from Lucas Bebber. I achieved the impact I wished by altering the alpha channel information within the values matrix.
const GooeyFilter = () => {
return (
<svg aria-hidden="true">
<defs>
<filter id="goo-effect">
<feGaussianBlur in="SourceGraphic" stdDeviation="5" end result="blur" />
<feColorMatrix
in="blur"
kind="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -15"
end result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
);
};
export default GooeyFilter;
Making use of the Impact
After creating the filter impact in our primary element, we apply this SVG filter to the mum or dad aspect via CSS.
<div className="wrapper">
<GooeyFilter />
</div>
Framer Movement Integration
Up up to now, the method has been easy. Now, let’s add the essential ending touches with Framer Movement. With the SVG filter now lively, it’s able to be utilized to our transferring components, bringing the gooey impact to life.
We are going to use 4 totally different states to handle the search bar:
const [state, setState] = useState({
step: 1, // Signifies the stage of the search course of 1: Preliminary state - 2: Search subject activated
searchData: [], // Comprises the outcomes of the search course of
searchText: "", // Shops the search textual content
isLoading: false, // Used to point out a loading icon when loading search outcomes
});
The code employs a nested construction of AnimatePresence elements. The outer layer manages the collective show of all outcomes, whereas the inside layer handles the person animation of every search end result.
<AnimatePresence mode="popLayout">
<movement.div
key="search-text-wrapper"
className="search-results"
function="listbox"
aria-label="Search outcomes"
exit={{ scale: 0, opacity: 0 }}
transition={{
delay: isUnsupported ? 0.5 : 1.25,
length: 0.5,
}}
>
<AnimatePresence mode="popLayout">
{state.searchData.map((merchandise, index) => (
<movement.div
key={merchandise}
whileHover={{ scale: 1.02, transition: { length: 0.2 } }}
variants={getResultItemVariants(index, isUnsupported)}
preliminary="preliminary"
animate="animate"
exit="exit"
transition={getResultItemTransition(index, isUnsupported)}
className="search-result"
function="possibility"
>
<div className="search-result-title">
<InfoIcon index={index} />
<movement.span
preliminary={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: index * 0.12 + 0.3 }}
>
{merchandise}
</movement.span>
</div>
</movement.div>
))}
</AnimatePresence>
</movement.div>
</AnimatePresence>;
getResultItemVariants
:
- exit: On exit, gadgets transfer barely upward (y: -4) until
isUnsupported
is true, creating the phantasm of merging with the search bar.
getResultItemTransition
:
- length and delay: Every merchandise has a transition length of 0.75 seconds and a delay calculated by its index (
index * 0.12
) to realize sequential animations. - kind and bounce: A spring animation with a bounce impact (0.35) is used for clean motion.
- filter: The filter property has easeInOut easing utilized to keep away from warnings within the spring calculation, as spring isn’t suitable with filter blur results.
const getResultItemVariants = (index, isUnsupported) => ({
preliminary: {
y: 0,
scale: 0.3,
filter: isUnsupported ? "none" : "blur(10px)",
},
animate: {
y: (index + 1) * 50,
scale: 1,
filter: "blur(0px)",
},
exit: {
y: isUnsupported ? 0 : -4,
scale: 0.8,
shade: "#000000",
},
});
const getResultItemTransition = (index) => ({
length: 0.75,
delay: index * 0.12,
kind: "spring",
bounce: 0.35,
exit: { length: index * 0.1 },
filter: { ease: "easeInOut" },
});
And that’s it! That is the end result:
Safari Compatibility Challenges
We noticed the isUnsupported
boolean within the code. This boolean was added by necessity. WebKit has some restrictions on SVG filters. It hasn’t been fastened for a very long time, though some customers have submitted bug stories.
isUnsupported
contains some fixes to animations for Safari.
Wrapping It Up
I hope this tutorial sparked your creativity and impressed you to attempt the Gooey impact in your individual initiatives. It’s a enjoyable and visually fascinating approach so as to add some character to easy elements. Thanks for following alongside—blissful coding!