<div id="container">
  <div id="square">
    <div class="face front">front</div>
    <div class="face right">right</div>
    <div class="face left">left</div>
    <div class="face back">back</div>
    <div class="face top">top</div>
    <div class="face bottom">bottom</div>
  </div>
  <input id="axis" placeholder="axis" value="X" />
  <input id="angle" placeholder="angle" value="+=90" />
  <button id="tilt-btn">Tilt</button>
  <div id="data">
    <span><h3>Current transform string:</h3><div></div></span>
    <span><h3>Current axis:</h3><div></div></span>
  </div>
</div>

<div id="scrollElement"></div>

X axis can work on all three x,y and z axes.
Y axis can work just on x and y axes.
Z axis can work only on z axis.

Plus the X axis looks to be the only one to be able to work on any custom angle.
body {
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}

#container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  perspective: 1000px;
}

#data {
  display: flex;
  flex-direction: column;
}

#data span {
  display: flex;
  align-items: center;
}
#square {
  width: 160px;
  height: 160px;
  background: red;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  transform-style: preserve-3d;
  padding: 0 !important;
}

#scrollElement {
  height: 10000px;
  width: 100vw;
}

.face {
  width: 100%;
  height: 100%;
  position: absolute;
  background-color: rgba(3, 121, 255, 0.5);

  color: #fff;
  line-height: 10em;
  text-align: center;
}

.front {
  transform: rotateY(0deg) translateZ(5em);
}
.right {
  transform: rotateY(90deg) translateZ(5em);
}
.left {
  transform: rotateY(-90deg) translateZ(5em);
}
.back {
  transform: rotateY(180deg) translateZ(5em);
}
.top {
  transform: rotateX(90deg) translateZ(5em);
}
.bottom {
  transform: rotateX(-90deg) translateZ(5em);
}
gsap.registerPlugin(ScrollTrigger);

const tl = gsap.timeline();

const el = document.querySelector("#square");
const btn = document.querySelector("#tilt-btn");
const axisEl = document.querySelector("#axis");
const angleEl = document.querySelector("#angle");
const transformStringContainer = document.querySelectorAll("#data div")[0];
const currentDynamicAxisContainer = document.querySelectorAll("#data div")[1];

const updateData = () => {
  transformStringContainer.innerText = el.style.transform;
  currentDynamicAxisContainer.innerText = axisEl.value;
};

ScrollTrigger.create({
  animation: tl,
  trigger: "#scrollElement",
  start: "top top",
  end: "100% 100%",
  scrub: 0.2,
  onUpdate: updateData
});

tl.to("#square", { rotationZ: "90deg", ease: "circ.out", duration: 5 })
  .to("#square", { rotationY: "45deg", ease: "circ.out", duration: 5 })
  .to("#square", {
    rotateX: "45deg",
    ease: "linear",
    duration: 5
  })
  .to("#square", {
    rotateY: "+=90deg",
    ease: "linear",
    duration: 5
  });

////////////////////////////////////////
// Axis builder
const { width, height } = el.getBoundingClientRect();
console.log(width, height);
const axes = {
  x: {
    rotate: "rotateZ(90deg)",
    rotatePerp: "rotateZ(90deg) rotateY(90deg)",
    top: "50%",
    left: "50%",
    translate: `translate(0, -${height}px)`,
    translatePerp: `translate3d(0px, -${height}px, -${height}px)`,
    color: "green"
  },
  y: {
    rotate: "",
    rotatePerp: "rotateY(90deg)",
    top: "50%",
    left: "50%",
    translate: `translate(0, -${height}px)`,
    color: "yellow"
  },
  z: {
    rotate: "rotateZ(90deg) rotateX(90deg)",
    rotatePerp: "rotateX(90deg)",
    top: "50%",
    left: "50%",
    translate: `translate(0px, -${height}px)`,
    color: "blue"
  }
};
for (const key in axes) {
  const axis = axes[key];
  const div = document.createElement("div");
  const div1 = document.createElement("div");
  div.id = key;
  div.style.transform = axis.translate + " " + axis.rotate;
  div.style.top = axis.top;
  div.style.left = axis.left;
  div.style.backgroundColor = axis.color;
  div.style.width = "5px";
  div.style.height = height * 2 + "px";
  div.style.position = "absolute";
  div.innerHTML = `<h4>${key}</h4>`;
  // Perpendicular axis
  div1.id = key + "_" + "perp";
  div1.style.transform = axis.translate + " " + axis.rotatePerp;
  div1.style.top = axis.top;
  div1.style.left = axis.left;
  div1.style.backgroundColor = axis.color;
  div1.style.width = "5px";
  div1.style.height = height * 2 + "px";
  div1.style.position = "absolute";
  div1.innerHTML = `<h4>${key}</h4>`;
  el.appendChild(div);
  el.appendChild(div1);
}

// Handle dynamic inputs

btn.onclick = () => {
  gsap.to("#square", { [`rotation${axisEl.value.toUpperCase()}`]: `${angleEl.value}` });
  updateData();
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

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