Разбирающийся куб

Описание

Всем привет! Захотел сделать куб и при наведении на него курсором, его грани должны раздвигаться. Также нужно сделать стрелочки с текстом к его граням, а при наведении стрелочки должны отодвигаться вместе с его гранями и появиться новый текст снизу.

.wrap {
  width: 1920px;
  position: relative;
  margin-top: 400px;
  perspective: 1000px;
  perspective-origin: 50% 50%;
}

.leg {
  position: relative;
  width: 0px;
  height: 100px;
  border: 1px solid black;
}

.fr,
.sc,
.th {
  position: relative;
}

.indicator {
  top: -85px;
  left: -170px;
  position: absolute;
}

.line {
  position: absolute;
  overflow: hidden;
  height: 300px;
  /* transform: rotate(45deg); */
}

.text {
  position: absolute;
  top: -120px;
  width: 100px;
}

.h-line {
  width: 100px;
  border: 1px solid black;
}

.d-line {
  margin-left: 86.5px;
  margin-top: 34px;
  width: 100px;
  border: 1px solid black;
  transform: rotate(45deg);
}

.cube {
  margin: auto;
  position: relative;
  height: 200px;
  width: 200px;
  transform-style: preserve-3d;
}

.cube>div {
  position: absolute;
  box-sizing: border-box;
  padding: 10px;
  height: 100%;
  width: 100%;
  opacity: 0.9;
  background-color: #000;
  border: solid 1px #eeeeee;
  color: #ff0000;
}

.legs {
  left: -65px;
  margin-top: 240px;
  position: absolute;
  display: flex;
  flex-direction: row;
  gap: 70px;
}

.sc {
  margin-left: 152px;
}

.th {
  margin-top: -78px;
}

.third {
  margin-top: 78px;
}

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

.front--1 {
  transform: translateZ(100px);
}

.back {
  transform: translateZ(-100px) rotateY(180deg);
}

.right {
  transform: rotateY(-270deg) translateX(100px);
  transform-origin: top right;
}

.right--1 {
  transform: rotateY(-270deg) translateX(100px);
  transform-origin: top right;
}

.left {
  transform: rotateY(270deg) translateX(-100px);
  transform-origin: center left;
}

.top {
  transform: rotateX(-270deg) translateY(-100px);
  transform-origin: top center;
}

.top--1 {
  transform: rotateX(-270deg) translateY(-100px);
  transform-origin: top center;
}

.bottom {
  transform: rotateX(270deg) translateY(100px);
  transform-origin: bottom center;
}

.wrap:hover .front {
  transform: translateZ(200px) translateX(-200px);
  /*   margin-left: -200px;
    transition: 0.3s ease-in; */
  background-color: #fcf;
}

.wrap:hover .right {
  transform: rotateY(-270deg) translateZ(150px) translateX(150px);
}

.wrap:hover .top {
  transform: rotateX(-270deg) translateZ(120px) translateY(-100px);
}

.wrap:hover .bottom {
  transform: rotateX(270deg) translateZ(100px) translateY(100px);
}

.wrap:hover .legs {
  transform: translateY(100px);
  transition: transform 0.3s ease-in;
}

.wrap:not(:hover) .legs {
  transform: translateY(0);
  transition: transform 0.3s ease-in;
}

.wrap:hover .first {
  margin-top: 20px;
  transition: 0.3s ease-in;
}

.wrap:not(:hover) .first {
  margin-top: 0;
  transition: 0.3s ease-in;
}

.wrap:hover .second {
  margin-top: 12px;
  transition: 0.3s ease-in;
}

.wrap:not(:hover) .second {
  margin-top: 0;
  transition: 0.3s ease-in;
}

.wrap:hover .third {
  margin-top: -7px;
  transition: 0.3s ease-in;
}

.wrap:not(:hover) .third {
  margin-top: 0;
  transition: 0.3s ease-in;
}

.wrap:hover .text {
  top: 5px;
  transition: 0.3s ease-in;
}

.wrap:not(:hover) .text {
  top: -120px;
  transition: 0.3s ease-in;
}

.cube>div {
  transition: transform 0.3s ease-in;
}

