枠線がぐるっと描かれるカードを、CSS だけで作る

要点

  • @property で変数に「角度」の型を教え、0deg から 360deg へなめらかに動かす
  • conic-gradient の扇形を、mask で 1px の枠だけ残す
  • JavaScript は使わず、CSS だけで作る

使っている CSS

  • @property:型(角度)があるので、アニメーションで、角度をなめらかに変えられます。
  • conic-gradient:「白はこの角度まで、その先は透明」と指定して、扇形に塗ります。
  • mask:中身をくり抜いて、枠の 1px だけを残します。扇形が「枠線」になります。

コード

@property --line {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.card {
  position: relative;
  border: 1px solid rgb(255 255 255 / 0.14);
  border-radius: 16px;
}

.card::before {
  --line: 0deg;
  content: "";
  position: absolute;
  inset: -1px;
  padding: 1px;
  border-radius: inherit;
  background: conic-gradient(from 0deg, #fff 0deg, #fff var(--line), transparent calc(var(--line) + 0.4deg));
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}

.is-active .card::before {
  animation: line 1.6s cubic-bezier(0.65, 0, 0.35, 1) forwards;
}

@keyframes line {
  to {
    --line: 360deg;
  }
}

仕組み

  1. 重ねる:::before をカードと同じ大きさで重ね、padding: 1px を付けます。この 1px が枠線の太さです。
  2. 塗る:conic-gradient の白の範囲を、var(--line) にします。
  3. くり抜く:mask を 2 枚重ねて mask-composite: exclude。内側が抜けて、padding の 1px だけが残ります。
  4. 描く:--line を 0deg から 360deg へアニメーションさせます。

気をつけたこと

  • 開始は、クラスで切り替えます。カードが画面に入ったときに is-active を付けます。
  • 3 枚は、animation-delay を 0.2 秒ずつずらします。順番に描かれて見えます。
  • 動きを減らす設定には、最初から 360deg にします。描き終わった状態を、そのまま見せます。
  • 土台に、普通の細い枠線を付けておきます。@property に対応していない環境では、角度が変わらず、描かれないためです。最近の Chrome・Safari・Firefox で動きます。