<body>
  <div class="cube">
    <div class="face front"></div>
    <div class="face back"></div>
    <div class="face top"></div>
    <div class="face bottom"></div>
    <div class="face left"></div>
    <div class="face right"></div>
  </div>
</body>
$size: 50vmin;

@mixin face($color: white, $tx: 0px, $ty: 0px, $tz: 0px, $rx: 0deg, $ry: 0deg) {
  background-color: $color;
  transform: translateX($tx) translateY($ty) translateZ($tz) rotateX($rx)
    rotateY($ry);
}

* {
  margin: 0;
  padding: 0;
}

body {
  background-color: black;
  height: 100vh;
  overflow: hidden;
  perspective: 1000px;
}

.cube {
  transform: rotateX(-25deg) rotateY(25deg);
  cursor: move;
  transform-style: preserve-3d;
  position: absolute;
  width: $size;
  height: $size;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  margin: auto;

  .face {
    box-sizing: border-box;
    border: 5px solid black;
    width: $size;
    height: $size;
    position: absolute;
    top: 0;
    left: 0;
    &.front {
      @include face($tz: $size / 2);
    }
    &.back {
      @include face($tz: -$size / 2);
    }
    &.top {
      @include face($ty: -$size / 2, $rx: 90deg);
    }
    &.bottom {
      @include face($ty: $size / 2, $rx: 90deg);
    }
    &.left {
      @include face($tx: -$size / 2, $ry: 90deg);
    }
    &.right {
      @include face($tx: $size / 2, $ry: 90deg);
    }
  }
}
View Compiled
const cubeEl = document.querySelector(".cube");

let clickX = 0;
let clickY = 0;

let moveX = 0;
let moveY = 0;

let lastX = -25;
let lastY = 25;


///////////////////
// 마우스 클릭 이벤트 //
///////////////////

cubeEl.addEventListener("mousedown", (e) => {
  let isClick = true;

  clickX = e.screenX;
  clickY = e.screenY;

  window.addEventListener("mousemove", (e) => {
    if (isClick) {
      const nowX = e.screenX;
      const nowY = e.screenY;

      moveX = lastX + clickY - nowY;
      moveY = lastY - clickX + nowX;

      console.log(`X 회전각 : ${moveX}
Y 회전각 : ${moveY}`);

      gsap.to(cubeEl, 0, {
        transform: `rotateX(${moveX}deg) rotateY(${moveY}deg)`,
      });
    }
  });

  window.addEventListener(
    "mouseup",
    (e) => {
      if (isClick) {
        lastX = moveX;
        lastY = moveY;
        isClick = false;
      }
    },
    { once: true }
  );
});


///////////////////
//   터치 이벤트    //
///////////////////

cubeEl.addEventListener(
  "touchstart",
  (e) => {
    let isTouch = true;

    clickX = e.targetTouches[0].screenX;
    clickY = e.targetTouches[0].screenY;

    window.addEventListener("touchmove", (e) => {
      if (isTouch) {
        const nowX = e.targetTouches[0].screenX;
        const nowY = e.targetTouches[0].screenY;

        moveX = lastX + clickY - nowY;
        moveY = lastY - clickX + nowX;

        console.log(`X 회전각 : ${moveX}
Y 회전각 : ${moveY}`);

        gsap.to(cubeEl, 0, {
          transform: `rotateX(${moveX}deg) rotateY(${moveY}deg)`,
        });
      }
    });

    window.addEventListener(
      "touchend",
      (e) => {
        if (isTouch) {
          lastX = moveX;
          lastY = moveY;
          isTouch = false;
        }
      },
      { once: true }
    );
  },
  false
);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.co/gsap@3/dist/gsap.min.js