It's that time of the year again. Easter arrives tomorrow, and I'd like to herald its coming with a HTML animation.
This one uses no images. Just good old CSS sleight-of-hand. For the animation, we'll use jQuery UI. Is it absolutely necessary? No, but I feel like it.
The starting HTML looks like this. We have a div, id
container. Below it, we'll have a
h1 tag. All divs are set to have
red outlines. Trust me, we'll need it.
<!DOCTYPE html>
<html>
<head>
<title>Easter 2026</title>
<style>
div
{
border: 1px solid rgb(255, 0, 0);
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="https://code.jquery.com/ui/1.13.3/jquery-ui.js"></script>
<script>
</script>
</head>
<body>
<div id="container">
</div>
<h1>HAPPY EASTER</h1>
</body>
</html>
Here, we define
h1 with some aesthetics.
container is set to a small size, centered in the screen via use of
margin properties. Round corners and all, because it's cute. I also gave it a thick
orange border.
<!DOCTYPE html>
<html>
<head>
<title>Easter 2026</title>
<style>
div
{
border: 1px solid rgb(255, 0, 0);
}
h1
{
font-family: verdana;
text-align: center;
color: rgb(255, 200, 0);
}
#container
{
margin: 100px auto 0 auto;
border-radius: 50px;
border: 5px solid rgb(255, 200, 0);
width: 300px;
height: 200px;
}
</style>
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="https://code.jquery.com/ui/1.13.3/jquery-ui.js"></script>
<script>
</script>
</head>
<body>
<div id="container">
</div>
<h1>HAPPY EASTER</h1>
</body>
</html>
In container, we have two divs - ids
aura and
hills.
<div id="container">
<div id="aura">
</div>
<div id="hills">
</div>
</div>
In
hills, we have five other divs. The first three are styled using the CSS class
hill. The fourth and fifth have the ids
cross and
shadow. They contain the HTML crucfix symbol.
<div id="container">
<div id="aura" class="unrotated">
</div>
<div id="hills">
<div class="hill"></div>
<div class="hill"></div>
<div class="hill"></div>
<div id="cross">✝</div>
<div id="shadow">✝</div>
</div>
</div>
Here's some styling. We want
aura to overlap the entirety of
container. I've made it bigger than
container's width and height, offset the margins accordingly, and turned it into a circle using the
border-radius property. This isn't
strictly necessary, but we're going to rotate it later, and it's easier to gauge if
aura will still cover
container that way. As for
hills, we want it to cover
container's full width but only the bottom half. So I've set the
margin-top property accordingly. I've set
position to
relative and
z-index to a positive number to ensure that
hills always stays on top of
aura in that order. This will be very relevant later.
<style>
div
{
border: 1px solid rgb(255, 0, 0);
}
h1
{
font-family: verdana;
text-align: center;
color: rgb(255, 200, 0);
}
#container
{
margin: 100px auto 0 auto;
border-radius: 50px;
border: 5px solid rgb(255, 200, 0);
width: 300px;
height: 200px;
}
#aura
{
width: 400px;
height: 400px;
margin-top: -100px;
margin-left: -50px;
border-radius: 50%;
}
#hills
{
width: 300px;
height: 100px;
margin-top: -120px;
position: relative;
z-index: 2;
}
</style>
So far, it's all here.
Now, what happens if we style
hill this way? All
150 pixel squares with rounded corners, and rotated 45 degrees.
#hills
{
width: 300px;
height: 100px;
margin-top: -120px;
position: relative;
z-index: 2;
}
.hill
{
width: 150px;
height: 150px;
border-radius: 10px;
background-color: rgb(0, 0, 0);
transform-origin: 50% 50%;
transform: rotate(45deg);
}
</style>
This is what you should have.
We use the
nth-of-type pseudoselector because each of these will now have their own properties at this point.
#hills
{
width: 300px;
height: 100px;
margin-top: -120px;
position: relative;
z-index: 2;
}
.hill
{
width: 150px;
height: 150px;
border-radius: 10px;
background-color: rgb(0, 0, 0);
transform-origin: 50% 50%;
transform: rotate(45deg);
}
.hill:nth-of-type(1)
{
}
.hill:nth-of-type(2)
{
}
.hill:nth-of-type(3)
{
}
</style>
The first hill gets moved 50 pixels left.
.hill:nth-of-type(1)
{
margin-left: -50px;
}
.hill:nth-of-type(2)
{
}
.hill:nth-of-type(3)
{
}
So far so good.
We move the middle "hill" up and to the right. It should placed higher than the other two.
.hill:nth-of-type(1)
{
margin-left: -50px;
}
.hill:nth-of-type(2)
{
margin-top: -180px;
margin-left: 75px;
}
.hill:nth-of-type(3)
{
}
See this?
Finally, we use
margin-top and
margin-left properties to move the last "hill".
.hill:nth-of-type(1)
{
margin-left: -50px;
}
.hill:nth-of-type(2)
{
margin-top: -180px;
margin-left: 75px;
}
.hill:nth-of-type(3)
{
margin-top: -120px;
margin-left: 200px;
}
Yep. It's coming along nicely.
What's next? The cross, of course! For
cross, ensure that it's a large font size.
margin-top is the property that moves the div vertically upwards while
text-align is set to center so that it rests on top of the middle "hill".
.hill:nth-of-type(3)
{
margin-top: -120px;
margin-left: 200px;
}
#cross
{
font-size: 5em;
font-family: arial;
font-weight: bold;
margin-top: -270px;
text-align: center;
}
</style>
See? There's the cross. Where's
shadow? Look to the left "hill", it's hiding there.
What we do here is repeat the styling for
shadow, but with a slightly different
margin-top property.
#cross
{
font-size: 5em;
font-family: arial;
font-weight: bold;
margin-top: -270px;
text-align: center;
}
#shadow
{
font-size: 5em;
font-family: arial;
font-weight: bold;
margin-top: -100px;
text-align: center;
}
</style>
Now
shadow overlaps
cross, but not perfectly. So the cross beam looks thicker. But that's not a problem. The animation later will take care of this.
For
aura, we want the background to have spokes radiating from the center. So that means it's going to be a repeating conic gradient of
yellow and
orange.
#aura
{
width: 400px;
height: 400px;
margin-top: -100px;
margin-left: -50px;
border-radius: 50%;
background-image: repeating-conic-gradient(rgb(255, 255, 0) 5deg 10deg, rgb(255, 200, 0) 15deg 20deg, rgb(255, 255, 0) 25deg 30deg);
}
Now would you look at that!
At this point, remove the
red outlines. And set the
overflow property of
container to
hidden.
div
{
border: 0px solid rgb(255, 0, 0);
}
h1
{
font-family: verdana;
text-align: center;
color: rgb(255, 200, 0);
}
#container
{
margin: 100px auto 0 auto;
border-radius: 50px;
border: 5px solid rgb(255, 200, 0);
width: 300px;
height: 200px;
overflow: hidden;
}
Oh, this is looking so good.
Time to animate!
Add the classes
unrotated and
rotated. We want
aura to rotate, see? So
unrotated is the start state and
rotated is the end state. I also use the
transition property in both cases, to determine duration and style. Not a big issue, carry on.
#aura
{
width: 400px;
height: 400px;
margin-top: -100px;
margin-left: -50px;
border-radius: 50%;
background-image: repeating-conic-gradient(rgb(255, 255, 0) 5deg 10deg, rgb(255, 200, 0) 15deg 20deg, rgb(255, 255, 0) 25deg 30deg);
}
.unrotated
{
transform: rotate(0deg);
transition: 10s ease;
}
.rotated
{
transform: rotate(360deg);
transition: 10s ease;
}
#hills
{
width: 300px;
height: 100px;
margin-top: -120px;
position: relative;
z-index: 2;
}
What we'll want to do here is set the class of
aura to
unrotated.
<div id="aura" class="unrotated">
</div>
Now use the
toggleClass() method on
aura.
<script>
$(document).ready(function() {
$("#aura").toggleClass("rotated");
});
</script>
And then use jQuery UI's "puff" effect on
shadow. We'll set the animation to last 1 second, and specify that the end size of
shadow will be 250% of the original.
<script>
$(document).ready(function() {
$("#aura").toggleClass("rotated");
$("#shadow").effect( "puff", {percent: 250}, 1000 );
});
</script>
See the effect! It looks like the crucifix with a fading
black aura amid the rotating spokes.
And if you want to make this continuous, put that in a
setInterval() function.
<script>
$(document).ready(function() {
$("#aura").toggleClass("rotated");
$("#shadow").effect("puff", {percent: 250}, 1000 );
setInterval
(
function()
{
$("#aura").toggleClass("rotated");
$("#shadow").effect("puff", {percent: 250}, 1000 );
},
10000
);
});
</script>
Auran't you glad it's Easter?
T___T