Как создать эффект (анимацию) вращения по оси Y объекта формы монеты с помощью СSS?

Была задумка сделать вращение круга, который по оси Z растягивался б на 20+ px (чтобы видно было толщину, как у ребра монеты), но как только я начал выставлять элементы по оси Z, элементы стали вращаться по совсем другой траектории. Как можно получить вышеописанный эффект с помощью CSS/JS/SVG?

вращение монеты

Второй вопрос: сейчас я использую setTimeout из-за того, что мои классы немедленно присваиваются и я вижу поворот по оси Y только 1 раз из 8 загрузок страницы. Как инициализировать поворот через класс без функции setTimeout, чтобы это срабатывало с каждой загрузкой страницы?

document.addEventListener('DOMContentLoaded', function(){
   //document.querySelector(".side").classList.add('active');
  function flipCircle(n){
   setTimeout( ()=> {n.classList.add('active')}, 50);
  }
   var sides = document.getElementsByClassName("side");
    for (var i = 0; i < sides.length; i++) {   
      flipCircle(sides[i]);
    }
});
@font-face {
    font-family: 'Digital Numbers';
    font-style: normal;
    font-weight: 400;
    src: local('Digital Numbers'), url('https://fonts.cdnfonts.com/s/15953/DigitalNumbers-Regular.woff') format('woff');
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: #0a0a0a;
}

.text {
  position: absolute;
  top: 0;left: 0;
  width: 100%;
  height: 100%;
  overflow-wrap: break-word;
  word-wrap: break-word;
}
h1 {
  display: inline;
  font-size: 260px;
  line-height: 290px;
  margin: 0;
  padding: 0;
  animation: glow 1.5s ease-in-out infinite;
  font-family: 'Digital Numbers', sans-serif;
}
.flip {
  margin: calc(50vh - 250px) auto;
  width: 500px;
  height: 500px;
  perspective: 700px;
}
.container {
  position: relative;
  width: 100%;
  height: 100%;
}

.side {
  width: 100%;
  height: 100%;
  position: absolute;
  top:0;left:0;
  backface-visibility: hidden;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
  animation: border-glow 1.5s ease-in-out infinite;
  border: 100px solid black;
  border-radius: 100%;
  transition: transform 30s cubic-bezier(1,.01,1,.5), border 500ms ease-in-out;;
  transform-style: preserve-3d;
}

.shuf-front {
  background-image: url('https://i.ibb.co/FgBcMfm/shuf-771.png'); 
}

.shuf-back {
  transform: rotateY(180deg) scale(-1, 1);
  background-image: url('https://i.ibb.co/FgBcMfm/shuf-771.png');
}

.active {
  transform: rotateY(calc(-2000 * 360deg));
}

/*rotateX(calc(2000 * 360deg))  */

@keyframes glow {
  0%,100%{ text-shadow:0 0 30px red; }
  25%{ text-shadow:0 0 50px orange; }
  50%{ text-shadow:0 0 50px forestgreen; }
  75%{ text-shadow:0 0 50px cyan; }
}

@keyframes border-glow {
  0%,100%{ border: 10px solid red; }
  25%{ border: 20px solid orange; }
  50%{ border: 10px solid forestgreen; }
  75%{ border: 20px solid blue; }
}

@keyframes wobble {
  0%,100%{ transform:rotate3d(1,1,0,40deg); }
  25%{ transform:rotate3d(-1,1,0,40deg); }
  50%{ transform:rotate3d(-1,-1,0,40deg); }
  75%{ transform:rotate3d(1,-1,0,40deg); }
}
<div class="text">
  <h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1><h1>3</h1>
    
  </div>

<div class="flip">
   <div class="container">
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
     <div class="shuf-front side"></div>
     <div class="shuf-back side"></div>
  </div>
</div>


Ответы (4 шт):

Автор решения: Andrey Fedorov

body {
  display: flex;
  min-height: 100vh;
}

