// React ver of this awesome pen: https://codepen.io/tahazsh/pen/wvQwqYv
body
#app
View Compiled
@import url("https://fonts.googleapis.com/css2?family=Alfa+Slab+One&family=Chivo&display=swap");
:root {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
margin: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
background-image: linear-gradient(
160deg in oklch,
color(display-p3 0.1 0.6 0.9) 15%,
color(display-p3 0.4 0.2 0.8) 99%
);
font-family: Chivo, sans-serif;
}
.card {
padding: 10px;
cursor: pointer;
img {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}
.content {
overflow: hidden;
display: flex;
flex-direction: column;
align-items: flex-start;
box-shadow: rgba(255, 255, 255, 0.3) 0 5vw 6vw -8vw,
rgba(255, 255, 255, 0) 0 4.5vw 5vw -6vw,
rgba(50, 50, 80, 0.5) 0px 4vw 8vw -2vw,
rgba(0, 0, 0, 0.8) 0px 4vw 5vw -3vw;
transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1);
background-color: rgb(6, 6, 6);
border-radius: 10px;
width: 100%;
max-width: 420px;
will-change: transform;
transition: transform 0.25s ease-out;
h2 {
font-family: "Alfa Slab One";
font-size: 2.2rem;
color: #f4f4f4;
padding: 1rem 1rem 0 1rem;
text-shadow: 2px 4px 3px rgba(255, 255, 255, 0.3);
}
p {
font-size: 1.2rem;
color: #b4b4b4;
padding: 0 1rem 0;
margin-bottom: 1rem;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
&:hover h2 {
text-decoration: underline;
}
}
}
.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;
.gloss--shine {
transition: 600ms opacity ease-out;
}
}
View Compiled
const { app } = window;
const { useState, useRef, useEffect } = React;
const useGlossEffect = (cardRef, cardContentRef, glossRef) => {
const mapNumberRange = (n, a, b, c, d) => {
return ((n - a) * (d - c)) / (b - a) + c;
};
const addShineClass = () => {
requestAnimationFrame(() => {
glossRef.current.classList.add("gloss--shine");
});
};
const calculateTransformValues = (pointerX, pointerY, cardRect) => {
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);
return { rx, ry, degree, distanceToCenter, maxDistance };
};
const applyTransform = (rx, ry, degree, distanceToCenter, maxDistance) => {
const cardTransform = `perspective(400px) rotate3d(${-rx}, ${ry}, 0, ${degree}deg)`;
const glossTransform = `translate(${-ry * 100}%, ${-rx * 100}%) scale(2.4)`;
const glossOpacity = mapNumberRange(distanceToCenter, 0, maxDistance, 0, 0.6);
cardContentRef.current.style.transform = cardTransform;
glossRef.current.style.transform = glossTransform;
glossRef.current.style.opacity = glossOpacity.toString();
};
const handleMouseMove = ({ clientX, clientY }) => {
const card = cardRef.current;
const cardRect = card.getBoundingClientRect();
const {
rx,
ry,
degree,
distanceToCenter,
maxDistance
} = calculateTransformValues(clientX, clientY, cardRect);
applyTransform(rx, ry, degree, distanceToCenter, maxDistance);
};
const handleMouseLeave = () => {
cardContentRef.current.style.transform = null;
glossRef.current.style.opacity = 0;
};
useEffect(() => {
const card = cardRef.current;
addShineClass();
card.addEventListener("mousemove", handleMouseMove);
card.addEventListener("mouseleave", handleMouseLeave);
return () => {
card.removeEventListener("mousemove", handleMouseMove);
card.removeEventListener("mouseleave", handleMouseLeave);
};
}, [cardRef, cardContentRef, glossRef]);
};
const Card = () => {
const cardRef = useRef(null);
const cardContentRef = useRef(null);
const glossRef = useRef(null);
useGlossEffect(cardRef, cardContentRef, glossRef);
return (
<div className="card" ref={cardRef}>
<div className="content" ref={cardContentRef}>
<div className="gloss" ref={glossRef} />
<img
src="https://images.pexels.com/photos/145683/pexels-photo-145683.jpeg"
alt="chain links"
/>
<h2>Predictive Analytics</h2>
<p>
Unleash the potential of AI-driven sentiment analysis, analyzing
social media and customer feedback to gain valuable insights and adapt
business strategies. Discover the power of AI-powered demand
forecasting, optimizing inventory management and minimizing supply
chain disruptions for improved profitability
</p>
</div>
</div>
);
};
const root = ReactDOM.createRoot(app);
root.render(<Card />);
View Compiled
This Pen doesn't use any external CSS resources.