Cursor Ring Follower
Dual-layer cursor: dot tracks instantly, ring trails via lerp.
beweeg hier
How to use it
- Paste the HTML where the components should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Cursor Ring Follower · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="crf-stage"><div class="crf-grid"><button class="crf-hot">Hover</button><button class="crf-hot">Drag</button><button class="crf-hot">Click</button></div><div class="crf-ring"></div><div class="crf-dot"></div><span class="crf-hint">beweeg hier</span></div>
</div>/* Cursor Ring Follower · NedDev · eigen werk. */
.crf-stage{position:absolute;inset:0;overflow:hidden;cursor:none;background:#fff;font-family:'Switzer',system-ui,sans-serif;display:flex;align-items:center;justify-content:center}
.crf-grid{display:flex;gap:14px}
.crf-hot{cursor:none;padding:14px 22px;border:1px solid rgba(10,10,10,.12);background:#fff;color:#0a0a0a;border-radius:12px;font-size:14px;font-weight:600;transition:background .2s,color .2s}
.crf-hot:hover{background:#0a0a0a;color:#fff}
.crf-ring{position:absolute;top:0;left:0;width:38px;height:38px;border:1.5px solid #F5480F;border-radius:50%;pointer-events:none;transform:translate(-50%,-50%);transition:width .25s,height .25s,background .25s;z-index:5;mix-blend-mode:multiply}
.crf-dot{position:absolute;top:0;left:0;width:6px;height:6px;background:#0a0a0a;border-radius:50%;pointer-events:none;transform:translate(-50%,-50%);z-index:6}
.crf-stage.is-hot .crf-ring{width:60px;height:60px;background:rgba(245,72,15,.08)}
.crf-hint{position:absolute;bottom:14px;left:14px;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:rgba(10,10,10,.4)}
@media(hover:none){.crf-ring,.crf-dot{display:none}.crf-stage{cursor:auto}.crf-hot{cursor:pointer}}// Cursor Ring Follower · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
root=root.querySelector('.crf-stage')||root;var ring=root.querySelector('.crf-ring'),dot=root.querySelector('.crf-dot');var mx=0,my=0,rx=0,ry=0;var reduce=matchMedia('(prefers-reduced-motion:reduce)').matches;
root.addEventListener('mousemove',function(e){var r=root.getBoundingClientRect();mx=e.clientX-r.left;my=e.clientY-r.top;dot.style.transform='translate('+mx+'px,'+my+'px) translate(-50%,-50%)';if(reduce)ring.style.transform=dot.style.transform;});
root.addEventListener('mouseleave',function(){ring.style.opacity='0';dot.style.opacity='0';});root.addEventListener('mouseenter',function(){ring.style.opacity='1';dot.style.opacity='1';});
root.querySelectorAll('.crf-hot').forEach(function(b){b.addEventListener('mouseenter',function(){root.classList.add('is-hot');});b.addEventListener('mouseleave',function(){root.classList.remove('is-hot');});});
if(!reduce){(function loop(){rx+=(mx-rx)*.16;ry+=(my-ry)*.16;ring.style.transform='translate('+rx+'px,'+ry+'px) translate(-50%,-50%)';requestAnimationFrame(loop);})();}
}
init(document.querySelector('.jouw-container'));
More components
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →