前端嘛 Logo
前端嘛
CSS实现3D国际象棋模型

CSS实现3D国际象棋模型

2025-11-26

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>CSS实现3D国际象棋模型</title>
    <style>
      body {
        margin: 0;
        height: 100dvh;
        overflow: hidden;
        perspective: 100vmin;
        display: grid;
        place-items: center;
        background: radial-gradient(circle, #1d1e22, #1c1c1c);
      }
      body *,
      body *::before,
      body *::after {
        transform-style: preserve-3d;
      }  
chessboard {
    --dark: #3a3634;
    --light: #eacba8;
    --black: #333333;
    --white: #f8d6a2;
    animation: rotateBoard 20s linear infinite;
    display: grid;
    width: 65vmin;
    aspect-ratio: 1/1;
    grid-auto-rows: 1fr;
    grid-auto-columns: 1fr;
    grid-template-areas:
      'a8 b8 c8 d8 e8 f8 g8 h8'
      'a7 b7 c7 d7 e7 f7 g7 h7'
      'a6 b6 c6 d6 e6 f6 g6 h6'
      'a5 b5 c5 d5 e5 f5 g5 h5'
      'a4 b4 c4 d4 e4 f4 g4 h4'
      'a3 b3 c3 d3 e3 f3 g3 h3'
      'a2 b2 c2 d2 e2 f2 g2 h2'
      'a1 b1 c1 d1 e1 f1 g1 h1';
    place-items: center;
    background: 0 0 / 25% 25%
      repeating-conic-gradient(var(--dark) 0 25%, var(--light) 25% 50%);
    transform: rotateX(75deg);
    position: relative;
    container-type: inline-size;
    &::after {
      content: '';
      position: absolute;
      inset: -50%;
      border-radius: 50%;
      mask-image: radial-gradient(black, transparent 50%);
      background: 0 0 / 12.5% 12.5%
        repeating-conic-gradient(var(--dark) 0 25%, var(--light) 25% 50%);
      transform: translateZ(-5cqi);
    }
    > sides {
      display: contents;
      > x {
        position: absolute;
        inset: var(--i);
        transform-origin: var(--o);
        transform: rotateX(var(--x, 0deg)) rotateY(var(--y, 0deg));
        background: linear-gradient(hsl(0 0 0 / var(--l))),
          repeating-linear-gradient(
            var(--d),
            var(--dark) 0 12.5%,
            var(--light) 0% 25%
          );
        &[f] {
          --i: 100% 0 -5% 0;
          --o: 50% 0%;
          --d: 90deg;
          --x: -90deg;
          --l: 0.5;
        }
        &[b] {
          --i: -5% 0 100% 0;
          --o: 50% 100%;
          --d: 270deg;
          --x: 90deg;
          --l: 0.5;
        }
        &[l] {
          --i: 0 100% 0 -5%;
          --o: 100% 50%;
          --d: 0deg;
          --y: -90deg;
          --l: 0.25;
        }
        &[r] {
          --i: 0 -5% 0 100%;
          --o: 0% 50%;
          --d: 180deg;
          --y: 90deg;
          --l: 0.25;
        }
      }
    }
    > piece {
      --s: 7cqi;
      --band-gradient: #0000 calc(100% - var(--s) * 0.2),
        var(--clr-opp) 0 calc(100% - var(--s) * 0.15), #0000 0;
      grid-area: attr(pos type(<custom-ident>));
      width: var(--s);
      aspect-ratio: 1/1;
      position: relative;
      background: var(--clr);
      &[white] {
        --clr: var(--white);
        --clr-opp: var(--black);
      }
      &[black] {
        --clr: var(--black);
        --clr-opp: var(--white);
        transform: rotate(180deg);
      }

      &[pawn] {
        &::before,
        &::after,
        > x::before,
        x::after {
          content: '';
          position: absolute;
          inset: 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(var(--bd), var(--band-gradient)), var(--clr);
          transform-origin: var(--to);
          transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
        }
        &::before {
          --bd: 180deg;
          --to: bottom;
          --rx: -90deg;
        }
        &::after {
          --bd: 0deg;
          --to: top;
          --rx: 90deg;
        }
        > x {
          position: absolute;
          inset: 0;
          background: var(--clr);
          transform: translateZ(var(--s));
        }
        > x::before {
          --bd: 90deg;
          --to: left;
          --ry: 90deg;
        }
        > x::after {
          --bd: -90deg;
          --to: right;
          --ry: -90deg;
        }
      }

      &[rook] {
        &::before,
        &::after,
        > x::before,
        > x::after {
          content: '';
          position: absolute;
          inset: var(--in);
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(var(--bd), var(--band-gradient)), var(--clr);
          transform-origin: var(--to);
          transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
        }
        &::before {
          --in: -100% 0 0 0;
          --bd: 180deg;
          --to: bottom;
          --rx: -90deg;
        }
        &::after {
          --in: 0 0 -100% 0;
          --bd: 0deg;
          --to: top;
          --rx: 90deg;
        }
        > x {
          position: absolute;
          inset: 0;
          background: var(--clr);
          transform: translateZ(calc(var(--s) * 2));
        }
        > x::before {
          --in: 0 -100% 0 0;
          --bd: 90deg;
          --to: left;
          --ry: 90deg;
        }
        > x::after {
          --in: 0 0 0 -100%;
          --bd: -90deg;
          --to: right;
          --ry: -90deg;
        }
      }
      &[knight] {
        &::before {
          /* front */
          content: '';
          position: absolute;
          inset: calc(var(--s) * -1) 0 0 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(var(--band-gradient)) var(--clr);
          transform-origin: bottom;
          transform: rotateX(-90deg);
        }
        &::after {
          /* back */
          content: '';
          position: absolute;
          inset: 0 0 0 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(to top, var(--band-gradient)) var(--clr);
          transform-origin: top;
          transform: rotateX(90deg);
        }
        > x {
          /* top */
          position: absolute;
          inset: 50% 0 0 0;
          background: var(--clr);
          transform: translateZ(calc(var(--s) * 2));
          &::before {
            /* left */
            content: '';
            position: absolute;
            inset: -100% -100% 0 0;
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to right, var(--band-gradient)), var(--clr);
            transform-origin: left;
            transform: rotateY(90deg);
            clip-path: polygon(
              0 50%,
              0 100%,
              100% 100%,
              100% 0%,
              50% 0,
              50% 50%
            );
          }
          &::after {
            /* right */
            content: '';
            position: absolute;
            inset: -100% 0 0 -100%;
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to left, var(--band-gradient)), var(--clr);
            transform-origin: right;
            transform: rotateY(-90deg);
            clip-path: polygon(
              0 0,
              0 100%,
              100% 100%,
              100% 50%,
              50% 50%,
              50% 0
            );
          }
        }
        > y {
          position: absolute;
          inset: 0 0 50% 0;
          background: var(--clr);
          transform: translateZ(var(--s));
          &::before {
            content: '';
            position: absolute;
            inset: -100% 0 0 0;
            transform-origin: bottom;
            transform: rotateX(-90deg);
            background: linear-gradient(hsl(0 0 0 / 0.2) 0 0), var(--clr);
          }
        }
      }
      &[bishop] {
        &::before {
          /* front */
          content: '';
          position: absolute;
          inset: calc(var(--s) * -1) 0 0 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(var(--band-gradient)), var(--clr);
          transform-origin: bottom;
          transform: rotateX(-90deg);
        }
        &::after {
          /* back */
          content: '';
          position: absolute;
          inset: 0 0 0 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(to top, var(--band-gradient)), var(--clr);
          transform-origin: top;
          transform: rotateX(90deg);
        }
        > x {
          /* top */
          position: absolute;
          inset: 100% 0 0 0;
          background: var(--clr);
          transform: translateZ(calc(var(--s) * 2));
          &::before {
            /* left */
            content: '';
            position: absolute;
            inset: calc(var(--s) * -1) calc(var(--s) * -1) 0 0;
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to right, var(--band-gradient)), var(--clr);
            transform-origin: left;
            transform: rotateY(90deg);
            clip-path: polygon(0 100%, 100% 100%, 100% 0%, 50% 0);
          }
          &::after {
            /* right */
            content: '';
            position: absolute;
            inset: calc(var(--s) * -1) 0 0 calc(var(--s) * -1);
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to left, var(--band-gradient)), var(--clr);
            transform-origin: right;
            transform: rotateY(-90deg);
            clip-path: polygon(0 0, 0 100%, 100% 100%, 50% 0);
          }
        }
        > y {
          position: absolute;
          inset: calc(var(--s) / -2.3) 0 0 0;
          background: var(--clr);
          transform: translateZ(calc(var(--s) * 2)) rotateX(45deg);
          transform-origin: bottom;
          background: var(--clr);
        }
      }
      &[queen] {
        &::before {
          /* front */
          content: '';
          position: absolute;
          inset: -125% 0 0 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(var(--band-gradient)), var(--clr);
          transform-origin: bottom;
          transform: rotateX(-90deg);
          clip-path: polygon(0 0, 50% 20%, 100% 0, 100% 100%, 0 100%);
        }
        &::after {
          /* back */
          content: '';
          position: absolute;
          inset: 0 0 -125% 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(to top, var(--band-gradient)), var(--clr);
          transform-origin: top;
          transform: rotateX(90deg);
          clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 80%, 0 100%);
        }
        > x {
          position: absolute;
          inset: 0;

          &::before {
            content: '';
            position: absolute;
            inset: 0 -125% 0 0;
            transform-origin: left;
            transform: rotateY(-90deg);
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to left, var(--band-gradient)), var(--clr);
          }
          &::after {
            content: '';
            position: absolute;
            inset: 0 0 0 -125%;
            transform-origin: right;
            transform: rotateY(90deg);
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to right, var(--band-gradient)), var(--clr);
          }
        }
        > y {
          position: absolute;
          inset: 0;
          transform: translateZ(calc(var(--s) * 2.25));
          &::before {
            content: '';
            position: absolute;
            inset: 0;
            background: blue;
            transform-origin: left;
            transform: rotateY(45deg);
            background: var(--clr);
          }
          &::after {
            content: '';
            position: absolute;
            inset: 0;
            background: green;
            transform-origin: right;
            transform: rotateY(-45deg);
            background: var(--clr);
          }
        }
      }
      &[king] {
        &::before {
          /* front */
          content: '';
          position: absolute;
          inset: -175% 0 0 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(var(--band-gradient)), var(--clr);
          transform-origin: bottom;
          transform: rotateX(-90deg);
          clip-path: polygon(0 20%, 50% 0%, 100% 20%, 100% 100%, 0 100%);
        }
        &::after {
          /* back */
          content: '';
          position: absolute;
          inset: 0 0 -175% 0;
          background: linear-gradient(hsl(0 0 0 / 0.2) 0 0),
            linear-gradient(to top, var(--band-gradient)), var(--clr);
          transform-origin: top;
          transform: rotateX(90deg);
          clip-path: polygon(0 0, 100% 0, 100% 80%, 50% 100%, 0 80%);
        }
        > x {
          position: absolute;
          inset: 0;

          &::before {
            content: '';
            position: absolute;
            inset: 0 -125% 0 0;
            transform-origin: left;
            transform: rotateY(-90deg);
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to left, var(--band-gradient)), var(--clr);
          }
          &::after {
            content: '';
            position: absolute;
            inset: 0 0 0 -125%;
            transform-origin: right;
            transform: rotateY(90deg);
            background: linear-gradient(hsl(0 0 0 / 0.1) 0 0),
              linear-gradient(to right, var(--band-gradient)), var(--clr);
          }
        }
        > y {
          position: absolute;
          inset: 0;
          transform: translateZ(calc(var(--s) * 2.25));
          &::before {
            content: '';
            position: absolute;
            inset: 0 calc(var(--s) * 0.29) 0 0;
            background: blue;
            transform-origin: left;
            transform: rotateY(-45deg);
            background: var(--clr);
          }
          &::after {
            content: '';
            position: absolute;
            inset: 0 0 0 calc(var(--s) * 0.29);
            background: green;
            transform-origin: right;
            transform: rotateY(45deg);
            background: var(--clr);
          }
        }
      }
    }
  }

  label {
    font-family: system-ui, sans-serif;
    position: fixed;
    top: 1em;
    right: 1em;
    color: white;
  }
  body:not(:has(input:checked)) > chessboard {
    animation-play-state: paused;
  }
  @keyframes rotateBoard {
    to {
      transform: rotateX(75deg) rotateZ(360deg);
    }
  }
  @keyframes piece-jump {
    4% {
      translate: 0 0 5cqi;
    }
    8% {
      translate: 0;
    }
  }
