Hover Reveal

호버 리빌

Content stays hidden until you hover — then extra info or actions slide or fade in. Keeps the surface clean while still holding detail underneath.

Also known as: Reveal on hover
···
html
<div class="hrv" id="hv">
  <div class="bgimg"></div>
  <div class="overlay" id="ov"><strong>More info</strong><small>Revealed on hover</small></div>
</div>
css
.fx-dot{position:absolute;left:0;top:0;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;
  background:var(--accent);box-shadow:0 0 0 5px color-mix(in srgb,var(--accent) 18%,transparent),0 2px 6px rgba(0,0,0,.3);
  pointer-events:none;z-index:9999;transition:opacity .25s}
.hrv{position:relative;width:min(78%,260px);aspect-ratio:16/10;border-radius:14px;overflow:hidden;box-shadow:0 14px 30px rgba(0,0,0,.2)}
.bgimg{position:absolute;inset:0;background:linear-gradient(135deg,var(--accent-2),var(--accent))}
.overlay{position:absolute;inset:auto 0 0 0;padding:14px 16px;display:grid;gap:2px;color:#fff;
  background:linear-gradient(to top,rgba(0,0,0,.65),transparent);
  transform:translateY(100%);transition:transform .28s ease}
.overlay.show{transform:translateY(0)}
.overlay strong{font-size:clamp(12px,3.6vmin,15px)}
.overlay small{opacity:.85;font-size:clamp(10px,2.8vmin,12px)}
js
const fxDot=document.createElement('div');fxDot.className='fx-dot';document.body.appendChild(fxDot);
let fxAuto=true,fxX=innerWidth/2,fxY=innerHeight/2;
function fxHide(){fxDot.style.opacity='0';}
addEventListener('pointerdown',()=>{fxAuto=false;fxHide();},{capture:true});
addEventListener('pointermove',e=>{fxAuto=false;fxHide();fxX=e.clientX;fxY=e.clientY;},{capture:true});
function fxWalk(x,y,ms){
  return new Promise(res=>{
    const sx=fxX,sy=fxY,start=performance.now();
    function step(now){
      if(!fxAuto)return res();
      const p=Math.min(1,(now-start)/ms),e=1-Math.pow(1-p,3);
      fxX=sx+(x-sx)*e;fxY=sy+(y-sy)*e;
      fxDot.style.transform='translate('+fxX+'px,'+fxY+'px)';
      if(p<1)requestAnimationFrame(step);else res();
    }
    requestAnimationFrame(step);
  });
}
function fxWait(ms){
  return new Promise(res=>{
    const start=performance.now();
    function step(now){ if(!fxAuto)return res(); if(now-start<ms)requestAnimationFrame(step);else res(); }
    requestAnimationFrame(step);
  });
}

const hv=document.getElementById('hv');
const ov=document.getElementById('ov');
function render(x,y){
  const r=hv.getBoundingClientRect();
  ov.classList.toggle('show', x>r.left&&x<r.right&&y>r.top&&y<r.bottom);
}
let t=0;
(function loop(){
  if(fxAuto){
    t+=0.015;
    const r=hv.getBoundingClientRect();
    fxX=r.left+r.width/2+Math.sin(t)*r.width*0.42;
    fxY=r.top+r.height/2+Math.sin(t*0.6)*10;
    fxDot.style.transform='translate('+fxX+'px,'+fxY+'px)';
  }
  render(fxX,fxY);
  requestAnimationFrame(loop);
})();

Normally keep the overlay hidden below the element with `transform: translateY(100%)`, then slide it to `translateY(0)` on hover (or whenever the cursor is inside the card's bounds). Moving position rather than just fading `opacity` gives it a clear "sliding out" direction.

To avoid hover flickering between the card and its children (a button, say), it's easier to bind the event once on the top-level card and propagate state down internally. A 0.2–0.3s transition reads as unhurried.

Touch devices have no hover, so you need a fallback — toggle on tap, or just expose some of that information up front.

When to use

Use it for captions or action buttons over image cards, edit icons on table rows — anything secondary that doesn't need to be visible by default.