Card Title
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
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>