<!-- Cursor -->
<div class="cursor">
<div class="cursor__circle cursor__circle--small"></div>
<div class="cursor__circle cursor__circle--large"></div>
</div>
<!-- End Cursor -->
<!-- Hero Header -->
<section class="hero-header">
<div class="hero-header__grid">
<div class="hero-header__content">
<h2 class="hero-header__title">Try to <span data-pointer="hover">hover me!</span></h2>
<p class="hero-header__description"></p>
</div>
<div class="hero-header__grid-overlay"></div>
</div>
</section>
<!-- End Hero Header -->
/* @start: Base */
:root {
--color-white: #FFFFFF;
--color-black: #000000;
--color-red : #E11D48;
}
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
position: relative;
top: auto;
width: 100%;
min-height: fill-available;
marquee-increment: 0vw;
font-size: 16px;
font-kerning: none;
font-smoothing: antialiased;
osx-font-smoothing: grayscale;
text-rendering: optimizeSpeed;
background-color: var(--color-black);
overflow: hidden;
}
body {
position: relative;
width: 100%;
min-height: 100vh;
font-family: 'Outfit';
min-height: fill-available;
background-color: var(--color-black);
}
/* @start: Hero Header */
.hero-header {
position: relative;
&__grid {
position: relative;
width: 100vw;
height: 100vh;
background: linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
background-size: 30px 30px;
overflow: hidden;
}
&__grid-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
pointer-events: none;
}
&__content {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
z-index: 2;
}
&__title {
color: var(--color-white);
font-size: clamp(24px,3vw,90px);
font-weight: 800;
letter-spacing: -1px;
span {
text-decoration: underline;
}
}
&__description {
color: var(--color-white);
font-size: 12px;
text-transform: uppercase;
text-align: center;
margin-top: 20px;
}
}
/* @start: Cursor */
.cursor {
position: fixed;
top: 0;
left: 0;
pointer-events: none;
z-index: 98;
&__circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
&--small {
width: 10px;
height: 10px;
background-color: var(--color-white);
}
&--large {
width: 40px;
height: 40px;
border: 2px solid var(--color-red);
}
}
}
.cursor-hide {
display: none;
}
View Compiled
import gsap from "https://esm.sh/gsap";
import lerp from "https://esm.sh/lerp";
import Bowser from "https://esm.sh/bowser";
// Cursor
const Cursor = (() => {
const cursorArea = document.querySelector('.cursor');
const cursorSmall = document.querySelector('.cursor__circle--small');
const cursorLarge = document.querySelector('.cursor__circle--large');
if (!cursorSmall || !cursorLarge) {
console.error('Cursor elements not found in the DOM.');
return null;
}
let smallPos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
let largePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
let mousePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
const updateCursor = () => {
smallPos.x = lerp(smallPos.x, mousePos.x, 0.2);
smallPos.y = lerp(smallPos.y, mousePos.y, 0.2);
largePos.x = lerp(largePos.x, mousePos.x, 0.1);
largePos.y = lerp(largePos.y, mousePos.y, 0.1);
gsap.set(cursorSmall, { x: smallPos.x, y: smallPos.y });
gsap.set(cursorLarge, { x: largePos.x, y: largePos.y });
};
const handleMouseMove = (e) => {
mousePos.x = e.clientX;
mousePos.y = e.clientY;
};
const handlePointerEnter = (e) => {
const pointerType = e.target.getAttribute('data-pointer');
// Check the value of `data-pointer` and apply animations accordingly.
// You can extend this logic to add more animations for different `data-pointer` values.
if ( pointerType == 'hover' ) {
gsap.to(cursorLarge, { scale: 2, opacity: 1, duration: 0.3 });
gsap.to(cursorSmall, { scale: 0, opacity: 0, duration: 0.3 });
}
};
const handlePointerLeave = () => {
gsap.to(cursorLarge, { scale: 1, opacity: 1, duration: 0.3 });
gsap.to(cursorSmall, { scale: 1, opacity: 1, duration: 0.3 });
};
const addPointerEvents = () => {
document.querySelectorAll('[data-pointer]').forEach((element) => {
element.addEventListener('mouseenter', handlePointerEnter);
element.addEventListener('mouseleave', handlePointerLeave);
});
};
const removePointerEvents = () => {
document.querySelectorAll('[data-pointer]').forEach((element) => {
element.removeEventListener('mouseenter', handlePointerEnter);
element.removeEventListener('mouseleave', handlePointerLeave);
});
};
const init = () => {
window.addEventListener('mousemove', handleMouseMove);
addPointerEvents();
gsap.ticker.add(updateCursor);
};
const destroy = () => {
window.removeEventListener('mousemove', handleMouseMove);
removePointerEvents();
gsap.ticker.remove(updateCursor);
};
const disable = () => {
destroy();
cursorArea.classList.add('cursor-hide');
}
return { init, destroy, disable };
})();
export default Cursor;
// Init
document.addEventListener('DOMContentLoaded', () => {
// Check Browser & Touch Devices
const browser = Bowser.getParser(window.navigator.userAgent);
const isHandheld = browser.getPlatformType() === 'tablet' || browser.getPlatformType() === 'mobile';
const descriptionLog = document.querySelector('.hero-header__description');
if (!isHandheld) {
Cursor.init();
descriptionLog.textContent = 'Non-touch device detected. Initializing cursor.';
console.log('Non-touch device detected. Initializing cursor.');
} else {
Cursor.disable();
descriptionLog.textContent = 'Touch device detected. Disabling cursor.';
console.log('Touch device detected. Disabling cursor.');
}
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.