0

I am struggling to figure out how to have a cube rotate one side at a time using CSS. I am able to get the cube to rotate to the top / bottom correctly and even to each side correctly but if I rotate to the top or bottom first and then to a side the cube will spin without rotating left or right.

Here is a codepen example of what I am working with:

https://codepen.io/exosyphon/pen/yLGOWyL

var cube = document.getElementById('cube');

var min = 1;
var max = 24;
var diceX = 0;
var diceY = 0;

document.getElementById('top').onclick = function() {
diceX += 90;
  cube.style.transform = 'rotateX('+diceX+'deg) rotateY('+diceY+'deg)';
}

document.getElementById('side').onclick = function() {
diceY += 90;
  cube.style.transform = 'rotateX('+diceX+'deg) rotateY('+diceY+'deg)';
}

Expected behavior: rotate to bottom (6), rotate to right and see another side (4)

Current behavior: rotate to bottom (6), rotate to right and only see 6

I am a bit worried this is not solvable with just CSS.

var cube = document.getElementById('cube');

var min = 1;
var max = 24;
var diceX = 0;
var diceY = 0;

document.getElementById('top').onclick = function() {
  diceX += 90;
  cube.style.transform = 'rotateX(' + diceX + 'deg) rotateY(' + diceY + 'deg)';
}

document.getElementById('side').onclick = function() {
  diceY += 90;
  cube.style.transform = 'rotateX(' + diceX + 'deg) rotateY(' + diceY + 'deg)';
}
h1 {
  font-family: Arial, Helvetica, sans-serif;
  text-align: center;
}

#cube .front {
  transform: translateZ(100px);
}

#cube .back {
  transform: rotateX(-180deg) translateZ(100px);
}

#cube .right {
  transform: rotateY(90deg) translateZ(100px);
}

#cube .left {
  transform: rotateY(-90deg) translateZ(100px);
}

#cube .top {
  transform: rotateX(90deg) translateZ(100px);
}

#cube .bottom {
  transform: rotateX(-90deg) translateZ(100px);
}

.container {
  width: 200px;
  height: 200px;
  position: relative;
  margin: 0 auto 40px;
  border: 1px solid #FFF;
  perspective: 1000px;
  perspective-origin: 50% 100%;
}

#cube {
  width: 100%;
  height: 100%;
  top: 100px;
  position: absolute;
  transform-style: preserve-3d;
  transition: transform 6s;
}

#cube:hover {
  cursor: pointer;
}

#cube div {
  background: hsla(0, 85%, 50%, 0.8);
  display: block;
  position: absolute;
  width: 200px;
  height: 100px;
  border: 2px solid #ab1a1a;
  margin: 0 auto;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 500%;
  text-align: center;
  padding: 50px 0;
}

.dot {
  display: block;
  position: absolute;
  width: 30px;
  height: 30px;
  background: #fff;
  border-radius: 15px;
}

.front .dot1 {
  top: 85px;
  left: 85px;
}

.back .dot1 {
  top: 45px;
  left: 45px;
}

.back .dot2 {
  top: 125px;
  left: 125px;
}

.right .dot1 {
  top: 45px;
  left: 45px;
}

.right .dot2 {
  top: 85px;
  left: 85px;
}

.right .dot3 {
  top: 125px;
  left: 125px;
}

.left .dot1 {
  top: 45px;
  left: 45px;
}

.left .dot2 {
  top: 45px;
  left: 125px;
}

.left .dot3 {
  top: 125px;
  left: 45px;
}

.left .dot4 {
  top: 125px;
  left: 125px;
}

.top .dot1 {
  top: 45px;
  left: 45px;
}

.top .dot2 {
  top: 45px;
  left: 125px;
}

.top .dot3 {
  top: 85px;
  left: 85px;
}

.top .dot4 {
  top: 125px;
  left: 45px;
}

.top .dot5 {
  top: 125px;
  left: 125px;
}

.bottom .dot1 {
  top: 45px;
  left: 45px;
}

.bottom .dot2 {
  top: 45px;
  left: 85px;
}

.bottom .dot3 {
  top: 45px;
  left: 125px;
}

.bottom .dot4 {
  top: 125px;
  left: 45px;
}

.bottom .dot5 {
  top: 125px;
  left: 85px;
}

.bottom .dot6 {
  top: 125px;
  left: 125px;
}
<h1>Click the dice to roll</h1>
<section class="container">
  <button id='top'>Top</button>
  <button id='side'>Side</button>
  <div id="cube">
    <div class="front">
      <span class="dot dot1"></span>
    </div>
    <div class="back">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
    </div>
    <div class="right">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
    </div>
    <div class="left">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
      <span class="dot dot4"></span>
    </div>
    <div class="top">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
      <span class="dot dot4"></span>
      <span class="dot dot5"></span>
    </div>
    <div class="bottom">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
      <span class="dot dot4"></span>
      <span class="dot dot5"></span>
      <span class="dot dot6"></span>
    </div>
  </div>
</section>
exosyphon
  • 47
  • 7
  • I'm not looking into this, but you should add `#cube div { outline: 1px solid transparent }` to your CSS to smoothen the jagged edges of the dice faces... – Rene van der Lende Aug 31 '23 at 13:54

1 Answers1

1

