Перейти к содержимому

Карточка (анимированная)

Card Title

Lorem ipsum dolor sit amet, consectetur adipisicing elit.

<div class="card">
<div class="card__content">
<div class="card__gloss"></div>
<img
class="card__image"
src="https://images.unsplash.com/photo-1579645899072-e14c6b840afa?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=400&q=80"
alt=""
/>
<h2 class="card__title">Card Title</h2>
<p class="card__description">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
</div>
<style>
* {
box-sizing: border-box;
}
.card {
padding: 10px;
cursor: pointer;
font-family: Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
}
.card__image {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}
.card__content {
overflow: hidden;
display: flex;
flex-direction: column;
align-items: flex-start;
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
background: whitesmoke;
border-radius: 10px;
width: 100%;
max-width: 300px;
will-change: transform;
transition: transform 0.25s ease-out;
}
.card__title {
color: hsl(201, 14%, 20%);
font-size: 20px;
padding: 10px 14px 5px;
margin: 0;
}
.card__description {
margin: 0;
padding: 0 14px 10px;
font-size: 14px;
color: hsl(201, 14%, 40%);
text-align: left;
}
.card__gloss {
opacity: 0;
z-index: 10;
width: 100%;
height: 100%;
left: 0;
top: 0;
border-radius: 50%;
background: radial-gradient(
circle,
rgba(255, 255, 255, 1) 0%,
rgba(255, 255, 255, 0) 50%,
rgba(255, 255, 255, 0) 100%
);
position: absolute;
will-change: opacity;
}
.card__gloss.card__gloss--animatable {
transition: 0.25s opacity ease-out;
}
</style>
<script lang="js">
function mapNumberRange(n, a, b, c, d) {
return ((n - a) * (d - c)) / (b - a) + c
}
function setup() {
Array.from(document.querySelectorAll('.card')).map((cardEl) =>
initCard(cardEl)
)
}
function initCard(card) {
const cardContent = card.querySelector('.card__content')
const gloss = card.querySelector('.card__gloss')
requestAnimationFrame(() => {
gloss.classList.add('card__gloss--animatable')
})
card.addEventListener('mousemove', (e) => {
const pointerX = e.clientX
const pointerY = e.clientY
const cardRect = card.getBoundingClientRect()
const halfWidth = cardRect.width / 2
const halfHeight = cardRect.height / 2
const cardCenterX = cardRect.left + halfWidth
const cardCenterY = cardRect.top + halfHeight
const deltaX = pointerX - cardCenterX
const deltaY = pointerY - cardCenterY
const distanceToCenter = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
const maxDistance = Math.max(halfWidth, halfHeight)
const degree = mapNumberRange(distanceToCenter, 0, maxDistance, 0, 10)
const rx = mapNumberRange(deltaY, 0, halfWidth, 0, 1)
const ry = mapNumberRange(deltaX, 0, halfHeight, 0, 1)
cardContent.style.transform = `perspective(400px) rotate3d(${-rx}, ${ry}, 0, ${degree}deg)`
gloss.style.transform = `translate(${-ry * 100}%, ${-rx * 100}%) scale(2.4)`
gloss.style.opacity = `${mapNumberRange(
distanceToCenter,
0,
maxDistance,
0,
0.6
)}`
})
card.addEventListener('mouseleave', () => {
cardContent.style = null
gloss.style.opacity = 0
})
}
setup()
</script>