Calling card · No.07

用纯 CSS 复刻 P5 的斜切菜单

≈ 2 MIN READ前端设计

01先拆解,再写代码

P5 主菜单的每一项看起来像被撕下来贴上去的,实际上拆开只有三个 CSS 属性在工作:

  1. transform: skewX(-10deg) —— 让方块变斜
  2. clip-path: polygon(...) —— 裁掉一个角,形成纸片
  3. 内层再反向 skewX(10deg) —— 让文字保持正立

第三条是新手最容易漏的:外层斜切后,文字也会跟着歪,所以内容要包一层反向变换。

html
<a class="p5-btn" href="#"><span>阅读全文</span></a>
css
.p5-btn { transform: skewX(-10deg); }
.p5-btn > span { display: inline-block; transform: skewX(10deg); }

02选中态:一块滑出来的黑板

真正让菜单「活」的是选中态。不要用边框变色,做法是给按钮加一个伪元素当底板,用宽度动画滑出来:

css
.nav-item { position: relative; padding-left: 24px; }
.nav-item::before {
  content: "";
  position: absolute; left: 4px; top: 50%;
  width: 10px; height: 10px;
  background: var(--red);
  transform: translateY(-50%) skewX(-20deg);
  opacity: 0; transition: .16s;
}
.nav-item:hover::before, .nav-item.on::before { opacity: 1; }

03撕纸字:中英文都要能玩

英文可以换字体(本站用的是 Persona5FanRansom),但中文没有对应字体,所以真正的解法是按字符包 span,给每个字随机旋转和底色:

js
// 把「怪盗笔记」拆成 4 个可独立旋转的字
[..."怪盗笔记"].forEach((ch, i) => {
  const s = document.createElement('span');
  s.className = 'ch ' + (i % 3 === 0 ? 'box' : i % 3 === 1 ? 'red' : '');
  s.style.setProperty('--t', `rotate(${(i % 2 ? 1 : -1) * 1.6}deg)`);
  s.textContent = ch;
  host.appendChild(s);
});
双叶

把文字拆成 DOM 节点的写法性能不太行吧,长标题会掉帧。

我

所以只拆标题,正文一个节点都不动——首屏 JS 至今不到 30KB。

04转场:一帧都不能拖

P5 的切屏是「斜切三色擦除」,用三条动画延迟 70ms 的色块就能复刻:

css
@keyframes wipeSweep {
  0%   { transform: skewX(-12deg) translateX(-190%); }
  55%  { transform: skewX(-12deg) translateX(0); }
  100% { transform: skewX(-12deg) translateX(400%); }
}

时长定在 450ms:再慢就变成等待,再快就看不见颜色了。

05半调网点:一张背景图都不用

P5 的红色背景上永远铺着一层细网点,用 CSS 渐变就能生成:

css
.hero-dots {
  background-image: radial-gradient(rgba(0,0,0,.55) 1.6px, transparent 1.7px);
  background-size: 9px 9px;
  mix-blend-mode: multiply;   /* 关键:让网点吃进下层颜色 */
}

两点经验:网点间距别超过 10px,否则会看到明显的摩尔纹;动画只改 background-position,永远不要动尺寸——那是唯一不会引发布局抖动的做法。

06性能与可访问性的底线

项目目标做法
动画开销全部在合成层只动 transform / opacity
首屏 JS< 30KB不引框架,只渲染可见内容
键盘可达Tab 走全站用语义标签,不拦截默认行为
减弱动效系统开关生效prefers-reduced-motion
双叶

所以到底该不该用 clip-path?

我

能用 transform 就别用它——低端手机上会触发整块重绘。先问自己:这个形状真的必须裁吗?