.cube {
  transform: rotateX(-15deg) rotateY(-25deg);
}
<div class="wrap">
  <div class="cube">
    <div class="front">
      <div class="indicator">
        1
        <div class="line">
          <div class="h-line"></div>
          <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
        </div>
        <div class="d-line"></div>
      </div>
      Front side
    </div>
    <div class="front--1">
      Front side
    </div>
    <div class="top">
      <div class="indicator">
        2
        <div class="line">
          <div class="h-line"></div>
          <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
        </div>
        <div class="d-line"></div>
      </div>
      Top side
    </div>
    <div class="top--1">
      Top side
    </div>
    <div class="bottom">
      <div class="indicator">
        3
        <div class="line">
          <div class="h-line"></div>
          <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
        </div>
        <div class="d-line"></div>
      </div>
      Bottom side
    </div>
    <div class="right">
      <div class="indicator">
        4
        <div class="line">
          <div class="h-line"></div>
          <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
        </div>
        <div class="d-line"></div>
      </div>
      Right side
    </div>

    <div class="right--1">
      Right side
    </div>
    <section class="legs">
      <div class="fr">
        <div class="first leg"></div>
        <div class="indicator">
          4
          <div class="line">
            <div class="h-line"></div>
            <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
          </div>
          <div class="d-line"></div>
        </div>
      </div>
      <div class="sc">
        <div class="second leg"></div>
        <div class="indicator">
          4
          <div class="line">
            <div class="h-line"></div>
            <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
          </div>
          <div class="d-line"></div>
        </div>
      </div>
      <div class="th">
        <div class="third leg"></div>
        <div class="indicator">
          4
          <div class="line">
            <div class="h-line"></div>
            <span class="text">текст текст текст текст текст текст текст текст текст текст текст текст</span>
          </div>
          <div class="d-line"></div>
        </div>
      </div>
    </section>
  </div>
</div>

Проблема

Стрелочки с текстом не должны быть в той же плоскости, что и грань куба, к которой она привязана, а всегда быть прямо перед пользователями.
Заранее спасибо за помощь!)


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

Автор решения: De.Minov

Приятно, что автор вопроса смог понять судь без описания :)
Но всё постараюсь объяснить для тех, кому этот вопрос может пригодиться.

Если применять CSS трансформацию к элементу, то его дочерние элементы тоже будут трансформированы, но если к ним применить инвертированное значение трансформации, то тогда они будут отображаться "обычно".

Разберём это на примере skewX()

body {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 40px;
  width: 100%;
  height: 100vh;
  margin: 0;
  overflow: hidden;
}

.box {
  display: block;
  width: 100px;
  height: 100px;
  background-color: red;
  transform: skewX(45deg);
}

.box-inner {
  display: block;
  width: 50px;
  height: 50px;
  background-color: blue;
  margin: 25px;
}
<div class="box">
  <div class="box-inner"></div>
</div>

<div class="box">
  <div class="box-inner" style="transform: skewX(-45deg)"></div>
</div>

Как видно из данного примера, левый красный и синий блок имеют одинаковый skewX().
А синему блоку с права был назначен skewX(-45deg), т.е. инвертированное значение его родителя.
Из-за этого кажется, что визуально синий блок никак не поддался трансформации.

Ровно так же работает и "выравнивание" элементов при использовании 3D в CSS, только тут значений для инвертирования нужно применить больше.

Для начала, стоит принять во внимание, что сам .cube тоже повёрнут в пространстве по оси Ycube = 45deg и Xcube = -25deg.

Далее пройдёмся по сторонам:

Сторона X: Повёрнута по оси Y на 90deg, т.к. по этой же оси вращается и .cube, по этому значение для "нормального" отображение будет rotateY(90deg - Ycube).
А вращение по X будет просто инвертированным rotateX(Xcube * -1);

Сторона Y: Повёрнута по оси X на 90deg, для начала инвертируем это значение, после чего вращаем Y на Ycube * -1, а затем уже вращаем относительно .cube по X, тут значение тоже будет инвертированным.

Сторона Z: Это в целом та сторона, которая уже находится в "нормальном" положение, по этому там достаточно лишь инвертировать вращение куба.

Для того чтобы расположить "подсказки" в других местах, потребуется другие значения, тут уже можно подобрать.
Или даже написать миксин (если используется препроцессор), где все значения будут подставляться от направления и положения подсказки, и вращение её будет относительно стороны куба.


На гифках использовался вот этот куб:

const x = document.querySelector('#rotateX')
const y = document.querySelector('#rotateY')
const cube = document.querySelector('.cube')

const xInitialValue = x.value
const yInitialValue = y.value

const changeX = () => cube.style.setProperty('--Xcube', x.value+'deg')
const changeY = () => cube.style.setProperty('--Ycube', y.value+'deg')

changeX()
changeY()

x.addEventListener('mousemove', changeX)
y.addEventListener('mousemove', changeY)

