Free Web Design Code & Scripts

3D Interactive Product Cards Using HTML CSS

3D Interactive Product Cards Using HTML CSS
Code Snippet:3D Product Cards
Author: Rafa
Published: 4 months ago
Last Updated: August 23, 2025
Downloads: 116
License: MIT
Edit Code online: View on CodePen
Read More

This tutorial will guide you through creating interactive 3D product cards using HTML, CSS, and JavaScript. We’ll leverage the power of model-viewer for displaying 3D models and adding engaging hover effects. The target audience for this tutorial is beginners with basic HTML, CSS, and JavaScript knowledge.

Step 1: Setting up the Project

First, you need to include the model-viewer library in your project. This is done by adding a script tag within the <head> section of your HTML document.

<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js"></script>

Step 2: Creating the HTML Structure for 3D Product Cards

Next, create the HTML structure for your 3D product cards. Each card will contain a 3D model, a visually appealing glass effect, and some product information.

<div class="card">
  <div class="overflow">
    <div class="model">
      <model-viewer src="https://assets.codepen.io/3421562/leaves_keyboard.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>LeafKey</h2>
    <p>A keyboard that brings the tranquility of the forest to your fingertips.</p>
    <div class="options">
      <div onclick="changeModelStyle(this, 0);" style="background: #f2c173;"></div>
      <div onclick="changeModelStyle(this, 60);" style="background: #96dd78;"></div>
      <div onclick="changeModelStyle(this, 110);" style="background: #6ee5bc;"></div>
    </div>
  </div>
</div>
<div class="card">
  <div class="overflow">
    <div class="model">
      <model-viewer src="https://assets.codepen.io/3421562/topo_keyboard.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>TopoKey</h2>
    <p>Custom illuminated keyboard with neon topology mapped out in every key.</p>
    <div class="options">
      <div onclick="changeModelStyle(this, 0);" style="background: #2cb4f0;"></div>
      <div onclick="changeModelStyle(this, 110);" style="background: #ff69e4;"></div>
      <div onclick="changeModelStyle(this, 290);" style="background: #16b83f;"></div>
    </div>
  </div>
</div>
<div class="card">
  <div class="overflow">
    <div class="model">
      <model-viewer src="https://assets.codepen.io/3421562/panda_keyboard.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>PandaKey</h2>
    <p>Panda, panda, panda, panda, panda, panda... panda</p>
    <div class="options">
     <div onclick="changeModelStyle(this, 0);" style="background: #1e1e1e;"></div>
      <div onclick="changeModelStyle(this, 0, 1);" style="background: #eee;"></div>
    </div>
  </div>
</div>

Step 3: Styling with CSS

Now, let’s add some CSS to style the cards and create the 3D effect. This includes styling the card itself, the glass effect, the 3D model, and the product information.

@import url('https://fonts.cdnfonts.com/css/satoshi');
*{ box-sizing: border-box; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; scroll-behavior: smooth;}
html, body { height: 100%; }
body {
    display: flex; flex-direction: row; flex-wrap: wrap; flex-wrap: nowrap;
    justify-content: center; align-items: center; margin: 0; padding-top: 120px;
    background: #ededed;
    font-family: 'Satoshi', sans-serif;

    --scale: clamp(200px, 22vw, 300px);
}

.card {
    --clr: #7da072; --fclr: #f0ffe2;
    &:nth-child(2) { --clr: #2e2e2e; --fclr: #fefefe; }
    &:nth-child(3) { --clr: #fff; --fclr: #000; }

    width: var(--scale); height: 364px;
    min-width: var(--scale);
    background: var(--clr);
    border-radius: 8px;
    box-shadow: 
    -8px -8px 24px 0 #fffc,
    1px 1px 3px 0px #0002,
    12px 42px 24px -8px #0001,
    10px 24px 42px 0 #0001,
    1px 4px 12px 0 #0002;
    &:nth-child(1) {
          box-shadow: 
    -8px -8px 24px 0 #fffc,
    1px 1px 3px 0px #7da07277,
    12px 42px 24px -8px #7da07233,
    10px 24px 42px 0 #7da07233,
    1px 4px 12px 0 #7da07244;
    } 
    &:nth-child(2) {
    box-shadow: 
      -8px -8px 24px 0 #fffc,
      1px 1px 3px 0px #0006,
      12px 42px 24px -8px #0003,
      10px 24px 42px 0 #0003,
      1px 4px 12px 0 #0006;
    }
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: end; align-items: center;
    cursor: pointer;

    margin: auto 24px;

    .content {
        position: relative;
        z-index: 1;
        bottom: 0; top: unset;
        height: fit-content;
        padding: 12px 24px; 
        h2 , p {
            margin: 0; padding: 0;
            line-height: 124%;
            margin-bottom: 8px;
            font-weight: 700;
            color: var(--fclr);
        }
        p {
            font-weight: 500;
            margin-bottom: 16px;
        }
        
        .options {
            position: absolute;
            display: flex; gap: 4px;
            bottom: 8px; right: 12px;
            > div {
                cursor: pointer;
                width: 24px; height: 16px;
                border-radius: 4px;
                background: white;
                border: 1px solid #aaa8;
                transition: border 0.1s ease-in-out;
                &:hover {
                    border: 1px solid #fff;
                }
            }
        }
    }
    .glass {
        pointer-events: none;
        position: absolute;
        bottom: 0; left: 0;
        width: 100%; height: 80%;
        background: linear-gradient(transparent, var(--clr) 80%);
        border-bottom-right-radius: 8px;
        border-bottom-left-radius: 8px;
        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(0 0, 100% 0, 100% 60%, 75% 60%, 75% 100%, 25% 100%, 25% 60%, 0 60%);
    }
    .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;
            }
        }
    }

    @media (max-width: 700px) {
        &:nth-child(3) {
            display: none;
        }
    }
    @media (max-width: 468px) {
        &:nth-child(1) {
            display: none;
        }
    }
}
@keyframes onLoad {
    to { opacity: 1; }
}


