DEV Community

Cover image for Create 3D Model Card Hover Effect using HTML CSS and Javascript
Codebox
Codebox

Posted on

Create 3D Model Card Hover Effect using HTML CSS and Javascript

Introduction

In modern web design, interactivity plays a crucial role in enhancing user engagement and experience. To make your website stand out, try incorporating amazing hover effects.

A 3D model card is a sleek way to give depth and interactive features to your website.

In this tutorial, we’ll explain the complete steps on how to create a 3D model card hover effect using only HTML, CSS, and Javascript.

Whether you're working on a portfolio, e-commerce site, or personal project, this effect will boost your web design.

You May Also Like: Autoplay Image Slider with Manual Navigation Button with HTML CSS and JS

Watch Live Preview 👉👉

Click Here to Download Source Code

Prerequisites

Before diving into the code, it's important to have a basic understanding of the following concepts:

  • HTML: The structure of your web page.
  • CSS: The styling and layout of your web page.
  • CSS Transitions and Transforms: We'll use for smooth animations and transformations.
  • CSS Pseudo-classes: These allow you to apply styles based on the state of an element (e.g., :hover).

With that in mind, let’s begin.

Alert: To run the file, you'll need to start the XAMPP server

Source Code

HTML Code Structure

<!DOCTYPE html>
<html lang="en" >
<!-- Code by Keyframe Tech Solution -->
<head>
    <meta charset="UTF-8">
    <title>Marvel Character Select</title>
    <script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js"></script>
    <link rel="stylesheet" href="./style.css">
</head>
<body>

<div class="card-container">
    <div class="card">
        <div class="overflow">
            <div class="model">
                <model-viewer camera-orbit="0deg 90deg 164m" camera-target="0m 140m 10m" src="models/spiderman.glb" shadow-intensity="0.4"></model-viewer>
            </div>
        </div>
        <div class="glass">
            <div class="gradient-blur">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>
        <div class="content">
            <h2>Spider-Man</h2>
            <p>Spider-Man is a superhero in American comic books published by Marvel Comics.</p>
        </div>
    </div>
</div>

<script  src="./script.js"></script>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

CSS Code Structure

@import url("https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400..700&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900&display=swap");
* {
    box-sizing: border-box;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    scroll-behavior: smooth;
}
html,
body {
    margin: 0;
    padding: 0;
    height: 100%;
    overflow: hidden;
}
body {
    background: #0096885c;
    --scale: 230px;
    position: relative;
}
body::before {
    content: "";
    width: 100%;
    height: 100%;
    position: absolute;
    background: radial-gradient(circle, #2d2d2d 20%, transparent 11%);
    background-size: 0.5em 0.5em;
    opacity: 0.5;
    inset: 0;
    z-index: 0;
}
.card-container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    flex-wrap: nowrap;
    justify-content: center;
    align-items: center;
    margin: 0;
    padding-top: 28vh;
    padding-bottom: 14vh;
    overflow: hidden;
}
@media (max-width: 800px) {
    html,
    body {
        overflow: inherit;
    }
    .card-container {
        flex-direction: column;
        gap: 80px;
        height: fit-content;
    }
}

