H

Hou Mengwei

DIGITAL SPACE

Html+CSS|制作hover效果的菜单/列表

Greenshift有一个Hover效果的菜单/列表样式:

这个 hover 效果的原理:
1,每一行的 hover 过场(黑色背景从下往上“盖住”)+ 文字/箭头动效(纯 CSS)
2,鼠标旁边跟随的图片预览浮层(JS 监听鼠标 + data-image 切图)

以下为HTML/CSS/JS的实现方案

HTML(选几张图片 URL 填进去)

<section class="hover-menu" aria-label="Interactive hover menu">
  <div class="hover-menu__wrap">
    <a class="hm-item" href="#t1" data-image="图片 URL_1">
      <div class="hm-curtain" aria-hidden="true"></div>
      <div class="hm-content">
        <div class="hm-title">标题1</div>
        <div class="hm-desc">这里是一行说明文字(分类/介绍)。</div>
      </div>
      <div class="hm-arrow" aria-hidden="true">↗</div>
    </a>

    <a class="hm-item" href="#t2" data-image="图片 URL_2">
      <div class="hm-curtain" aria-hidden="true"></div>
      <div class="hm-content">
        <div class="hm-title">标题2</div>
        <div class="hm-desc">这里是一行说明文字(分类/介绍)。</div>
      </div>
      <div class="hm-arrow" aria-hidden="true">↗</div>
    </a>

    <a class="hm-item" href="#t3" data-image="图片 URL_3">
      <div class="hm-curtain" aria-hidden="true"></div>
      <div class="hm-content">
        <div class="hm-title">标题3</div>
        <div class="hm-desc">这里是一行说明文字(分类/介绍)。</div>
      </div>
      <div class="hm-arrow" aria-hidden="true">↗</div>
    </a>

    <a class="hm-item" href="#t4" data-image="图片 URL_4">
      <div class="hm-curtain" aria-hidden="true"></div>
      <div class="hm-content">
        <div class="hm-title">标题4</div>
        <div class="hm-desc">这里是一行说明文字(分类/介绍)。</div>
      </div>
      <div class="hm-arrow" aria-hidden="true">↗</div>
    </a>

    <a class="hm-item" href="#t5" data-image="图片 URL_5">
      <div class="hm-curtain" aria-hidden="true"></div>
      <div class="hm-content">
        <div class="hm-title">标题5</div>
        <div class="hm-desc">这里是一行说明文字(分类/介绍)。</div>
      </div>
      <div class="hm-arrow" aria-hidden="true">↗</div>
    </a>
  </div>

  <!-- 鼠标跟随预览容器 -->
  <div id="hm-cursor" class="hm-cursor" aria-hidden="true"></div>
</section>

CSS(黑幕过场 + 文字/箭头动效 + 浮动图片)

