Building an Animated 3D Loader with HTML and CSS || FREE Source Code

Aarzoo IslamAarzoo Islam
5 min read

In the world of web development, creating engaging loading animations is crucial to enhance user experience. In this article, we'll explore how to build an animated 3D loader using HTML and CSS. This loader will not only keep users entertained while waiting for content to load but also add a touch of creativity to your website.

Project Overview

The animated 3D loader we're going to build consists of a series of dots arranged in a circular pattern. These dots will animate in a visually appealing manner, giving the impression of a loading sequence. Additionally, there will be accompanying text indicating the loading activity, adding clarity to the process.

HTML Structure

<!DOCTYPE html>
<html lang="en">

<head>
    <!-- Define the character encoding for the document -->
    <meta charset="UTF-8">
    <!-- Specify the latest version of Internet Explorer -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!-- Set the viewport width to the width of the device and initial scale to 1.0 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Link to an external stylesheet -->
    <link rel="stylesheet" href="style.css">
    <!-- Title of the webpage -->
    <title>Animated 3D Loader</title>
</head>

<body>
    <!-- Container for the animated loader -->
    <div class="pl">
        <!-- Individual dots for the loader animation -->
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <div class="pl__dot"></div>
        <!-- Text indicating loading status -->
        <div class="pl__text">Loading…</div>
    </div>
</body>

</html>

CSS Styles

/* 
  Resetting default styles for all elements to ensure consistent styling across browsers
*/
* {
    border: 0;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

/* 
  Setting up custom variables for colors, transitions, and font sizes
*/
:root {
    --bg: #032241;
    --fg: #e3e4e8;
    --fg-t: rgba(227, 228, 232, 0.5);
    --primary1: #00509f;
    --primary2: #0b42cb;
    --trans-dur: 0.3s;
    font-size: calc(16px + (20 - 16) * (100vw - 320px) / (1280 - 320));
}

/* 
  Global styles for the body
*/
body {
    background-color: var(--bg);
    background-image: linear-gradient(135deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.2));
    color: var(--fg);
    font: 1em/1.5 "Varela Round", Helvetica, sans-serif;
    height: 100vh;
    min-height: 360px;
    display: grid;
    place-items: center;
    transition: background-color var(--trans-dur), color var(--trans-dur);
}

/* 
  Styles for the placeholder element (.pl)
*/
.pl {
    box-shadow: 2em 0 2em rgba(0, 0, 0, 0.2) inset, -2em 0 2em rgba(255, 255, 255, 0.1) inset;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    transform: rotateX(30deg) rotateZ(45deg);
    width: 15em;
    height: 15em;
}

/* 
  Styles for the dots within the placeholder (.pl__dot)
*/
.pl,
.pl__dot {
    border-radius: 50%;
}

.pl__dot {
    animation-name: shadow;
    box-shadow: 0.1em 0.1em 0 0.1em black, 0.3em 0 0.3em rgba(0, 0, 0, 0.5);
    top: calc(50% - 0.75em);
    left: calc(50% - 0.75em);
    width: 1.5em;
    height: 1.5em;
}

.pl__dot,
.pl__dot:before,
.pl__dot:after {
    animation-duration: 2s;
    animation-iteration-count: infinite;
    position: absolute;
}

.pl__dot:before,
.pl__dot:after {
    content: "";
    display: block;
    left: 0;
    width: inherit;
    transition: background-color var(--trans-dur);
}

.pl__dot:before {
    animation-name: pushInOut1;
    background-color: var(--bg);
    border-radius: inherit;
    box-shadow: 0.05em 0 0.1em rgba(255, 255, 255, 0.2) inset;
    height: inherit;
    z-index: 1;
}

.pl__dot:after {
    animation-name: pushInOut2;
    background-color: var(--primary1);
    border-radius: 0.75em;
    box-shadow: 0.1em 0.3em 0.2em rgba(255, 255, 255, 0.4) inset, 0 -0.4em 0.2em #2e3138 inset, 0 -1em 0.25em rgba(0, 0, 0, 0.3) inset;
    bottom: 0;
    clip-path: polygon(0 75%, 100% 75%, 100% 100%, 0 100%);
    height: 3em;
    transform: rotate(-45deg);
    transform-origin: 50% 2.25em;
}

.pl__dot:nth-child(1) {
    transform: rotate(0deg) translateX(5em) rotate(0deg);
    z-index: 5;
}

.pl__dot:nth-child(1),
.pl__dot:nth-child(1):before,
.pl__dot:nth-child(1):after {
    animation-delay: 0s;
}

.pl__dot:nth-child(2) {
    transform: rotate(-30deg) translateX(5em) rotate(30deg);
    z-index: 4;
}

.pl__dot:nth-child(2),
.pl__dot:nth-child(2):before,
.pl__dot:nth-child(2):after {
    animation-delay: -0.1666666667s;
}

.pl__dot:nth-child(3) {
    transform: rotate(-60deg) translateX(5em) rotate(60deg);
    z-index: 3;
}

.pl__dot:nth-child(3),
.pl__dot:nth-child(3):before,
.pl__dot:nth-child(3):after {
    animation-delay: -0.3333333333s;
}

.pl__dot:nth-child(4) {
    transform: rotate(-90deg) translateX(5em) rotate(90deg);
    z-index: 2;
}

