Разбирающийся куб
Описание
Всем привет! Захотел сделать куб и при наведении на него курсором, его грани должны раздвигаться. Также нужно сделать стрелочки с текстом к его граням, а при наведении стрелочки должны отодвигаться вместе с его гранями и появиться новый текст снизу.
.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 шт):
Приятно, что автор вопроса смог понять судь без описания :)
Но всё постараюсь объяснить для тех, кому этот вопрос может пригодиться.
Если применять 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 (по вертикали).
ПКМ по ползунку сбросит на стартовые значения.