.gradient-blur {
    position: absolute;
    z-index: 1;
    height: 100%;
    inset: auto 0 0 0;
    pointer-events: none;
}
.gradient-blur:nth-child(2){
    inset: auto 0;
}
.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%;
}
.gradient-blur::before {
    content: "";
    z-index: 1;
    backdrop-filter: blur(0.5px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p1),
        rgba(0, 0, 0, 1) var(--p2),
        rgba(0, 0, 0, 1) var(--p3),
        rgba(0, 0, 0, 0) var(--p4)
    );
}
.gradient-blur > div:nth-of-type(1) {
    z-index: 2;
    backdrop-filter: blur(1px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p2),
        rgba(0, 0, 0, 1) var(--p3),
        rgba(0, 0, 0, 1) var(--p4),
        rgba(0, 0, 0, 0) var(--p5)
    );
}
.gradient-blur > div:nth-of-type(2) {
    z-index: 3;
    backdrop-filter: blur(2px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p3),
        rgba(0, 0, 0, 1) var(--p4),
        rgba(0, 0, 0, 1) var(--p5),
        rgba(0, 0, 0, 0) var(--p6)
    );
}
.gradient-blur > div:nth-of-type(3) {
    z-index: 4;
    backdrop-filter: blur(4px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p4),
        rgba(0, 0, 0, 1) var(--p5),
        rgba(0, 0, 0, 1) var(--p6),
        rgba(0, 0, 0, 0) var(--p7)
    );
}
.gradient-blur > div:nth-of-type(4) {
    z-index: 5;
    backdrop-filter: blur(8px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p5),
        rgba(0, 0, 0, 1) var(--p6),
        rgba(0, 0, 0, 1) var(--p7),
        rgba(0, 0, 0, 0) var(--p8)
    );
}
.gradient-blur > div:nth-of-type(5) {
    z-index: 6;
    backdrop-filter: blur(16px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p6),
        rgba(0, 0, 0, 1) var(--p7),
        rgba(0, 0, 0, 1) var(--p8),
        rgba(0, 0, 0, 0) var(--p9)
    );
}
.gradient-blur > div:nth-of-type(6) {
    z-index: 7;
    backdrop-filter: blur(32px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p7),
        rgba(0, 0, 0, 1) var(--p8),
        rgba(0, 0, 0, 1) var(--p9)
    );
}
.gradient-blur::after {
    content: "";
    z-index: 8;
    backdrop-filter: blur(64px);
    mask: linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0) var(--p8),
        rgba(0, 0, 0, 1) var(--p9)
    );
}

Step 4: Adding Interactivity with JavaScript

Finally, we’ll add some JavaScript to make the cards interactive. This includes adding hover effects to the 3D models and color changing options.

(() => {
    const modelViewers = document.querySelectorAll('model-viewer');
    const cards = document.querySelectorAll('.card');
    const defaultOrbit = '64deg 25deg 64m';
    const hoverOrbit = '90deg -42deg 50m';
    const defaultTarget = '8m 1m 1m';
    const hoverTarget = '4m 1m 1m';
    const applyOrbit = (modelViewer, orbit, target) => {
      modelViewer.setAttribute('camera-orbit', orbit);
      modelViewer.setAttribute('camera-target', target);
      modelViewer.setAttribute('interpolation-decay', '124');
    };
    cards.forEach((card, index) => {
      const modelViewer = modelViewers[index];
      if (modelViewer) {
        applyOrbit(modelViewer, defaultOrbit, defaultTarget);
        card.addEventListener('mouseenter', () => applyOrbit(modelViewer, hoverOrbit, hoverTarget));
        card.addEventListener('mouseleave', () => applyOrbit(modelViewer, defaultOrbit, defaultTarget));
        modelViewer.addEventListener('load', () => {
          modelViewer.classList.add('loaded');
        });
      } else {
        console.log(`No model found for card at i:${index}`);
      }
    });
})();
function changeModelStyle(element, deg, invert = 0) {
    const card = element.closest('.card');
    const modelViewer = card.querySelector('model-viewer');
    if (modelViewer) { modelViewer.style.filter = `hue-rotate(${deg}deg) invert(${invert})`; }
}

Congratulations! You’ve successfully created interactive 3D product cards using HTML, CSS and JavaScript. Remember to replace placeholder image and model URLs with your own assets.

Related Code Snippets:

Loading... ...

Loading preview...

Device: Desktop
Dimensions: 1200x800
Lines: 0 Characters: 0 Ln 1, Ch 1

Leave a Comment

About W3Frontend

W3Frontend provides free, open-source web design code and scripts to help developers and designers build faster. Every snippet is reviewed before publishing for quality. Learn more.