I’m trying to build a 3D rotating cube with a smooth animation, where a single video plays in the background — but the video should only be visible through the cube’s faces, like looking through a window. The goal is to create the illusion that the video exists only behind the cube and is not visible anywhere else on the screen.
I’ve tried several approaches using CSS clip-path, mask, and even WebGL, but I keep facing issues like:
The video appears outside the cube area.
The video is duplicated on all cube faces instead of staying in the background.
I can’t make the cube act like a window while hiding everything else.
What I want:
A 3D cube with a smooth continuous rotation.
One video positioned behind the cube.
The video should only be seen through the cube (not outside it).
The cube should not have the video mapped on all six faces — just act as a viewing window.
Any suggestions, code samples, or best practices would be appreciated! I’m open to using CSS, Three.js, or a combination. Thanks in advance! here is my jsx –
import '../../styles/Cube3D.css';
const Cube3D = () => {
return (
<div className="scene">
<div className="cube">
<div className="face-wrapper front">
<div className="face-blur">
</div>
<div className="face-content">Front Face</div>
</div>
<div className="face-wrapper back">
<div className="face-blur"></div>
</div>
<div className="face-wrapper right">
<div className="face-blur"></div>
</div>
<div className="face-wrapper left">
<div className="face-blur"></div>
</div>
<div className="face-wrapper top">
<div className="face-blur"></div>
</div>
<div className="face-wrapper bottom">
<div className="face-blur"></div>
</div>
</div>
</div>
);
};
export default Cube3D;
and here is my css –
.scene {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
perspective: 1500px;
background-color: rgb(0, 0, 0);
/* background-image: url('../../assets/image1.png'); */
/* overflow: hidden; */
}
.cube {
position: relative;
width: 250px;
height: 250px;
transform-style: preserve-3d;
animation: spinCube 5s linear infinite;
animation-delay: 3s;
z-index: 1;
}
.front .face-content {
animation: fadeOutText 5s linear infinite;
animation-delay: 3s;
}
@keyframes spinCube {
0% {
transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) scale3d(1, 1, 1);
}
10% {
transform: rotateX(-7deg) rotateY(-25deg) rotateZ(4deg)
scale3d(1.1, 1.1, 1.1);
}
25% {
transform: rotateX(-14deg) rotateY(-51deg) rotateZ(10deg)
scale3d(1.3, 1.3, 1.3);
}
45% {
transform: rotateX(-21deg) rotateY(-76deg) rotateZ(12deg)
scale3d(1.5, 1.5, 1.5);
}
60% {
transform: rotateX(-25deg) rotateY(-120deg) rotateZ(12deg)
scale3d(1.6, 1.6, 1.6);
}
75% {
transform: rotateX(-21deg) rotateY(-240deg) rotateZ(10deg)
scale3d(1.75, 1.75, 1.75);
}
90% {
transform: rotateX(-14deg) rotateY(-300deg) rotateZ(4deg)
scale3d(2.5, 2.5, 2.5);
}
100% {
transform: rotateX(-7deg) rotateY(-360deg) rotateZ(0deg) scale3d(3, 3, 3);
}
}
@keyframes fadeOutText {
0%,
45% {
opacity: 1;
}
46%,
100% {
opacity: 0;
}
}
/* Cube Faces */
.face-wrapper {
position: absolute;
width: 250px;
height: 250px;
clip-path: polygon(
5% 0%,
95% 0%,
100% 5%,
100% 95%,
95% 100%,
5% 100%,
0% 95%,
0% 5%
);
/* overflow: hidden; */
}
.face-blur {
position: absolute;
inset: 0;
background: linear-gradient(145deg, rgba(255, 0, 0, 0.5), rgba(0, 0, 0, 0.1));
/* background: linear-gradient(
145deg,
rgba(255, 255, 255, 0.05),
rgba(0, 0, 0, 0.1)
); */
backdrop-filter: blur(6px) brightness(1.1);
-webkit-backdrop-filter: blur(6px) brightness(1.1);
z-index: 0;
}
.face-content {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
height: 250px;
text-align: center;
font-size: 2.2rem;
font-weight: bolder;
color: black;
font-family: Arial, Helvetica, sans-serif;
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: inset 0 0 30px rgba(255, 255, 255, 0.05),
0 0 20px rgba(0, 255, 255, 0.2), 0 0 40px rgba(0, 255, 255, 0.15);
}
/* Cube Faces Positions */
.front {
transform: translateZ(125px);
}
.back {
transform: rotateY(180deg) translateZ(125px);
}
.right {
transform: rotateY(90deg) translateZ(125px);
}
.left {
transform: rotateY(-90deg) translateZ(125px);
}
.top {
transform: rotateX(90deg) translateZ(125px);
}
.bottom {
transform: rotateX(-90deg) translateZ(125px);
}