In this tutorial, we’ll create a fun and interactive frying pan preloader using HTML, CSS, and a touch of JavaScript. This preloader will feature a frying pan flipping bacon-like blobs while splashing drops of grease, providing a playful loading animation for your website or application.
Let’s break down the code and understand how each component works together to create the animation.
HTML Structure
<svg class="pl" viewBox="0 0 128 128" width="128px" height="128px" role="img" aria-label="A pan being used to flip a blob resembling bacon as it splashes drops of grease in and out">
<clipPath id="pan-clip">
<rect rx="12" ry="14" x="4" y="52" width="68" height="28" />
</clipPath>
<defs>
<linearGradient id="pl-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#000" />
<stop offset="100%" stop-color="#fff" />
</linearGradient>
<mask id="pl-mask">
<rect x="0" y="0" width="88" height="80" fill="url(#pl-grad)" />
</mask>
</defs>
<g fill="currentColor">
<g fill="none" stroke-dasharray="20 221" stroke-dashoffset="20" stroke-linecap="round" stroke-width="4">
<g stroke="hsl(38,90%,50%)">
<circle class="pl__ring" cx="44" cy="40" r="35" transform="rotate(90,44,40)" />
</g>
<g stroke="hsl(8,90%,40%)" mask="url(#pl-mask)">
<circle class="pl__ring" cx="44" cy="40" r="35" transform="rotate(90,44,40)" />
</g>
</g>
<g fill="hsla(223,10%,70%,0)">
<g class="pl__drop pl__drop--1">
<circle class="pl__drop-inner" cx="13" cy="60" r="2" />
</g>
<g class="pl__drop pl__drop--2">
<circle class="pl__drop-inner" cx="13" cy="60" r="2" />
</g>
<g class="pl__drop pl__drop--3">
<circle class="pl__drop-inner" cx="67" cy="72" r="2" />
</g>
<g class="pl__drop pl__drop--4">
<circle class="pl__drop-inner" cx="67" cy="72" r="2" />
</g>
<g class="pl__drop pl__drop--5">
<circle class="pl__drop-inner" cx="67" cy="72" r="2" />
</g>
</g>
<g class="pl__pan">
<rect rx="2" ry="2" x="4" y="66" width="68" height="14" clip-path="url(#pan-clip)" id="pan" />
<rect rx="2" ry="2" x="76" y="66" width="48" height="7" />
</g>
<rect class="pl__shadow" fill="hsla(223,10%,50%,0.2)" rx="3.5" ry="3.5" x="10" y="121" width="60" height="7" />
</g>
</svg>
HTMLThe HTML structure consists of an SVG element with various nested elements like<clipPath>
,<defs>
, and graphical elements (<circle>
,<rect>
). These elements define the shapes and properties of the frying pan, bacon-like blobs, and drops of grease.
CSS Styling
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--hue: 223;
--bg: hsl(var(--hue),10%,90%);
--fg: hsl(var(--hue),10%,10%);
--trans-dur: 0.3s;
font-size: calc(14px + (30 - 14) * (100vw - 280px) / (3840 - 280));
}
body {
background-color: var(--bg);
color: var(--fg);
display: flex;
font: 1em/1.5 sans-serif;
height: 100vh;
transition:
background-color var(--trans-dur),
color var(--trans-dur);
}
$debug: false;
$size: 12em;
$ease-in: cubic-bezier(0.33,0.16,0.67,0.16);
$ease-out: cubic-bezier(0.33,0.84,0.67,0.84);
$ease-in-out: cubic-bezier(0.65,0,0.35,1);
$drop-color: hsl(223,10%,70%,1);
$drop-color-t: hsl(223,10%,70%,0);
.pl {
@if ($debug == true) {
outline: 1px solid;
}
margin: auto;
width: $size;
height: $size;
&__drop,
&__drop-inner,
&__pan,
&__ring,
&__shadow {
animation: pan 2s $ease-in-out infinite;
}
&__drop {
$drops: 5;
transform-origin: 13px 60px;
&-inner {
animation-timing-function: $ease-out;
}
@for $d from 1 through $drops {
&--#{$d} {
animation-name: drop-#{$d};
@if $d >= 3 {
transform-origin: 67px 72px;
}
}
&--#{$d} &-inner {
animation-name: drop-#{$d}-inner;
}
}
}
&__pan {
transform-origin: 36px 74px;
}
&__ring {
animation-name: flip-ring;
}
&__shadow {
animation-name: pan-shadow;
transform-origin: 36px 124.5px;
}
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(var(--hue),10%,10%);
--fg: hsl(var(--hue),10%,90%);
}
}
/* Animation */
@keyframes drop-1 {
from {
animation-timing-function: steps(1,end);
transform: translate(0,0);
visibility: hidden;
}
30% {
animation-timing-function: linear;
transform: translate(0,0);
visibility: visible;
}
50%,
to {
transform: translate(-6px,0);
}
}
@keyframes drop-1-inner {
from,
30% {
fill: $drop-color;
transform: translate(0,0);
}
50%,
to {
fill: $drop-color-t;
transform: translate(0,-27px);
}
}
@keyframes drop-2 {
from {
animation-timing-function: steps(1,end);
transform: translate(0,0);
visibility: hidden;
}
30% {
animation-timing-function: linear;
transform: translate(0,0);
visibility: visible;
}
50%,
to {
transform: translate(-8px,0);
}
}
@keyframes drop-2-inner {
from,
30% {
fill: $drop-color;
transform: translate(0,0);
}
50%,
to {
fill: $drop-color-t;
transform: translate(0,-9px);
}
}
@keyframes drop-3 {
from {
animation-timing-function: steps(1,end);
transform: translate(0,0);
visibility: hidden;
}
78% {
animation-timing-function: linear;
transform: translate(0,0);
visibility: visible;
}
98%,
to {
transform: translate(-24px,0);
}
}
@keyframes drop-3-inner {
from,
78% {
fill: $drop-color;
transform: translate(0,0);
}
98%,
to {
fill: $drop-color-t;
transform: translate(0,-28px);
}
}
@keyframes drop-4 {
from {
animation-timing-function: steps(1,end);
transform: translate(0,0);
visibility: hidden;
}
78% {
animation-timing-function: linear;
transform: translate(0,0);
visibility: visible;
}
98%,
to {
transform: translate(-8px,0);
}
}
@keyframes drop-4-inner {
from,
78% {
fill: $drop-color;
transform: translate(0,0);
}
98%,
to {
fill: $drop-color-t;
transform: translate(0,-36px);
}
}
@keyframes drop-5 {
from {
animation-timing-function: steps(1,end);
transform: translate(0,0);
visibility: hidden;
}
78% {
animation-timing-function: linear;
transform: translate(0,0);
visibility: visible;
}
98%,
to {
transform: translate(8px,0);
}
}
@keyframes drop-5-inner {
from,
78% {
fill: $drop-color;
transform: translate(0,0);
}
98%,
to {
fill: $drop-color-t;
transform: translate(0,-32px);
}
}
@keyframes flip-ring {
from,
27% {
animation-timing-function: $ease-out;
stroke-dashoffset: 20;
stroke-width: 4px;
}
53.5% {
animation-timing-function: $ease-in;
stroke-dashoffset: -100;
stroke-width: 10px;
}
80%,
to {
stroke-dashoffset: -220;
stroke-width: 4px;
}
}
@keyframes pan {
from,
88%,
to {
transform: translate(0,0) rotate(0);
}
20% {
animation-timing-function: $ease-in;
transform: translate(-5px,0) rotate(-30deg);
}
30% {
animation-timing-function: $ease-out;
transform: translate(0,0) rotate(20deg);
}
60%,
78% {
animation-timing-function: linear;
transform: translate(0,0) rotate(0);
}
81.33% {
animation-timing-function: linear;
transform: translate(0,4px) rotate(0);
}
84.67% {
animation-timing-function: linear;
transform: translate(0,-2px) rotate(0);
}
}
@keyframes pan-shadow {
from,
88%,
to {
fill: hsla(223,10%,50%,0.2);
transform: scaleX(1);
}
20% {
animation-timing-function: $ease-in;
fill: hsla(223,10%,50%,0.2);
transform: scaleX(0.77);
}
30% {
animation-timing-function: $ease-out;
fill: hsla(223,10%,50%,0.2);
transform: scaleX(1);
}
60%,
78% {
animation-timing-function: linear;
fill: hsla(223,10%,50%,0.2);
transform: scaleX(1);
}
81.33% {
animation-timing-function: linear;
fill: hsla(223,10%,50%,0.25);
transform: scaleX(0.87);
}
84.67% {
animation-timing-function: linear;
fill: hsla(223,10%,50%,0.225);
transform: scaleX(1.065);
}
}
SCSSCSS is used to style the SVG elements and define animation properties. Custom properties (--hue
,--bg
,--fg
,--trans-dur
) are utilized to control colors and transition durations, allowing for easy customization.
JavaScript (Optional)
No JavaScript code is provided in the snippet. However, you can use JavaScript to add interactivity or customize the animation further. For example, you can dynamically adjust animation timings or trigger the preloader to start and stop based on certain events.
Animation
The animation is achieved using CSS keyframes. Keyframes are defined for each animated component, such as the bacon-like blobs flipping, drops of grease splashing, and the frying pan moving.
Dark Theme Support
The preloader also supports dark mode using CSS media queries (@media (prefers-color-scheme: dark)
), ensuring a seamless experience for users in different environments.
Conclusion
In this tutorial, we explored how to create a frying pan preloader using HTML, CSS, and optionally JavaScript. By combining SVG graphics and CSS animations, we achieved a visually appealing loading animation that can be easily customized and integrated into web projects. Whether you’re designing a cooking website or simply want to add a touch of fun to your loading process, this preloader is sure to delight your users.
Happy Coding!