这是一款js和CSS3超酷3D立体卡片。该特效中的卡片使用多层png图片堆叠在一起,当鼠标滑过卡片时,通过js代码使各层图片进行移动,从而制作出3D立体的视觉差效果,非常炫酷。
使用方法
HTML代码
<div class="centered">
<div class="card border-left-behind">
<div class="shadow" style="--url: url('../images/full.png')"></div>
<div class="image background" style="--url: url('../images/Background.png')"></div>
<div class="image cutout" style="--url: url('../images/cutout.png')"></div>
<div class="content">
<h2 class="title">Hover me!</h2>
<p class="desc">Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
</div>
<div class="card border-right-behind border-bottom-behind">
<div class="shadow" style="--url: url('../images/m-full.png')"></div>
<div class="image background" style="--url: url('../images/m-background.png')"></div>
<div class="image cutout" style="--url: url('../images/m-cutout.png')"></div>
<div class="content">
<h2 class="title">Hover me!</h2>
<p class="desc">Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
</div>
<div class="card border-left-behind">
<div class="shadow" style="--url: url('../images/b-full.png')"></div>
<div class="image background" style="--url: url('../images/b-background.png')"></div>
<div class="image cutout" style="--url: url('../images/b-cutout.png')"></div>
<div class="content">
<h2 class="title">Hover me!</h2>
<p class="desc">Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p>
</div>
</div>
</div>
CSS代码
html,
body {
width: 100%;
height: 100%;
font-family: "Montserrat", sans-serif;
}
.centered {
display: flex;
align-items: center;
justify-content: center;
gap: 3rem;
height: 100%;
}
.card {
position: relative;
height: 28rem;
width: 20rem;
aspect-ratio: 5/7;
color: #ffffff;
perspective: 50rem;
}
.card .shadow {
position: absolute;
inset: 0;
background: var(--url);
background-size: cover;
background-position: center;
opacity: 0.8;
filter: blur(2rem) saturate(0.9);
box-shadow: 0 -1.5rem 2rem -0.5rem rgba(0, 0, 0, 0.7);
transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 2rem, -2rem);
}
.card .image {
position: absolute;
inset: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent 40%), var(--url);
background-size: cover;
background-position: center;
-webkit-mask-image: var(--url);
mask-image: var(--url);
-webkit-mask-size: cover;
mask-size: cover;
-webkit-mask-position: center;
mask-position: center;
}
.card .image.background {
transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 0rem);
}
.card .image.cutout {
transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 4rem) scale(0.92);
z-index: 3;
}
.card .content {
position: absolute;
display: flex;
flex-direction: column;
justify-content: flex-end;
inset: 0;
padding: 3.5rem;
transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 6rem);
z-index: 4;
}
.card::after, .card::before {
content: "";
position: absolute;
inset: 1.5rem;
border: #e2c044 0.5rem solid;
transform: rotateX(var(--rotateX)) rotateY(var(--rotateY)) translate3d(0, 0, 2rem);
}
.card::before {
z-index: 4;
}
.card.border-left-behind::before {
border-left: transparent;
}
.card.border-right-behind::before {
border-right: transparent;
}
.card.border-bottom-behind::before {
border-bottom: transparent;
}
h2.title {
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.5rem;
text-shadow: 0 0 2rem rgba(0, 0, 0, 0.5);
}
p.desc {
font-weight: 300;
text-shadow: 0 0 2rem rgba(0, 0, 0, 0.5);
}
CSS代码
const angle = 20;
const rotateCard = window;
const lerp = (start, end, amount) => {
return (1 - amount) * start + amount * end;
};
const remap = (value, oldMax, newMax) => {
const newValue = (value + oldMax) * (newMax * 2) / (oldMax * 2) - newMax;
return Math.min(Math.max(newValue, -newMax), newMax);
};
window.addEventListener("DOMContentLoaded", event => {
const cards = document.querySelectorAll(".card");
cards.forEach(e => {
e.addEventListener("mousemove", event => {
const rect = e.getBoundingClientRect();
const centerX = (rect.left + rect.right) / 2;
const centerY = (rect.top + rect.bottom) / 2;
const posX = event.pageX - centerX;
const posY = event.pageY - centerY;
const x = remap(posX, rect.width / 2, angle);
const y = remap(posY, rect.height / 2, angle);
e.dataset.rotateX = x;
e.dataset.rotateY = -y;
});
e.addEventListener("mouseout", event => {
e.dataset.rotateX = 0;
e.dataset.rotateY = 0;
});
});
const update = () => {
cards.forEach(e => {
let currentX = parseFloat(e.style.getPropertyValue('--rotateY').slice(0, -1));
let currentY = parseFloat(e.style.getPropertyValue('--rotateX').slice(0, -1));
if (isNaN(currentX)) currentX = 0;
if (isNaN(currentY)) currentY = 0;
const x = lerp(currentX, e.dataset.rotateX, 0.05);
const y = lerp(currentY, e.dataset.rotateY, 0.05);
e.style.setProperty("--rotateY", x + "deg");
e.style.setProperty("--rotateX", y + "deg");
});
};
setInterval(update, 1000 / 60);
});