x.addEventListener('contextmenu', (event) => {
  event.preventDefault()
  x.value = xInitialValue
  changeX()
})
y.addEventListener('contextmenu', (event) => {
  event.preventDefault()
  y.value = yInitialValue
  changeY()
})
body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
  background-color: #f2f2f2;
  font-family: sans-serif;
  color: #000;
  overflow: hidden auto;
  margin: 0;
  user-select: none;
}

#rotateY,
#rotateX {
  position: fixed;
  z-index: 10;
}

#rotateY {
  width: 90%;
  height: 10px;
  top: 10px;
}

#rotateX {
  -webkit-appearance: slider-vertical;
  -moz-appearance: slider-vertical;
  appearance: slider-vertical;
  width: 10px;
  height: 45%;
  right: 10px;
}

.container {
  perspective: 700px;
}

.cube {
  display: block;
  width: 200px;
  height: 200px;
  --Xcube: -25deg;
  --Ycube: 45deg;
  transform: rotateX(var(--Xcube)) rotateY(var(--Ycube));
  transform-style: preserve-3d;
}

.cube * {
  transform-style: preserve-3d;
}

.cube-face {
  display: block;
  width: 200px;
  height: 200px;
  border: 5px dashed #333;
  background-color: rgba(0, 0, 0, 0.05);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  position: absolute;
}

.cube-face:hover .cube-hover {
  transform: translateZ(100px);
}

.cube-face--x {
  transform: translateX(-50%) rotateY(-90deg);
}

.cube-face--x-opposite {
  transform: translateX(50%) rotateY(90deg);
}

.cube-face--x .cube-face-normal {
  transform: rotateY(calc(90deg - var(--Ycube))) rotateX(calc(var(--Xcube) * -1));
}

.cube-face--x .cube-hover {
  background-color: rgba(255, 0, 0, 0.2);
  color: #f00;
}

.cube-face--y {
  transform: translateY(-50%) rotateX(90deg);
}

.cube-face--y-opposite {
  transform: translateY(50%) rotateX(-90deg);
}

.cube-face--y .cube-face-normal {
  transform: rotateX(-90deg) rotateY(calc(var(--Ycube) * -1)) rotateX(calc(var(--Xcube) * -1));
}

.cube-face--y .cube-hover {
  background-color: rgba(0, 128, 0, 0.2);
  color: #008000;
}

.cube-face--y .cube-hover-title {
  right: 0;
}

.cube-face--z {
  transform: rotateX(90deg) translateY(50%) rotateX(-90deg);
}

.cube-face--z-opposite {
  transform: rotateX(90deg) translateY(-50%) rotateX(90deg);
}

.cube-face--z .cube-face-normal {
  transform: rotateY(calc(var(--Ycube) * -1)) rotateX(calc(var(--Xcube) * -1));
}

.cube-face--z .cube-hover {
  background-color: rgba(0, 0, 255, 0.2);
  color: #00f;
}

.cube-hover {
  display: block;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 0, 0, 0.1);
  transition: transform 0.3s ease;
  position: relative;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  pointer-events: none;
}

.cube-hover-title {
  display: block;
  width: 100px;
  height: 50px;
  background-image: 
    linear-gradient(#000 0 0), 
    linear-gradient(to top right, 
      transparent calc(50% - 1px), 
      #000 calc(50% - 1px), 
      #000 calc(50% + 1px), 
      transparent calc(50% + 1px)
    );
  background-repeat: no-repeat;
  background-position: 0 1em, 100% 1em;
  background-size: 70% 2px, 30% calc(100% - 1em);
  position: absolute;
  right: 100%;
  bottom: 100%;
  transform-origin: right bottom;
}
<input id="rotateY" type="range" min="-180" value="45" max="180" />
<input id="rotateX" type="range" min="-90" value="-25" max="90" />

<div class="container">
  <div class="cube">
    <div class="cube-face cube-face--x">
      <div class="cube-hover">
        <div class="cube-hover-title cube-face-normal">X face</div>
      </div>
    </div>
    <div class="cube-face cube-face--x-opposite"></div>
    <div class="cube-face cube-face--y">
      <div class="cube-hover">
        <div class="cube-hover-title cube-face-normal">Y face</div>
      </div>
    </div>
    <div class="cube-face cube-face--y-opposite"></div>
    <div class="cube-face cube-face--z">
      <div class="cube-hover">
        <div class="cube-hover-title cube-face-normal">Z face</div>
      </div>
    </div>
    <div class="cube-face cube-face--z-opposite"></div>
  </div>
</div>

Верхний ползунок (input[type="range"]) отвечает за вращение по Y (по горизонтали), а правый за вращение по X (по вертикали).
ПКМ по ползунку сбросит на стартовые значения.

→ Ссылка