Magnetic Halo
A ring follows your pointer with a gentle delay, while nearby numbered circles lean toward it. It makes a simple page feel responsive and hands-on.
Interactive preview
What is Magnetic Halo?
Move your mouse across the page and a ring follows behind it. The numbered circles gently shift toward the pointer, making the whole area feel connected to your movement.
This effect works well as an opening moment for a portfolio, an experimental landing page, or a section that invites visitors to explore.
How it’s built
The page tracks the pointer inside the full-screen area and gives the ring a target position. The ring then moves toward that target over several animation frames, creating a soft trailing effect. JavaScript also checks the position of each numbered circle and shifts it a short distance toward the pointer. When the pointer leaves, the circles return to their starting positions.
Ideal for
- A portfolio homepage that introduces a designer or creative studio
- An interactive opening section for an experimental website
- A visual introduction to a digital magazine or editorial project
- A project showcase where visitors can explore the page before reading more
Accessibility
The hidden pointer can make the page harder to use for people who rely on a mouse or trackpad, and the effect has no keyboard equivalent. Keep all important content and links usable without this interaction. Consider restoring the normal pointer and offering a reduced-motion option.
Performance
The implementation runs a continuous animation loop and updates the ring as the pointer moves. It also checks and shifts only three numbered elements, so the workload is small. The always-running animation may still be worth pausing or simplifying on low-power devices.
Source code
Explore, copy and reuse the original HTML, CSS and JavaScript.
<main class="cursor-field" id="field"><div class="halo" id="halo"></div><div class="node n1">01</div><div class="node n2">02</div><div class="node n3">03</div><p>drag your attention<br><span>through the field</span></p></main>
*{box-sizing:border-box}html,body{margin:0;min-height:100%;background:#e9e5dc;font-family:Arial,sans-serif}body{min-height:100vh;display:grid;place-items:center}.cursor-field{width:100%;height:100vh;min-height:300px;position:relative;overflow:hidden;cursor:none;color:#262421}.halo{position:absolute;width:88px;height:88px;border:1px solid #262421;border-radius:50%;pointer-events:none;transform:translate(-50%,-50%);transition:width .3s,height .3s;background:#2624210a}.halo:after{content:'';position:absolute;inset:32px;background:#e85d3f;border-radius:50%}.node{position:absolute;width:50px;height:50px;border:1px solid #262421;border-radius:50%;display:grid;place-items:center;font:10px monospace;transition:transform .25s ease}.n1{left:18%;top:30%}.n2{right:20%;top:22%}.n3{left:43%;bottom:22%}.cursor-field p{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);font-size:clamp(24px,5vw,52px);line-height:.95;text-align:center;margin:0;letter-spacing:-.06em}.cursor-field p span{color:#e85d3f}.cursor-field.active .halo{width:120px;height:120px}
const field=document.querySelector('#field'),halo=document.querySelector('#halo'),nodes=[...document.querySelectorAll('.node')];let tx=0,ty=0,x=0,y=0;field.addEventListener('pointermove',e=>{const r=field.getBoundingClientRect();tx=e.clientX-r.left;ty=e.clientY-r.top;field.classList.add('active');nodes.forEach(n=>{const nr=n.getBoundingClientRect(),cx=nr.left+nr.width/2-r.left,cy=nr.top+nr.height/2-r.top;const dx=tx-cx,dy=ty-cy,d=Math.max(50,Math.hypot(dx,dy));n.style.transform=`translate(${dx/d*16}px,${dy/d*16}px)`})});field.addEventListener('pointerleave',()=>{field.classList.remove('active');nodes.forEach(n=>n.style.transform='')});function loop(){x+=(tx-x)*.16;y+=(ty-y)*.16;halo.style.left=x+'px';halo.style.top=y+'px';requestAnimationFrame(loop)}loop();