div.vhs
div.vhs__control
div.vhs__case
div.vhs__front
div.vhs__back
div.vhs__left
div.vhs__right
div.vhs__top
div.vhs__bottom
View Compiled
/* Shout out to https://vhscollector.com/ for the box art! */
:root {
--cw: 122;
--ch: 202;
--cd: 27;
--brl: 4;
--brs: 1;
--m: min(0.3svw, 0.3svh);
}
body {
background-color: oklch(98% 0.05 200deg);
display: grid;
margin: 0;
min-block-size: 100dvh;
place-content: center;
}
.vhs {
perspective: 1000px;
}
.vhs__control {
cursor: all-scroll;
transform-origin: center center calc(((var(--cd) * var(--m)) / 2) * -1);
transform-style: preserve-3d;
}
.vhs__case {
position: relative;
transform-style: preserve-3d;
}
.vhs__case > * {
transform-style: preserve-3d;
}
.vhs__front,
.vhs__back,
.vhs__left,
.vhs__right,
.vhs__top,
.vhs__bottom {
background-color: oklch(95% 0.01 200deg);
box-shadow: inset 0 0 calc(10 * var(--m)) 0 oklch(50% 0.01 200deg / 30%);
position: absolute;
}
.vhs__case,
.vhs__front,
.vhs__back {
block-size: calc(var(--ch) * var(--m));
inline-size: calc(var(--cw) * var(--m));
}
.vhs__front,
.vhs__back {
inset: 0;
}
.vhs__front::after,
.vhs__back::after,
.vhs__left::after {
background-image: url("https://www.transparenttextures.com/patterns/solid.png");
content: '';
inset: 2.5% 0;
opacity: 0.7;
position: absolute;
}
.vhs__front::after {
inset-inline-end: 2.5%;
}
.vhs__back::after {
inset-inline-start: 2.5%;
}
.vhs__front::before,
.vhs__back::before {
background-color: oklch(100% 0 0deg / 20%);
content: '';
position: absolute;
inline-size: 2px;
inset-block: 2.5%;
}
.vhs__front::before {
inset-inline: auto 6%;
}
.vhs__back::before {
inset-inline: 6% auto;
}
.vhs__front {
background-image: url(https://vhscollector.com/sites/default/files/vhsimages/20475_Stand%2520by%2520Me%25201.jpg);
background-size: 96% 96%;
background-position: left center;
background-repeat: no-repeat;
border-radius: calc(var(--brs) * var(--m)) calc(var(--brl) * var(--m)) calc(var(--brl) * var(--m)) calc(var(--brs) * var(--m));
}
.vhs__back {
background-image: url(https://vhscollector.com/sites/default/files/vhsimages/20475_Stand%2520by%2520Me%25202.jpg);
background-size: 96% 96%;
background-position: right center;
background-repeat: no-repeat;
border-radius: calc(var(--brl) * var(--m)) calc(var(--brs) * var(--m)) calc(var(--brs) * var(--m)) calc(var(--brl) * var(--m));
rotate: 0 1 0 180deg;
translate: 0 0 calc((var(--cd) * var(--m)) * -1);
}
.vhs__left,
.vhs__right {
block-size: calc(var(--ch) * var(--m));
inline-size: calc(var(--cd) * var(--m));
}
.vhs__left {
background-image: url(https://vhscollector.com/sites/default/files/vhsimages/20475_Stand%2520by%2520Me%25203.jpg);
background-size: 100% 96%;
background-position: center center;
background-repeat: no-repeat;
border-radius: calc(var(--brs) * var(--m));
inset-inline: auto 100%;
rotate: 0 1 0 -90deg;
transform-origin: right center;
}
.vhs__right {
block-size: calc(((var(--ch) * var(--m)) / 100) * 97);
inset: 50% auto auto 98.5%;
overflow: clip;
rotate: 0 1 0 90deg;
transform-origin: left center;
translate: 0 -50%;
}
.vhs__right::before {
background-color: oklch(10% 0 0deg / 40%);
content: '';
filter: blur(calc(1 * var(--m)));
inset: 0 6%;
position: absolute;
}
.vhs__top,
.vhs__bottom {
block-size: calc(var(--cd) * var(--m));
inline-size: calc(((var(--cw) * var(--m)) / 100) * 98.5);
}
.vhs__top::before,
.vhs__bottom::before {
background-color: oklch(10% 0 0deg / 40%);
content: '';
filter: blur(calc(1 * var(--m)));
position: absolute;
}
.vhs__top {
inset-block: auto 98.5%;
rotate: 1 0 0 90deg;
transform-origin: center bottom;
}
.vhs__top::before {
inset: 6% 0 6% 3%;
}
.vhs__bottom {
inset-block: 98.5% auto;
rotate: 1 0 0 -90deg;
transform-origin: center top;
}
.vhs__bottom::before {
inset: 6% 0 6% 3%;
}
const vhs = document.querySelector('.vhs__control');
let isDragging = false;
let previousX = 0;
let previousY = 0;
let rotateX = -30;
let rotateY = 0;
let velocityX = 0;
let velocityY = 0;
let autoRotate = true;
function animate() {
if (autoRotate) {
rotateY += 0.1;
} else {
rotateX -= velocityY;
rotateY += velocityX;
// Apply friction.
velocityX *= 0.95;
velocityY *= 0.95;
// Stop completely if velocity is low.
if (Math.abs(velocityX) < 0.001 && Math.abs(velocityY) < 0.001) {
velocityX = 0;
velocityY = 0;
}
}
vhs.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
requestAnimationFrame(animate);
}
// Mouse events.
vhs.addEventListener('mousedown', (e) => {
isDragging = true;
autoRotate = false;
previousX = e.clientX;
previousY = e.clientY;
});
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaX = e.clientX - previousX;
const deltaY = e.clientY - previousY;
velocityX = deltaX * 0.1;
velocityY = deltaY * 0.1;
rotateY += velocityX;
rotateX -= velocityY;
previousX = e.clientX;
previousY = e.clientY;
});
window.addEventListener('mouseup', () => {
isDragging = false;
autoRotate = true;
});
// Touch events.
vhs.addEventListener('touchstart', (e) => {
isDragging = true;
autoRotate = false;
previousX = e.touches[0].clientX;
previousY = e.touches[0].clientY;
});
vhs.addEventListener('touchmove', (e) => {
const deltaX = e.touches[0].clientX - previousX;
const deltaY = e.touches[0].clientY - previousY;
velocityX = deltaX * 0.1;
velocityY = deltaY * 0.1;
rotateY += velocityX;
rotateX -= velocityY;
previousX = e.touches[0].clientX;
previousY = e.touches[0].clientY;
});
vhs.addEventListener('touchend', () => {
isDragging = false;
autoRotate = true;
});
// Start animation loop.
requestAnimationFrame(animate);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.