</style>
 </head>

  <body>
    <chessboard>
      <piece white rook pos="a1"><x></x></piece>
      <piece white knight pos="b1"><x></x><y></y></piece>
      <piece white bishop pos="c1"><x></x><y></y></piece>
      <piece white queen pos="d1"><x></x><y></y></piece>
      <piece white king pos="e1"><x></x><y></y></piece>
      <piece white bishop pos="f1"><x></x><y></y></piece>
      <piece white knight pos="g1"><x></x><y></y></piece>
      <piece white rook pos="h1"><x></x></piece>
  <piece white pawn pos="a2"><x></x></piece>
  <piece white pawn pos="b2"><x></x></piece>
  <piece white pawn pos="c2"><x></x></piece>
  <piece white pawn pos="d2"><x></x></piece>
  <piece white pawn pos="e2"><x></x></piece>
  <piece white pawn pos="f2"><x></x></piece>
  <piece white pawn pos="g2"><x></x></piece>
  <piece white pawn pos="h2"><x></x></piece>

  <piece black pawn pos="a7"><x></x></piece>
  <piece black pawn pos="b7"><x></x></piece>
  <piece black pawn pos="c7"><x></x></piece>
  <piece black pawn pos="d7"><x></x></piece>
  <piece black pawn pos="e7"><x></x></piece>
  <piece black pawn pos="f7"><x></x></piece>
  <piece black pawn pos="g7"><x></x></piece>
  <piece black pawn pos="h7"><x></x></piece>

  <piece black rook pos="a8"><x></x></piece>
  <piece black knight pos="b8"><x></x><y></y></piece>
  <piece black bishop pos="c8"><x></x><y></y></piece>
  <piece black queen pos="d8"><x></x><y></y></piece>
  <piece black king pos="e8"><x></x><y></y></piece>
  <piece black bishop pos="f8"><x></x><y></y></piece>
  <piece black knight pos="g8"><x></x><y></y></piece>
  <piece black rook pos="h8"><x></x></piece>

  <sides><x f></x><x b></x><x l></x><x r></x></sides>
</chessboard>

<label for="rotate"
  ><span>旋转</span><input type="checkbox" id="rotate" checked
/></label>
  </body>
</html>