How to Create a Rotating 3D Coin Animation with CSS3, LESS and React

This article explains how to build a rotating 3D coin effect for a web page using CSS3 animations, @keyframes, LESS variables and loops, and a React component that generates the coin's faces and edges, providing a visual cue to attract users to an activity.

ByteFE
ByteFE
ByteFE
How to Create a Rotating 3D Coin Animation with CSS3, LESS and React

Recently the product team asked to add a rotating coin in a corner of a page to attract users to an activity, with a very tight deadline and no design ready.

The simplest idea is to use a GIF inside an <a> tag, but we chose a pure CSS solution.

The rotation itself can be done with CSS3 animation and @keyframes:

.coin {
  transform-style: preserve-3d;
  animation: rotate3d 3s linear infinite;
}

@keyframes rotate3d {
  0% {
    transform: perspective(1000px) rotateY(0deg);
  }
  100% {
    transform: perspective(1000px) rotateY(360deg);
  }
}

To create the coin faces we use two div elements. By removing the surface pattern the coin becomes a very thin cylinder, so we need a front, a back and a curved side.

The front and back are straightforward:

$coin-thickness: 4px;
$coin-front: "https://tosv.byted.org/obj/maat/img/d2FuZ3NoaXpoZW4uNzUzMA/file_178a58ae02711.png";
$coin-back: "https://tosv.byted.org/obj/maat/img/d2FuZ3NoaXpoZW4uNzUzMA/file_178a58ae0276.png";

.coin__front {
  background-image: url($coin-front);
  background-size: cover;
  transform: translateZ($coin-thickness / 2);
}

.coin__back {
  background-image: url($coin-back);
  background-size: cover;
  transform: translateZ(-$coin-thickness / 2) rotateY(180deg);
}

The side of the coin cannot be drawn with a single CSS property; we approximate it with a many‑sided polygon (a “multi‑prism”). The number of sides determines how convincing the illusion is.

We generate the required div elements with a LESS @for loop (or a JavaScript Array.map) and then position each side using translate and rotate transforms.

const n = 80;
<div className={styles.coin__edge}>
  {Array(n).fill(1).map((value, key) => (
    <div key={key} />
  ))}
</div>

The SCSS for each edge calculates its height, width and the series of transforms needed to place it around the coin:

// size of the prism (diameter)
$coin-diameter: 320px;
// thickness of the coin
$coin-thickness: 40px;
// colour of the coin
$coin-color: rgb(255, 223, 95);
// number of faces
$edge-faces: 100;
// length of each face
$edge-face-length: 3.14 * $coin-diameter / $edge-faces;

.coin__edge {
  > div {
    position: absolute;
    height: $edge-face-length;
    width: $coin-thickness;
  }
  @for $i from 1 through $edge-faces {
    div:nth-child(#{$i}) {
      background: $coin-color;
      transform:
        translateY($coin-diameter / 2 - $edge-face-length / 2)
        translateX($coin-diameter / 2 - $coin-thickness / 2)
        rotateY(90deg)
        rotateX(360deg / $edge-faces * $i + 90)
        translateZ($coin-diameter / 2);
    }
  }
}

Images below illustrate the steps: default position, moving to centre, horizontal rotation, adjusting each facet’s angle, and finally moving the facet to the edge of the prism.

Finally the complete code is provided, including the React component skeleton, all LESS variables, the styles for .coin, .coin__front, .coin__back, .coin__edge, the shadow element, and the two keyframe animations ( rotate3d and shine).

const CoinTrans = () => {
  return (
    <div className="coin">
      <div className="coin__front" />
      <div className="coin__back" />
      <div className="coin__edge">
        {Array(100).fill(1).map((item, key) => (
          <div key={key} />
        ))}
      </div>
      {/* shadow */}
      <div className="coin__shadow" />
    </div>
  );
};

$coin-diameter: 320px;
$coin-thickness: 40px;
$coin-color: rgb(255, 223, 95);
$edge-faces: 100;
$edge-face-length: 3.14 * $coin-diameter / $edge-faces;

.coin {
  position: relative;
  width: $coin-diameter;
  height: $coin-diameter;
  margin: 0 auto;
  transform-style: preserve-3d;
  animation: rotate3d 8s linear infinite;
  transition: all 0.3s;
}

.coin__front, .coin__back {
  position: absolute;
  width: $coin-diameter;
  height: $coin-diameter;
  border-radius: 50%;
  overflow: hidden;
  background-color: $coin-color;
  color: #000;
  font-size: $coin-diameter * 0.6;
  /* shine effect */
  &:after {
    content: "";
    position: absolute;
    left: -$coin-diameter / 2;
    bottom: 100%;
    height: $coin-diameter * 0.6;
    width: $coin-diameter * 2;
    background: #fff;
    opacity: 0.3;
    animation: shine linear 4s / 2 infinite;
  }
}

.coin__front {
  background-image: url($coin-front);
  background-size: cover;
  transform: translateZ($coin-thickness / 2);
}

.coin__back {
  background-image: url($coin-back);
  background-size: cover;
  transform: translateZ(-$coin-thickness / 2) rotateY(180deg);
}

.coin__edge {
  > div {
    position: absolute;
    height: $edge-face-length;
    width: $coin-thickness;
  }
  @for $i from 1 through $edge-faces {
    div:nth-child(#{$i}) {
      background: $coin-color;
      transform:
        translateY($coin-diameter / 2 - $edge-face-length / 2)
        translateX($coin-diameter / 2 - $coin-thickness / 2)
        rotateY(90deg)
        rotateX(360deg / $edge-faces * $i + 90)
        translateZ($coin-diameter / 2);
    }
  }
}

.coin__shadow {
  position: absolute;
  width: $coin-diameter;
  height: $coin-thickness;
  border-radius: 50%;
  background: #000;
  box-shadow: 0 0 $coin-thickness * 5 $coin-thickness * 5 #000;
  opacity: 0.125;
  transform: rotateX(90deg) translateZ(-$coin-diameter * 1.1) scale(0.5);
}

@keyframes rotate3d {
  0% { transform: perspective(1000px) rotateY(0deg); }
  100% { transform: perspective(1000px) rotateY(360deg); }
}

@keyframes shine {
  0%, 15% { transform: translateY($coin-diameter * 2) rotate(-40deg); }
  50% { transform: translateY(-$coin-diameter) rotate(-40deg); }
}

The data platform front‑end team is responsible for big‑data products such as Wind‑God, TEA, Libra and Dorado, and we are passionate about front‑end technology, data visualisation, massive data optimisation, web‑excel, WebIDE, private deployment and engineering tools.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Reactcss33Dcoinless
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.