/* ========== Layout ========== */
.hover-menu{
  width: min(980px, 92vw);
  margin: 48px auto;
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
.hover-menu__wrap{
  border-top: 1px solid rgba(0,0,0,.15);
}

/* ========== Menu Items ========== */
.hm-item{
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;

  padding: 22px 6px;
  border-bottom: 1px solid rgba(0,0,0,.15);
  text-decoration: none;
  color: #111;
  overflow: hidden;
}

.hm-content{ position: relative; z-index: 2; }
.hm-title{
  font-size: 40px;
  line-height: 1.05;
  letter-spacing: -0.02em;
  transition: color .25s ease;
}
.hm-desc{
  margin-top: 10px;
  font-size: 14px;
  line-height: 1.4;
  color: rgba(0,0,0,.6);
  transition: color .25s ease;
}

.hm-arrow{
  position: relative;
  z-index: 2;
  font-size: 22px;
  transform: translate3d(0,0,0);
  transition: transform .25s ease, color .25s ease;
  user-select: none;
}

/* 黑幕 curtain:默认裁掉;hover时显示 */
.hm-curtain{
  position: absolute;
  inset: 0;
  background: #111;
  z-index: 1;
  pointer-events: none;

  clip-path: inset(100% 0 0 0);
  transition: clip-path .45s cubic-bezier(.25,1,.5,1);
}

.hm-item:hover .hm-curtain{
  clip-path: inset(0 0 0 0);
}
.hm-item:hover .hm-title{ color: #fff; }
.hm-item:hover .hm-desc{ color: rgba(255,255,255,.65); }
.hm-item:hover .hm-content{ transform: translateX(18px); transition: transform .25s ease; }
.hm-item:hover .hm-arrow{
  color: #fff;
  transform: translate(-14px, 0) rotate(45deg) scale(1.08);
}

/* ========== Cursor Preview ========== */
.hm-cursor{
  position: fixed;
  top: 0; left: 0;
  width: 320px;
  height: 220px;
  border-radius: 14px;
  overflow: hidden;
  background: #111;
  box-shadow: 0 30px 60px rgba(0,0,0,.25);
  pointer-events: none;
  z-index: 9999;

  opacity: 0;
  transform: translate3d(0,0,0) scale(.92);
  transition: opacity .25s ease;
  display: none; /* 移动端默认不显示 */
}

@media (min-width: 768px){
  .hm-cursor{ display: block; }
}

.hm-cursor img{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 图片切换的“擦拭”动画(进/出) */
@keyframes hmWipeIn{
  from { clip-path: inset(100% 0 0 0); transform: scale(1.02); }
  to   { clip-path: inset(0 0 0 0); transform: scale(1); }
}
@keyframes hmWipeOut{
  from { clip-path: inset(0 0 0 0); opacity: 1; }
  to   { clip-path: inset(0 0 100% 0); opacity: 0.9; }
}

.hm-img-enter{ animation: hmWipeIn .65s cubic-bezier(.25,1,.5,1) both; z-index: 2; }
.hm-img-exit { animation: hmWipeOut .65s cubic-bezier(.25,1,.5,1) both; z-index: 1; }

/* 减少动态:尊重系统设置 */
@media (prefers-reduced-motion: reduce){
  .hm-curtain, .hm-arrow, .hm-title, .hm-desc { transition: none !important; }
  .hm-img-enter, .hm-img-exit { animation: none !important; }
}

.hm-item{
  padding: clamp(16px, 1.2vw + 12px, 28px) 6px;
  gap: clamp(16px, 2vw, 28px);
}

.hm-content{
  max-width: 70ch; /* 防止说明太长撑爆 */
}

JS(监听 hover + 鼠标跟随 + 切图)

<script>
(() => {
  const cursor = document.getElementById('hm-cursor');
  const items = document.querySelectorAll('.hm-item');
  if (!cursor || !items.length) return;

  // 跟随参数
  const CURSOR_W = 320;
  const CURSOR_H = 220;
  const PAD = 24;
  const SHIFT_X = 360; // 鼠标右侧偏移
  const LERP = 0.12;   

  let mouseX = 0, mouseY = 0;
  let x = 0, y = 0;
  let hovering = false;
  let activeSrc = null;
  let rafId = null;

  // 预加载图片(避免 hover 时空白)
  items.forEach(a => {
    const url = a.getAttribute('data-image');
    if (url) { const img = new Image(); img.src = url; }
  });

  const onMove = (e) => { mouseX = e.clientX; mouseY = e.clientY; };
  document.addEventListener('mousemove', onMove, { passive: true });

  items.forEach(item => {
    item.addEventListener('mouseenter', () => {
      hovering = true;
      cursor.style.opacity = '1';
      const url = item.getAttribute('data-image');
      if (url) swapImage(url);
      start();
    });

    item.addEventListener('mouseleave', () => {
      hovering = false;
      cursor.style.opacity = '0';
    });

    // 键盘 focus 也能看见(无鼠标时不跟随,只显示在右侧固定位置)
    item.addEventListener('focus', () => {
      const url = item.getAttribute('data-image');
      if (url) swapImage(url);
      hovering = true;
      cursor.style.opacity = '1';
      // 固定放到视口右侧中间
      x = Math.min(window.innerWidth - CURSOR_W - PAD, window.innerWidth * 0.55);
      y = Math.max(PAD, (window.innerHeight - CURSOR_H) / 2);
      cursor.style.transform = `translate3d(${x}px, ${y}px, 0) scale(1)`;
    });

    item.addEventListener('blur', () => {
      hovering = false;
      cursor.style.opacity = '0';
    });
  });

  function start(){
    if (rafId) return;
    rafId = requestAnimationFrame(tick);
  }

  function tick(){
    // 目标位置:鼠标右侧 + 垂直居中
    let tx = mouseX + SHIFT_X;
    let ty = mouseY - CURSOR_H / 2;

    // 防止出屏(右边和上下)
    const maxLeft = window.innerWidth - CURSOR_W - PAD;
    const maxTop = window.innerHeight - CURSOR_H - PAD;

    tx = Math.max(PAD, Math.min(tx, maxLeft));
    ty = Math.max(PAD, Math.min(ty, maxTop));

    // 平滑跟随
    x += (tx - x) * LERP;
    y += (ty - y) * LERP;

    const scale = hovering ? 1 : 0.92;
    cursor.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale})`;

    rafId = requestAnimationFrame(tick);
  }

  function swapImage(url){
    if (activeSrc === url) return;
    activeSrc = url;

    const img = document.createElement('img');
    img.src = url;
    img.alt = ""; // 装饰性预览图
    img.className = 'hm-img-enter';

    // 旧图出场
    [...cursor.children].forEach(old => {
      old.classList.remove('hm-img-enter');
      old.classList.add('hm-img-exit');
      setTimeout(() => old.remove(), 700);
    });

    cursor.appendChild(img);
  }

  // 视口变化时避免出屏
  window.addEventListener('resize', () => {
    x = Math.min(x, window.innerWidth - CURSOR_W - PAD);
    y = Math.min(y, window.innerHeight - CURSOR_H - PAD);
  }, { passive: true });
})();
</script>

效果

优化方案:
1,把箭头文字  换成 SVG箭头
2,标题字号自适应:用 clamp() 做流体排版

把每一行里的:

<div class=”hm-arrow” aria-hidden=”true”>↗</div>

替换成:

<div class="hm-arrow" aria-hidden="true">
<svg class="hm-arrow__svg" viewBox="0 0 24 24" fill="none">
<path d="M7 17L17 7" />
<path d="M9 7h8v8" />
</svg>
</div>

追加到现有 CSS 里:

/* 标题:流体字号(手机小、桌面大) */
.hm-title{
  font-size: clamp(26px, 3.2vw + 10px, 56px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  transition: color .25s ease;
}

/* 说明文字也稍微流体一点(更高级) */
.hm-desc{
  margin-top: 10px;
  font-size: clamp(13px, 0.55vw + 11px, 16px);
  line-height: 1.45;
  color: rgba(0,0,0,.6);
  transition: color .25s ease;
}

/* SVG 箭头 */
.hm-arrow{
  position: relative;
  z-index: 2;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  transform: translate3d(0,0,0);
  transition: transform .25s ease, color .25s ease;
  user-select: none;
}

.hm-arrow__svg{
  width: 22px;
  height: 22px;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* hover 时箭头更像示例:向左上“抽走”+ 轻微旋转放大 */
.hm-item:hover .hm-arrow{
  color: #fff;
  transform: translate(-14px, 0) rotate(45deg) scale(1.08);
}

效果

进阶:Html+CSS|制作hover效果的菜单/列表:自动关联最新文章