ȸ鿡 **Ä¿¼ Àü¿ë DOM(.cursor)**À» ¶ç¿öµÎ°í, ¸¶¿ì½º À§Ä¡¸¦ CSS º¯¼ö --mx, --my·Î Àü´ÞÇØ Ä¿¼°¡ µû¶ó¿Àµµ·Ï ±¸¼ºÇÑ ¿¹Á¦ÀÔ´Ï´Ù.
¸µÅ©/¹öư¿¡ ¸¶¿ì½º¸¦ ¿Ã¸®¸é body:has(...) ¼±ÅÃÀÚ·Î »óŸ¦ °¨ÁöÇØ Ä¿¼ Å©±â·Ç¥½Ã ÅØ½ºÆ®(¿¹: “View”)·¾ÆÀÌÄÜ Ç¥½Ã°¡ ÀÚµ¿ ÀüȯµË´Ï´Ù.
»ç¿ë ¹æ¹ýÀº °£´ÜÇÕ´Ï´Ù. ÆäÀÌÁö¿¡ .cursor ¸¶Å©¾÷À» Æ÷ÇÔÇϰí, Ä¿¼ »óŸ¦ ¹Ù²Ù°í ½ÍÀº ¿ä¼Ò¿¡ cursor-read, cursor-icon °°Àº Ŭ·¡½º¸¦ ºÙÀÌ¸é µË´Ï´Ù.
¶ÇÇÑ JS´Â mousemove À̺¥Æ®·Î ÁÂÇ¥¸¦ ¹Þ°í, ¾à°£ÀÇ Áö¿¬(ºÎµå·¯¿î ÃßÀû)À» Àû¿ëÇØ --mx/--my¸¦ °»½ÅÇÕ´Ï´Ù.
Âü°í·Î :has()´Â ºê¶ó¿ìÀú Áö¿ø ¹üÀ§°¡ ÀÖÀ¸´Ï(±¸Çü ȯ°æ) ¿î¿µ Àû¿ë Àü ȣȯ¼º üũ¸¦ ±ÇÀåÇÕ´Ï´Ù.
HTML ±¸Á¶
<div class="demo">
<div class="demo__elements">
<a href="#!" class="demo__button">Default</a>
<a href="#!" class="demo__button cursor-read">Word</a>
<button href="#!" class="demo__button cursor-icon">Icon</button>
</div>
</div>
<div class="cursor">
<div class="cursor__pointer cursor__pointer--default"></div>
<div class="cursor__pointer cursor__pointer--action cursor__pointer--read">
View
</div>
<div class="cursor__pointer cursor__pointer--action cursor__pointer--icon">
<svg
class="icon icon-plus cursor__icon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="20"
height="20"
viewBox="0 0 20 20"
>
<g id="Group_1" data-name="Group 1" transform="translate(-0.75 -0.75)">
<line
id="Line_1"
data-name="Line 1"
class="icon-plus-line"
y2="10"
transform="translate(10.75 5.75)"></line>
<line
id="Line_2"
data-name="Line 2"
class="icon-plus-line"
y2="10"
transform="translate(15.75 10.75) rotate(90)"></line>
</g>
</svg>
</div>
</div>
CSS ¼Ò½º
body {
padding: 1rem;
font-family: sans-serif;
display: grid;
place-items: center;
min-height: 100vh;
}
a,
button {
display: inline-block;
border: 1px solid gray;
padding: .5rem 1rem;
border-radius: 4px;
text-decoration: none;
margin: 0;
width: auto;
overflow: visible;
background: transparent;
color: inherit;
font: inherit;
line-height: inherit;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
}
.demo__elements {
display: flex;
gap: 1rem
}
.cursor {
--cursor-diameter: 75px;
margin-top: 0;
position: fixed;
top: 0;
left: 0;
translate: calc(var(--mx) - var(--cursor-diameter) / 2)
calc(var(--my) - var(--cursor-diameter) / 2);
width: var(--cursor-diameter);
aspect-ratio: 1/1;
pointer-events: none;
display: grid;
z-index: 1000;
opacity: 0;
scale: 0;
transition: opacity 0.25s ease;
}
:root:hover .cursor {
opacity: 1;
scale: 1;
}
.cursor__pointer {
grid-row: 1;
grid-column: 1;
position: relative;
width: var(--cursor-diameter);
height: var(--cursor-diameter);
transform-origin: 50% 50%;
border-radius: 50%;
background: hotpink;
transition: 0.25s ease;
scale: 0.2;
/* opacity: 0.5; */
color: white;
}
.cursor__pointer--action {
opacity: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-family: monospace;
text-transform: uppercase;
}
.cursor__pointer--icon {
stroke: currentColor;
}
.cursor__icon {
scale: 2;
}
.cursor__pointer--default {
opacity: 0.5;
}
body:has(:is(a:hover, button:hover)) .cursor__pointer {
scale: 0.5;
}
body:has(:is(a:hover, button:hover)) .cursor__pointer--default {
opacity: 0.5;
}
body:has(.cursor-read:hover) .cursor__pointer {
scale: 1;
}
body:has(.cursor-read:active) .cursor__pointer {
scale: 0.9;
}
body:has(.cursor-read:hover) .cursor__pointer--read {
opacity: 1;
}
body:has(.cursor-icon:hover) .cursor__pointer {
scale: 0.5;
}
body:has(.cursor-icon:active) .cursor__pointer {
scale: 0.4;
}
body:has(.cursor-icon:hover) .cursor__pointer--icon {
opacity: 1;
}
body:has(:is(a:active, button:active)) .cursor__pointer--default {
scale: 0.4;
}
JS ¼Ò½º
const delay = 5;
let posX = 0,
posY = 0,
mouseX = 0,
mouseY = 0;
const mouseDelay = () => {
posX += (mouseX - posX) / delay;
posY += (mouseY - posY) / delay;
document.documentElement.style.setProperty("--mx", posX + "px");
document.documentElement.style.setProperty("--my", posY + "px");
requestAnimationFrame(mouseDelay);
};
mouseDelay();
document.addEventListener("mousemove", (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});