As mentioned in the comments, this is a JavaScript based issue rather than a CSS one, as there are no easy methods to solve this with CSS.

Since you are rotating the cube, the axis, and face change, therefore you see erratic behavior where it sometimes rotates on the axis correctly, whereas on other faces it would not.

You can refer to similar questions asked here previously.

The linked questions explain some of the challenges faced when rotating across multiple axes.

However, it would be recommended to dig in some more on this complex topic.

Based on the the reference linked above I have created a working solution for your code.

A few important changes to note

  • Added gimble class to the div with the cube id.
  • Added gimble class in CSS with some styles necessary
  • Cube position is set by translate instead of top to align with the transform-origin property for gimble

var cube = document.getElementById('cube');

var min = 1;
var max = 24;
var diceX = 0;
var diceY = 0;
var diceZ = 0

function rotate(axis, degrees) {
    let outermostRotator = document.querySelector('section.container .gimbal');
    outermostRotator.outerHTML = `<div class='gimbal' id='container' style="width: 0; height: 0; ">${outermostRotator.outerHTML}</div>`;
    
    window.setTimeout(function () {
        container.style.transform = `rotate${axis}(${degrees}deg)`;
        container.removeAttribute('id');
    }, 10);
}

document.getElementById('top').onclick = function() {
  rotate("X",90);
}

document.getElementById('side').onclick = function() {
  rotate("Y",90);
}
h1 {
  font-family: Arial, Helvetica, sans-serif;
  text-align: center;
}

#cube .front {
  transform: 
    translateZ(100px);
}

#cube .back {
  transform: 
    rotateX(-180deg)
    translateZ(100px);
}

#cube .right {
  transform:
    rotateY(90deg)
    translateZ(100px);
}

#cube .left {
  transform:
    rotateY(-90deg)
    translateZ(100px);
}

#cube .top {
  transform:
    rotateX(90deg)
    translateZ(100px);
}

#cube .bottom {
  transform:
    rotateX(-90deg)
    translateZ(100px);
}

.container {
  width: 200px;
  height: 400px;
  position: relative;
  margin: 0 auto 40px;
  border: 1px solid #FFF;
  perspective: 1000px;
  perspective-origin: 50% 50%;
}

#cube {
  width: 100%;
  height: 100%;

  position: absolute;
  transform-style: preserve-3d;
  transition: transform 6s;
  transform: translateY(100px);
}

.gimbal {
  transition: transform 6s;
  transform-style: preserve-3d;
  position: absolute; 
  transform-origin: 100px 200px;
}

#cube:hover {
  cursor: pointer;
}

#cube div {
  background: hsla(0, 85%, 50%, 0.8);
  display: block;
  position: absolute;
  width: 200px;
  height: 100px;
  border: 2px solid #ab1a1a;
  
  margin: 0 auto;  
  
  font-family: Arial, Helvetica, sans-serif;
  font-size: 500%;
  text-align: center;
  padding: 50px 0;
}

.dot {
  display: block;
  position: absolute;
  width: 30px;
  height: 30px;
  background: #fff;
  border-radius: 15px;  
}

.front .dot1 { top: 85px; left: 85px; }
.back .dot1 { top: 45px; left: 45px; }
.back .dot2 { top: 125px; left: 125px; }
.right .dot1 { top: 45px; left: 45px; }
.right .dot2 { top: 85px; left: 85px; }
.right .dot3 { top: 125px; left: 125px; }
.left .dot1 { top: 45px; left: 45px; }
.left .dot2 { top: 45px; left: 125px; }
.left .dot3 { top: 125px; left: 45px; }
.left .dot4 { top: 125px; left: 125px; }
.top .dot1 { top: 45px; left: 45px; }
.top .dot2 { top: 45px; left: 125px; }
.top .dot3 { top: 85px; left: 85px; }
.top .dot4 { top: 125px; left: 45px; }
.top .dot5 { top: 125px; left: 125px; }
.bottom .dot1 { top: 45px; left: 45px; }
.bottom .dot2 { top: 45px; left: 85px; }
.bottom .dot3 { top: 45px; left: 125px; }
.bottom .dot4 { top: 125px; left: 45px; }
.bottom .dot5 { top: 125px; left: 85px; }
.bottom .dot6 { top: 125px; left: 125px; }
<h1>Click the dice to roll</h1>
<section class="container">
  <button id='top'>Top</button>
  <button id='side'>Side</button>
  <div id="cube" class="gimbal">
    <div class="front">
      <span class="dot dot1"></span>
    </div>
    <div class="back">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
    </div>
    <div class="right">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
    </div>
    <div class="left">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
      <span class="dot dot4"></span>
    </div>
    <div class="top">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
      <span class="dot dot4"></span>
      <span class="dot dot5"></span>
    </div>
    <div class="bottom">
      <span class="dot dot1"></span>
      <span class="dot dot2"></span>
      <span class="dot dot3"></span>
      <span class="dot dot4"></span>
      <span class="dot dot5"></span>
      <span class="dot dot6"></span>
    </div>
  </div>
</section>

The approach used basically appends a new parent div above the cube with each button click and rotates that parent div instead of the cube. So the divs inside maintain their orientation and only a single rotation is applied to the new parent element.

deepanshu223
  • 433
  • 2
  • 6