08デフォルトカーソルを隠す
cursor: none でブラウザ標準の矢印を消し、その場所をカスタムカーソル要素に置き換える、他の効果すべての土台となる処理。
使いどころカスタムカーソルを使うあらゆるインタラクティブサイトの基盤設定
cursor: none포인터 기기 감지기반 설정

<main class="stage">
<div class="zone zone--default"><span>デフォルト</span></div>
<div class="zone zone--custom">
<span>カスタム</span>
<div class="cursor is-demo" aria-hidden="true"></div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-loop; // 점이 오른쪽 영역을 도는 한 바퀴
$easing: $ease-spring;
$color: $subject-blue; // 커스텀 점 색
@include stage($stage-paper);
.stage { display: flex; gap: clamp(6px, 2vw, 10px); padding: clamp(10px, 3vw, 16px); }
.zone {
position: relative;
overflow: hidden;
flex: 1;
min-height: clamp(80px, 34vw, 220px);
border-radius: $r-card;
display: grid;
place-items: center;
font-weight: 700; font-size: clamp(12px, 3.4vw, 15px); line-height: 1; font-family: inherit;
}
.zone--default { background: $subject-cream; color: $stage-ink; cursor: auto; } // 브라우저 기본 화살표
.zone--custom { background: $stage-ink; color: $subject-cream; cursor: none; } // cursor: none 이 이 효과의 전제다
.cursor {
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
border-radius: 50%;
background: $color;
pointer-events: none;
&.is-demo { animation: cursor-path $duration $easing infinite; }
}
@keyframes cursor-path {
0% { transform: translate(30px, 30px); }
35% { transform: translate(110px, 90px); }
70% { transform: translate(50px, 130px); }
100% { transform: translate(30px, 30px); }
}
@media (pointer: coarse) {
.cursor { display: none; }
.zone--custom { cursor: auto; }
}
@media (prefers-reduced-motion: reduce) {
.cursor.is-demo { animation: none; transform: translate(30px, 30px); }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.stage {
display: flex;
gap: clamp(6px, 2vw, 10px);
padding: clamp(10px, 3vw, 16px);
}
.zone {
position: relative;
overflow: hidden;
flex: 1;
min-height: clamp(80px, 34vw, 220px);
border-radius: 16px;
display: grid;
place-items: center;
font-weight: 700;
font-size: clamp(12px, 3.4vw, 15px);
line-height: 1;
font-family: inherit;
}
.zone--default {
background: #fff7e6;
color: #17141a;
cursor: auto;
}
.zone--custom {
background: #17141a;
color: #fff7e6;
cursor: none;
}
.cursor {
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
border-radius: 50%;
background: #2f6df6;
pointer-events: none;
}
.cursor.is-demo {
animation: cursor-path 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
@keyframes cursor-path {
0% {
transform: translate(30px, 30px);
}
35% {
transform: translate(110px, 90px);
}
70% {
transform: translate(50px, 130px);
}
100% {
transform: translate(30px, 30px);
}
}
@media (pointer: coarse) {
.cursor {
display: none;
}
.zone--custom {
cursor: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.cursor.is-demo {
animation: none;
transform: translate(30px, 30px);
}
}// 오른쪽 영역 안에서만 カスタム 점이 실제 포인터를 따라간다. 왼쪽은 デフォルト 화살표 그대로.
if (matchMedia('(pointer: fine)').matches) {
var stage = document.querySelector('.stage'), dot = document.querySelector('.cursor'), zone = document.querySelector('.zone--custom');
stage.addEventListener('pointermove', function (e) {
var zr = zone.getBoundingClientRect();
var inside = e.clientX >= zr.left && e.clientX <= zr.right && e.clientY >= zr.top && e.clientY <= zr.bottom;
dot.style.opacity = inside ? 1 : 0;
if (inside) { dot.classList.remove('is-demo'); dot.style.transform = 'translate(' + (e.clientX - zr.left) + 'px,' + (e.clientY - zr.top) + 'px)'; }
});
}import { useState } from "react";
import type { CSSProperties, PointerEvent } from "react";
import styles from "./HideNativeCursor.module.scss";
export type HideNativeCursorProps = {
defaultLabel?: string;
customLabel?: string;
duration?: number;
easing?: string;
color?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function HideNativeCursor({
defaultLabel = "기본",
customLabel = "커스텀",
duration = 2000,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#2f6df6",
}: HideNativeCursorProps) {
const [demo, setDemo] = useState(true);
const [pos, setPos] = useState({ x: 0, y: 0, on: false });
const move = (e: PointerEvent<HTMLDivElement>) => {
const r = e.currentTarget.getBoundingClientRect();
setDemo(false);
setPos({ x: e.clientX - r.left, y: e.clientY - r.top, on: true });
};
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<div className={styles.stage} style={vars}>
<div className={styles.zoneDefault}><span>{defaultLabel}</span></div>
<div className={styles.zoneCustom} onPointerMove={move} onPointerLeave={() => setPos((p) => ({ ...p, on: false }))}>
<span>{customLabel}</span>
<div
className={demo ? `${styles.cursor} ${styles.isDemo}` : styles.cursor}
style={demo ? undefined : { transform: `translate(${pos.x}px,${pos.y}px)`, opacity: pos.on ? 1 : 0 }}
aria-hidden="true"
/>
</div>
</div>
);
}08 デフォルトカーソルを隠す — 計測 累積面積 1.843% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · 「カーソル エフェクト css」9選 — 一行でコピペOK
このデモが属する記事
「カーソル エフェクト css」9選 — 一行でコピペOKカーソル エフェクト cssは、既定の矢印カーソルを消し、代わりの要素をtransformで動かして見せる、CSSと数行のJavaScriptで作る演出です。この記事では9種類を選び、cursor: noneとpointermove、tra この記事のデモをすべて見る