.pl__dot:nth-child(4),
.pl__dot:nth-child(4):before,
.pl__dot:nth-child(4):after {
    animation-delay: -0.5s;
}

.pl__dot:nth-child(5) {
    transform: rotate(-120deg) translateX(5em) rotate(120deg);
    z-index: 1;
}

.pl__dot:nth-child(5),
.pl__dot:nth-child(5):before,
.pl__dot:nth-child(5):after {
    animation-delay: -0.6666666667s;
}

.pl__dot:nth-child(6) {
    transform: rotate(-150deg) translateX(5em) rotate(150deg);
    z-index: 1;
}

.pl__dot:nth-child(6),
.pl__dot:nth-child(6):before,
.pl__dot:nth-child(6):after {
    animation-delay: -0.8333333333s;
}

.pl__dot:nth-child(7) {
    transform: rotate(-180deg) translateX(5em) rotate(180deg);
    z-index: 2;
}

.pl__dot:nth-child(7),
.pl__dot:nth-child(7):before,
.pl__dot:nth-child(7):after {
    animation-delay: -1s;
}

.pl__dot:nth-child(8) {
    transform: rotate(-210deg) translateX(5em) rotate(210deg);
    z-index: 3;
}

.pl__dot:nth-child(8),
.pl__dot:nth-child(8):before,
.pl__dot:nth-child(8):after {
    animation-delay: -1.1666666667s;
}

.pl__dot:nth-child(9) {
    transform: rotate(-240deg) translateX(5em) rotate(240deg);
    z-index: 4;
}

.pl__dot:nth-child(9),
.pl__dot:nth-child(9):before,
.pl__dot:nth-child(9):after {
    animation-delay: -1.3333333333s;
}

.pl__dot:nth-child(10) {
    transform: rotate(-270deg) translateX(5em) rotate(270deg);
    z-index: 5;
}

.pl__dot:nth-child(10),
.pl__dot:nth-child(10):before,
.pl__dot:nth-child(10):after {
    animation-delay: -1.5s;
}

.pl__dot:nth-child(11) {
    transform: rotate(-300deg) translateX(5em) rotate(300deg);
    z-index: 6;
}

.pl__dot:nth-child(11),
.pl__dot:nth-child(11):before,
.pl__dot:nth-child(11):after {
    animation-delay: -1.6666666667s;
}

.pl__dot:nth-child(12) {
    transform: rotate(-330deg) translateX(5em) rotate(330deg);
    z-index: 6;
}

.pl__dot:nth-child(12),
.pl__dot:nth-child(12):before,
.pl__dot:nth-child(12):after {
    animation-delay: -1.8333333333s;
}

/* 
  Styles for the text within the placeholder (.pl__text)
*/
.pl__text {
    font-size: 0.75em;
    max-width: 5rem;
    position: relative;
    text-shadow: 0 0 0.1em var(--fg-t);
    transform: rotateZ(-45deg);
}

/* 
  Keyframe animations
*/
@keyframes shadow {
    from {
        animation-timing-function: ease-in;
        box-shadow: 0.1em 0.1em 0 0.1em black, 0.3em 0 0.3em rgba(0, 0, 0, 0.3);
    }

    25% {
        animation-timing-function: ease-out;
        box-shadow: 0.1em 0.1em 0 0.1em black, 0.8em 0 0.8em rgba(0, 0, 0, 0.5);
    }

    50%,
    to {
        box-shadow: 0.1em 0.1em 0 0.1em black, 0.3em 0 0.3em rgba(0, 0, 0, 0.3);
    }
}

@keyframes pushInOut1 {
    from {
        animation-timing-function: ease-in;
        background-color: var(--bg);
        transform: translate(0, 0);
    }

    25% {
        animation-timing-function: ease-out;
        background-color: var(--primary2);
        transform: translate(-71%, -71%);
    }

    50%,
    to {
        background-color: var(--bg);
        transform: translate(0, 0);
    }
}

@keyframes pushInOut2 {
    from {
        animation-timing-function: ease-in;
        background-color: var(--bg);
        clip-path: polygon(0 75%, 100% 75%, 100% 100%, 0 100%);
    }

    25% {
        animation-timing-function: ease-out;
        background-color: var(--primary1);
        clip-path: polygon(0 25%, 100% 25%, 100% 100%, 0 100%);
    }

    50%,
    to {
        background-color: var(--bg);
        clip-path: polygon(0 75%, 100% 75%, 100% 100%, 0 100%);
    }
}

Implementation

The provided HTML and CSS code form the foundation of our animated 3D loader. To see it in action, simply copy the code into your project files and open the HTML file in a web browser.

Preview

Download

If you want to download the code, please click here.

Contact Me

For any inquiries or collaborations, feel free to reach out to me here. Let's create something amazing together!


This project is part of my #100DaysOfCode challenge, and it was created on day 4. Follow my coding journey on Twitter for more updates and projects!

1
Subscribe to my newsletter

Read articles from Aarzoo Islam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aarzoo Islam
Aarzoo Islam

Founder of AroNus πŸš€ | Full-stack web developer πŸ‘¨πŸ»β€πŸ’» | Sharing web development tips and showcasing projects πŸ“‚ | Transforming ideas into digital realities 🌟