.card {
    --clr: #c63e3c;
    --fclr: #f0ffe2;

    width: var(--scale);
    height: 324px;
    min-width: var(--scale);

    &::before {
        content: "";
        display: block;
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        width: 100%;
        height: 107%;
        background: linear-gradient(var(--clr), #000000 100%);
        transform: skewY(10deg);
        box-shadow: 0 0 0 0px #f9da27, 1px 1px 3px 0px #0002,
            12px 42px 24px -8px #0001, 10px 24px 42px 0 #0001, 1px 4px 12px 0 #0002;

        transition: all 0.2s ease-in-out;
    }

    &::after {
        content: "";
        display: block;
        position: absolute;
        left: -6%;
        right: 0;
        top: 64%;
        width: 112%;
        height: 16%;
        background: var(--clr);
        transform: rotate(-8deg) skewX(-54deg);
        clip-path: polygon(
            -100% -100%,
            200% -100%,
            200% 200%,
            130% 200%,
            59% -100%,
            -30.4% -100%,
            41% 200%,
            -100% 200%
        );

        box-shadow: 0 0 0 0px #f9da27;
        scale: 0.6;
        opacity: 0;
        transition: all 0.2s ease-in-out;
    }

    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: end;
    align-items: center;
    cursor: pointer;

    margin: auto 16px;

    &:hover {
        z-index: 2;

        &::before {
            box-shadow: 0 0 0 8px #234563, 1px 1px 3px 0px #0002,
                12px 42px 24px -8px #0001, 10px 24px 42px 0 #0001, 1px 4px 12px 0 #0002;
        }
    }

    .content {
        position: relative;
        z-index: 1;
        bottom: 0;
        top: unset;
        height: fit-content;
        padding: 12px 24px 0 24px;
        text-align: center;
        h2,
        p {
            margin: 0;
            padding: 0;
            line-height: 124%;
            margin-bottom: 8px;
            font-weight: 700;
            color: var(--fclr);
        }
        p {
            font-size: 14px;
            font-weight: 400;
            margin-bottom: 16px;
            font-family: "Roboto", serif;
        }
        h2{
            font-size: 30px;
            font-family: "Dancing Script", serif;
        }
    }

    .glass {
        pointer-events: none;
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 80%;
        background: linear-gradient(transparent, #000 100%);
        overflow: hidden;
    }
    .overflow {
        pointer-events: none;
        position: absolute;
        width: 200%;
        height: 200%;
        overflow: hidden;
        display: flex;
        justify-content: end;
        align-items: end;
        clip-path: polygon(25% 0, 75% 0, 75% 100%, 25% 100%);
    }
    .model {
        position: absolute;
        width: 100%;
        height: 100%;
        bottom: -10%;
        display: flex;
        justify-content: center;
        align-items: center;
        model-viewer {
            position: absolute;
            width: 500px;
            height: 500px;
            width: 500px;
            height: calc((5 / 3) * var(--scale));
            --progress-bar-color: none;
            --progress-bar-height: 0px;
            opacity: 0;
            transition: filter 0.4s ease-in-out;
            &.loaded {
                animation: onLoad 1s ease-in forwards;
            }
        }
    }
}
.card:hover .overflow {
    clip-path: unset !important;
}
@keyframes onLoad {
    to {
        opacity: 1;
    }
}

.gradient-blur {
    position: absolute;
    z-index: 1;
    height: 100%;
    inset: auto 0 0 0;
    pointer-events: none;
}
.gradient-blur > div,
.gradient-blur::before,
.gradient-blur::after {
    position: absolute;
    inset: 0;
    --p1: 0%;
    --p2: 12.5%;
    --p3: 25%;
    --p4: 37.5%;
    --p5: 50%;
    --p6: 62.5%;
    --p7: 75%;
    --p8: 87.5%;
    --p9: 100%;
    --bclr0: #0000;
    --bclr1: #000;
}
.gradient-blur::before {
    content: "";
    z-index: 1;
    backdrop-filter: blur(0.5px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p1),
        var(--bclr1) var(--p2),
        var(--bclr1) var(--p3),
        var(--bclr0) var(--p4)
    );
}
.gradient-blur > div:nth-of-type(1) {
    z-index: 2;
    backdrop-filter: blur(1px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p2),
        var(--bclr1) var(--p3),
        var(--bclr1) var(--p4),
        var(--bclr0) var(--p5)
    );
}
.gradient-blur > div:nth-of-type(2) {
    z-index: 3;
    backdrop-filter: blur(2px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p3),
        var(--bclr1) var(--p4),
        var(--bclr1) var(--p5),
        var(--bclr0) var(--p6)
    );
}
.gradient-blur > div:nth-of-type(3) {
    z-index: 4;
    backdrop-filter: blur(4px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p4),
        var(--bclr1) var(--p5),
        var(--bclr1) var(--p6),
        var(--bclr0) var(--p7)
    );
}
.gradient-blur > div:nth-of-type(4) {
    z-index: 5;
    backdrop-filter: blur(8px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p5),
        var(--bclr1) var(--p6),
        var(--bclr1) var(--p7),
        var(--bclr0) var(--p8)
    );
}
.gradient-blur > div:nth-of-type(5) {
    z-index: 6;
    backdrop-filter: blur(16px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p6),
        var(--bclr1) var(--p7),
        var(--bclr1) var(--p8),
        var(--bclr0) var(--p9)
    );
}
.gradient-blur > div:nth-of-type(6) {
    z-index: 7;
    backdrop-filter: blur(32px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p7),
        var(--bclr1) var(--p8),
        var(--bclr1) var(--p9)
    );
}
.gradient-blur::after {
    content: "";
    z-index: 8;
    backdrop-filter: blur(64px);
    mask: linear-gradient(
        to bottom,
        var(--bclr0) var(--p8),
        var(--bclr1) var(--p9)
    );
}
Enter fullscreen mode Exit fullscreen mode

JavaScript Code Structure

(() => {
    const modelViewers = document.querySelectorAll("model-viewer");
    const cards = document.querySelectorAll(".card");
    const defaultOrbit = "0deg 90deg 164m";
    const applyOrbit = (modelViewer, orbit, vert) => {
        modelViewer.setAttribute("camera-orbit", orbit);
        modelViewer.setAttribute("interpolation-decay", "200");
        modelViewer.setAttribute("camera-target", `0m ${vert}m 10m`);
    };
    cards.forEach((card, index) => {
        const modelViewer = modelViewers[index];
        if (modelViewer) {
            const calcOrbit = (xPos, cardRect) => {
                const normalizedX = (xPos - cardRect.left) / cardRect.width;
                const angle = normalizedX * 90 - 45;
                return `${-angle}deg 90deg 164m`;
            };
            card.addEventListener("mousemove", (event) => {
                const cardRect = card.getBoundingClientRect();
                const orbit = calcOrbit(event.clientX, cardRect);
                applyOrbit(modelViewer, orbit, 120);
            });
            card.addEventListener("mouseleave", () => {
                applyOrbit(modelViewer, defaultOrbit, 140);
            });
            modelViewer.addEventListener("load", () => {
                modelViewer.classList.add("loaded");
            });
        } else {
            console.log(`No model found for card at i:${index}`);
        }
    });
})();
Enter fullscreen mode Exit fullscreen mode

You May Also Like: How to Build Vertical Timeline Design using HTML CSS & jQuery

Testing and Debugging

Before deploying, it’s crucial to test your card hover effect in various browsers and on different devices to ensure smooth performance.

Make sure the animations are responsive and that the hover effect works as expected.

Conclusion

You’ve now created a 3D model card hover effect.

By using simple but powerful properties like perspective, transform, and transition, this technique creates an interactive and engaging user experience

You can easily adapt and customize this effect to suit your specific needs, such as adding more content, tweaking the design, or experimenting with different animations.

By mastering hover effects, you can add depth and interactivity to your websites, making them visually appealing and more engaging to users.

Happy coding!

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs