---
title: "荧光笔划过"
summary: "链接和按钮被荧光笔从左往右涂一道：笔头是斜的、位置偏低、跨行每行单独划，离开时往右擦掉。纯 CSS。"
category: "button (按钮与控件)"
origin: "original"
stack: ["html","css"]
tags: ["按钮","链接","手作感","零依赖","微交互"]
demo: "https://zhuoqidev.com/design/demos/highlighter-button/"
url: "https://zhuoqidev.com/design/highlighter-button/"
license: "MIT"
added: "2026-10-19"
translation: "https://zhuoqidev.com/en/design/highlighter-button/index.md"
---

# 荧光笔划过

> 链接和按钮被荧光笔从左往右涂一道：笔头是斜的、位置偏低、跨行每行单独划，离开时往右擦掉。纯 CSS。

## 看点

- **划线是背景，不是伪元素**：链接的背景是 `linear-gradient(transparent 52%, 黄 52%, 黄 90%, transparent 90%)`，只占行高的下半截，像荧光笔拖得偏低；`background-size` 从 `0% 100%` 过渡到 `100% 100%`，就是从左往右涂。
- **跨行也对**：`box-decoration-break: clone` 让换行的链接每一行各自有一道，而不是第一行起、最后一行止的一整块。
- **斜笔头**：按钮上的那道是 `::before`，`skewX(-18deg)` 把两端切成斜口，再 `rotate(-2deg)` 让它不那么正；`scaleX` 从 0 到 1。
- **擦掉的方向**：离开时把 `transform-origin` 换到右边，收起来就像往右擦掉，而不是原路缩回。
- **标重点**：第三种是可切换的，`aria-pressed="true"` 时留着那道黄，适合「读过」「收藏」这类状态。

## 怎么用

1. 正文链接：拷 `.hl`，给 `<a>` 加类名；换色改 `--c`。
2. 按钮：拷 `.mk` 和它的 `::before`、`:hover` 规则；按钮背景要透明，黄条才在字下面露出来（`isolation: isolate` + `z-index: -1` 已经处理好层级）。
3. 深色模式下荧光黄要换成暗一点的饱和色（示例里是 `#7d6416`），浅色字压在上面才读得清。

## 坑

- `background-size` 的过渡在 Safari 很老的版本里会跳帧，可以接受。
- 链接默认没有下划线，记得留一个平时就看得出是链接的记号（示例用一条虚线），不然只有指上去才知道能点。
- 触屏没有 hover：链接点下去就跳走了，看不到动画，这没关系；按钮可以把同一效果挂在 `:active` 上。

## 参考实现（完整源码）

在线演示：https://zhuoqidev.com/design/demos/highlighter-button/。单文件，存成 .html 就能打开。