div {
  margin: auto;
  width: 50vh;
  height: 50vh;
  border-radius: 100%;
  background-color: #D1C7BB;
  background-size: cover;
  background-repeat: no-repeat;
  background-image: url(https://i115.fastpic.org/thumb/2021/0905/38/_94172dc7f7cefcb43a44012c6c104438.jpeg);
  animation: bshit 3s infinite linear;
}

@keyframes bshit {
  from {
    transform: rotateY(0);
    background-image: url(https://i115.fastpic.org/thumb/2021/0905/38/_94172dc7f7cefcb43a44012c6c104438.jpeg);
  }
  50% {
    background-image: url(https://i115.fastpic.org/thumb/2021/0905/35/_41069afe25725a9574235b4b84009235.jpeg);
  }
  to {
    transform: rotateY(360deg);
  }
}
<div></div>

→ Ссылка
Автор решения: highpassion

Отличное решение от h на чистом CSS:

body {
  width: 100vw;
  height: 100vh;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  perspective: 1000px;
  background-color: white;
}

.coin {
  position: relative;
  width: 30vmin;
  height: 30vmin;
  transform-style: preserve-3d;
  animation: rotate 4s infinite linear;
}

@keyframes rotate {
  100% {
    transform: rotateY(1turn);
  }
}
.coin__layer {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: repeating-linear-gradient(black, black 1vmin, white 1vmin, white 1.5vmin);
}

.coin__layer:first-child,
.coin__layer:last-child {
  display: flex;
  align-items: center;
  justify-content: center;
  backface-visibility: hidden;
  background: black;
  color: white;
  font-weight: 900;
  font-size: 3vmin;
}

.coin__layer:nth-child(2) {
  transform: translateZ(-0.1vmin);
}

.coin__layer:nth-child(3) {
  transform: translateZ(-0.15vmin);
}

.coin__layer:nth-child(4) {
  transform: translateZ(-0.2vmin);
}

.coin__layer:nth-child(5) {
  transform: translateZ(-0.25vmin);
}

.coin__layer:nth-child(6) {
  transform: translateZ(-0.3vmin);
}

.coin__layer:nth-child(7) {
  transform: translateZ(-0.35vmin);
}

.coin__layer:nth-child(8) {
  transform: translateZ(-0.4vmin);
}

.coin__layer:nth-child(9) {
  transform: translateZ(-0.45vmin);
}

.coin__layer:nth-child(10) {
  transform: translateZ(-0.5vmin);
}

.coin__layer:nth-child(11) {
  transform: translateZ(-0.55vmin);
}

.coin__layer:nth-child(12) {
  transform: translateZ(-0.6vmin);
}

.coin__layer:nth-child(13) {
  transform: translateZ(-0.65vmin);
}

.coin__layer:nth-child(14) {
  transform: translateZ(-0.7vmin);
}

.coin__layer:nth-child(15) {
  transform: translateZ(-0.75vmin);
}

.coin__layer:nth-child(16) {
  transform: translateZ(-0.8vmin);
}

.coin__layer:nth-child(17) {
  transform: translateZ(-0.85vmin);
}

.coin__layer:nth-child(18) {
  transform: translateZ(-0.9vmin);
}

.coin__layer:nth-child(19) {
  transform: translateZ(-0.95vmin);
}

.coin__layer:nth-child(20) {
  transform: translateZ(-1vmin);
}

.coin__layer:nth-child(21) {
  transform: translateZ(-1.05vmin);
}

.coin__layer:nth-child(22) {
  transform: translateZ(-1.1vmin);
}

.coin__layer:nth-child(23) {
  transform: translateZ(-1.15vmin);
}

.coin__layer:nth-child(24) {
  transform: translateZ(-1.2vmin);
}

.coin__layer:nth-child(25) {
  transform: translateZ(-1.25vmin);
}

.coin__layer:nth-child(26) {
  transform: translateZ(-1.3vmin);
}

.coin__layer:nth-child(27) {
  transform: translateZ(-1.35vmin);
}

.coin__layer:nth-child(28) {
  transform: translateZ(-1.4vmin);
}

.coin__layer:nth-child(29) {
  transform: translateZ(-1.45vmin);
}

.coin__layer:last-child {
  transform: translateZ(-1.5vmin) rotateY(-180deg);
}
<body translate="no">
  <div class="coin">
  <div class="coin__layer">FRONT</div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer"></div>
  <div class="coin__layer">BACK</div>
</div>
</body>

→ Ссылка
Автор решения: Alexey Vladimirov

Монетка с ребром ?

.coin {
  --diameter:200px;
  --thickness:20px;
  --scale:0.75;
  --period:5s;
  --gradient: linear-gradient(gold 0%, yellow 50%, gold 100%);
  
  transform-style: preserve-3d;
  width: var(--diameter);
  height: var(--diameter);
  animation: spin var(--period) linear infinite;
}

.side {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-color: gold; 
  display: flex;
  align-items: center;
  justify-content: center;
  color: yellow;
  font-size: calc(var(--diameter) * 0.6);
  font-weight: bold;
}

.head {
  transform: translateZ(calc(var(--thickness) / 2)); 
  animation: flip-head var(--period) linear infinite;
}

.tail {
  transform:
    translateZ(calc(0px - var(--thickness) / 2)) 
    rotateY(180deg);
  animation: flip-tail var(--period) linear infinite;
}

.edge {
  position: absolute;
  width: var(--thickness);
  height: 100%;
  transform:
    translateX(calc(var(--diameter) / 2))
    translateZ(calc(var(--thickness) / 2))
    rotateY(90deg);
  transform-origin: 0 0;
  background-color: gold;
  background-image: var(--gradient);
}

@keyframes spin {
  0% {
    transform: rotateY(0deg) scale(var(--scale));
  }
  100% {
    transform: rotateY(360deg) scale(var(--scale));
  }
}

@keyframes flip-tail {
  0% {
    background-image: var(--gradient);
    color: transparent;
  }
  50% {
    background-image: none;
    color: yellow;
  }
  100% {
    background-image: var(--gradient);
    color: transparent;
  }
}

@keyframes flip-head {
  0% {
    background-image: none;
    color: yellow;
  }
  50% {
    background-image: var(--gradient);
    color: transparent;
  }
  100% {
    background-image: none;
    color: yellow;
  }
}
<div class="coin">
  <div class="side head">$</div>
  <div class="side tail">1</div>
  <div class="edge"></div>
</div>

В основе решения идея, реализованная в первом ответе от Andrey Fedorov — плавная анимация вращения вокруг оси Y, и пошаговая смена фона.

Однако, если смотреть на объект строго в направлении оси Z, то можно добиться иллюзии объема за счет синхронного вращения двух скругленных плоскостей и вертикального прямоугольника ("ребра"), проходящего через центры кругов под углом 90 градусов. Ширина прямоугольника должна быть равна расстоянию между кругами по оси Z.

При вращении дальний от наблюдателя круг принимает цвет ребра и незаметно сливается с ним в положениях, близких к 90 и 270 градусам. Одинаковая градиентная заливка ребра и дальнего круга усиливает эффект, создавая иллюзию кривизны поверхности.

схема анимации

? Ниже схематическая анимация с полупрозрачными элементами:

.coin {
  --diameter:200px;
  --thickness:20px;
  --scale:0.75;
  --period:5s;
  --gradient: linear-gradient(blue 0%, blue 100%);
  
  transform-style: preserve-3d;
  width: var(--diameter);
  height: var(--diameter);
  animation: spin var(--period) linear infinite;
}

.side {
  opacity:0.75;
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-color: red; 
  display: flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  font-size: calc(var(--diameter) * 0.6);
}

.head {
  transform: translateZ(calc(var(--thickness) / 2)); 
  animation: flip-head var(--period) linear infinite;
}

.tail {
  transform:
    translateZ(calc(0px - var(--thickness) / 2)) 
    rotateY(180deg);
  animation: flip-tail var(--period) linear infinite;
}

.edge {
  opacity:0.75;
  position: absolute;
  width: var(--thickness);
  height: 100%;
  transform:
    translateX(calc(var(--diameter) / 2))
    translateZ(calc(var(--thickness) / 2))
    rotateY(90deg);
  transform-origin: 0 0;
  background-color: gold;
  background-image: var(--gradient);
}

@keyframes spin {
  0% {
    transform: rotateY(0deg) scale(var(--scale));
  }
  100% {
    transform: rotateY(360deg) scale(var(--scale));
  }
}

@keyframes flip-tail {
  0% {
    background-image: var(--gradient);
    color: transparent;
  }
  50% {
    background-image: none;
    color: green;
  }
  100% {
    background-image: var(--gradient);
    color: transparent;
  }
}

@keyframes flip-head {
  0% {
    background-image: none;
    color: green;
  }
  50% {
    background-image: var(--gradient);
    color: transparent;
  }
  100% {
    background-image: none;
    color: green;
  }
}
<div class="coin">
  <div class="side head">$</div>
  <div class="side tail">1</div>
  <div class="edge"></div>
</div>

→ Ссылка
Автор решения: Leonid

Решение с помощью canvas:

const canvas = document.getElementById('coin');
const ctx = canvas.getContext('2d');
const w = canvas.width = 600;
const h = canvas.height = 180;

const coinWidth = 8;
const radius = Math.min(w,h)/3;
const risk = 0.8;

let pattern;
let heads;
let tails;
let gurt;

let pos1 = 0;
let pos2 = coinWidth;

let scale = 1;
let direction = 0.01;

let image = new Image();
image.src = 'https://as2.ftcdn.net/v2/jpg/02/15/57/69/500_F_215576902_nDCSvmb5QL3D6kHknzy9gorsFoBEFV8t.jpg';
image.onload = () => {
    let faceCanvas = document.createElement('canvas');
    let fw = faceCanvas.width = radius*2 + 2;
    let fh = faceCanvas.height = radius*2 + 2;
    let context = faceCanvas.getContext('2d');
    context.drawImage(image,28,13,210,210,0,0,fw,fh);
    tails = ctx.createPattern(faceCanvas, 'no-repeat');
    
    context.drawImage(image,262,13,210,210,0,0,fw,fh);
    heads = ctx.createPattern(faceCanvas, 'no-repeat');
    pattern = heads;
    
    context.fillStyle = '#efd894';
    context.fillRect(0,0,fw,fh);
    for(let i=0; i < fh; i+=risk*2){
        context.fillStyle = '#efd894';
        context.fillRect(0,i,fw,risk);
        context.fillStyle = '#7b521c';
        context.fillRect(0,i+risk,fw,risk);
    }
    gurt = ctx.createPattern(faceCanvas,'no-repeat');
    
    ctx.strokeStyle = '#7b521c';
    ctx.strokeWidth = 1;
    
    ctx.translate(w/2,h/2);
    draw();
}

function draw(){
    ctx.clearRect(-w/2,-h/2,w,h);
    
    if(scale >= 1){
        direction = -direction;
        [pos1,pos2] = [pos2,pos1];
    } else if(scale <= 0){
        direction = - direction;
        pattern = pattern== tails ? heads : tails;
        [pos1,pos2] = [pos2,pos1];
    }
    
    
    ctx.save();
    ctx.translate(pos1-(Math.abs(scale)*pos1),0);
    ctx.scale(scale,1);
    ctx.beginPath();
    ctx.arc(0, 0, radius, 0, 2 * Math.PI);
    ctx.translate(-radius,-radius-1);
    ctx.fillStyle = gurt;
    ctx.fill();
    ctx.stroke();
    ctx.restore();
    
    ctx.save();
    ctx.beginPath();
    ctx.rect(0, -radius-1, coinWidth*(1-scale), radius*2+2);
    ctx.translate(-radius,-radius-1);
    ctx.fillStyle = gurt;
    ctx.fill();
    ctx.restore();
    
    
    ctx.save();
    ctx.translate(pos2-(Math.abs(scale)*pos2),0);
    ctx.scale(scale,1);
    ctx.beginPath();
    ctx.arc(0, 0, radius, 0, 2 * Math.PI);
    ctx.translate(-radius,-radius);
    ctx.fillStyle = pattern;
    ctx.fill();
    ctx.stroke();
        
    ctx.restore();
    
    scale += direction;
    requestAnimationFrame(draw);
}
<canvas id="coin"></canvas>

→ Ссылка