### index.html

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>荧光笔划过</title>
    <style>
      :root {
        --paper: #fbf8f1;
        --card: #fffdf8;
        --ink: #1c2230;
        --ink-2: #4a5163;
        --ink-3: #636877;
        --line: #e6dfd0;
        --dot: #d8d0bf;
        --blue: #2f5bd3;
        --seal: #c0391f;
        --hi: #ffe066; /* highlighter yellow */
        --hi-pink: #ffb3c7;
        --hi-green: #b6f0a8;
        --ease: cubic-bezier(0.16, 1, 0.3, 1);
        --f-sans: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
        --f-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
        color-scheme: light;
      }
      @media (prefers-color-scheme: dark) {
        :root {
          --paper: #14161b;
          --card: #1e2129;
          --ink: #ece7dc;
          --ink-2: #bab6ac;
          --ink-3: #959aa8;
          --line: #31353f;
          --dot: #2c3039;
          --blue: #8eaaff;
          --seal: #f0694e;
          /* On dark paper the ink is light, so the marker turns into a dim, saturated stripe. */
          --hi: #7d6416;
          --hi-pink: #7a2e44;
          --hi-green: #2f5f2a;
          color-scheme: dark;
        }
      }
      * { box-sizing: border-box; }
      body {
        margin: 0;
        min-height: 100vh;
        display: grid;
        place-items: center;
        padding: 32px 16px;
        background: var(--paper) radial-gradient(var(--dot) 1px, transparent 1.2px) 0 0 / 22px 22px;
        color: var(--ink);
        font: 16px/1.8 var(--f-sans);
      }
      .sheet {
        width: min(560px, 100%);
        padding: 28px;
        background: var(--card);
        border: 1px solid var(--line);
        border-radius: 14px;
        box-shadow: 0 10px 28px -14px rgba(28, 34, 48, 0.28);
      }
      .label { margin: 0 0 10px; font: 500 11px/1 var(--f-mono); letter-spacing: 0.12em; color: var(--ink-3); }
      .label + * { margin-top: 0; }
      section + section { margin-top: 26px; }

      /* ---------- 1. a link the marker sweeps across ----------
         The stroke is a background that grows from 0 to 100% wide. The gradient stops leave a band
         in the lower part of the line, like a highlighter dragged a bit low; box-decoration-break
         makes each wrapped line get its own stroke. */
      .hl {
        --c: var(--hi);
        color: inherit;
        text-decoration: none;
        background: linear-gradient(transparent 52%, var(--c) 52%, var(--c) 90%, transparent 90%) no-repeat 0 0 / 0% 100%;
        -webkit-box-decoration-break: clone;
        box-decoration-break: clone;
        padding: 0 2px;
        transition: background-size 0.45s var(--ease);
        border-bottom: 1px dashed var(--ink-3);
      }
      .hl:hover,
      .hl:focus-visible { background-size: 100% 100%; border-bottom-color: transparent; outline: none; }
      .hl.pink { --c: var(--hi-pink); }
      .hl.green { --c: var(--hi-green); }

      /* ---------- 2. a button with a marker stroke behind its label ----------
         The stroke is a pseudo-element: skewed so its ends are cut at an angle like a chisel tip,
         scaled from the left edge, and a little rotated so it is never perfectly straight. */
      .mk {
        position: relative;
        isolation: isolate;
        padding: 10px 18px;
        border: 1.5px solid var(--ink);
        border-radius: 999px;
        background: transparent;
        color: var(--ink);
        font: 600 15px/1 var(--f-sans);
        cursor: pointer;
        transition: scale 0.2s;
      }
      .mk::before {
        content: "";
        position: absolute;
        z-index: -1;
        left: 10px;
        right: 10px;
        top: 52%;
        height: 42%;
        background: var(--hi);
        border-radius: 2px;
        transform-origin: 0 50%;
        transform: rotate(-2deg) skewX(-18deg) scaleX(0);
        transition: transform 0.4s var(--ease);
      }
      .mk:hover::before,
      .mk:focus-visible::before { transform: rotate(-2deg) skewX(-18deg) scaleX(1); }
      .mk:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
      .mk:active { scale: 0.95; }
      /* Leaving the button, the stroke wipes away to the right instead of shrinking back. */
      .mk:not(:hover):not(:focus-visible)::before { transform-origin: 100% 50%; }

      /* ---------- 3. "already read" lines that stay highlighted ----------
         Toggling aria-pressed draws the stroke; it stays, like marking up a printout. */
      .pick {
        all: unset;
        cursor: pointer;
        --c: var(--hi);
        background: linear-gradient(104deg, transparent 0.6em, var(--c) 0.6em calc(100% - 0.4em), transparent calc(100% - 0.4em)) no-repeat 0 62% / 0% 58%;
        -webkit-box-decoration-break: clone;
        box-decoration-break: clone;
        transition: background-size 0.5s var(--ease);
        border-radius: 3px;
        margin: 0 -0.6em 0 -0.6em;
        padding: 0 0.4em 0 0.6em;
      }
      .pick[aria-pressed="true"] { background-size: 100% 58%; }
      .pick:focus-visible { outline: 2px dashed var(--blue); outline-offset: 2px; }
      p.quote { color: var(--ink-2); margin: 0; }
      .hint { margin: 8px 0 0; font-size: 12px; color: var(--ink-3); }

      @media (prefers-reduced-motion: reduce) {
        .hl, .mk::before, .pick { transition: none; }
      }
    </style>
  </head>
  <body>
    <main class="sheet">
      <section>
        <p class="label">01 · 链接</p>
        <p>
          正文里的链接平时只有一条虚线，指上去才被荧光笔<a class="hl" href="#">从左往右划一道</a>；换个颜色也行：<a class="hl pink" href="#">粉色</a>、<a class="hl green" href="#">绿色</a>。跨行的长链接<a class="hl" href="#">每一行都会单独划到，不会被拉成一整块奇怪的色带</a>。
        </p>
      </section>
      <section>
        <p class="label">02 · 按钮</p>
        <div style="display: flex; flex-wrap: wrap; gap: 12px">
          <button class="mk" type="button">读全文 →</button>
          <button class="mk" type="button">订阅更新</button>
        </div>
        <p class="hint">笔头是斜的，进来从左往右涂，离开往右边擦掉。</p>
      </section>
      <section>
        <p class="label">03 · 点一下标重点</p>
        <p class="quote">
          <button class="pick" type="button" aria-pressed="false">先给结论，再给依据。</button>
          读者没时间的时候，只看标黄的那几句也能明白你要说什么。
          <button class="pick" type="button" aria-pressed="true">数字放进图里，别堆在段落里。</button>
        </p>
      </section>
    </main>
    <script>
      for (const b of document.querySelectorAll(".pick")) {
        b.addEventListener("click", () => b.setAttribute("aria-pressed", String(b.getAttribute("aria-pressed") !== "true")));
      }
      for (const a of document.querySelectorAll('a[href="#"]')) a.addEventListener("click", (e) => e.preventDefault());
    </script>
  </body>
</html>
```
