ブログ

CSSで実装する角丸ボタンの枠線が1周するアニメーション

角丸ボタンの枠線が1周するアニメーションを実装する機会があったので、デモと仕組みを残しておきます。 四角いボタンであれば擬似要素などで外周のアニメーションを表現できますが、角丸の場合は少し工夫が必要でした。

完成デモ

早速ですが、以下がデモです。ボタンにホバーするとアニメーションが始まります。

デモ

以下の3パターンを実装しています。

  • 単色の枠線 + 単色のリングアニメーション
  • 単色の枠線 + グラデーションのリングアニメーション
  • グラデーションの枠線 + グラデーションのリングアニメーション(少しわかりにくいかもしれません…)

いずれも基本的な実装方法は同じで、CSS変数をカスタマイズすることでバリエーションを表現しています。

実装コード

実装に使用したコードです。必要な部分のみ抜粋しています。

HTML

index.html
<p>シンプルボーダーアニメーション</p>
<a href="/" class="button"><span class="button__inner">ボタンが入ります</span></a>
<p>グラデーションリングアニメーション</p>
<a href="/" class="button" data-type="gradient-ring"><span class="button__inner">ボタンが入ります</span></a>
<p>グラデーションボーダー + グラデーションリングアニメーション</p>
<a href="/" class="button" data-type="gradient-border"><span class="button__inner">ボタンが入ります</span></a>

CSS

style.css
@property --ring-angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.button {
/* 色関連 */
--_bg: #fff; /* ボタン内部の背景色 */
--_border-color: #ddd; /* デフォルトのボーダー色 */
--_gradient-from: #10a15c; /* グラデーション開始色 */
--_gradient-to: #10a15c; /* グラデーション終了色 */
/* サイズ関連 */
--_border-width: 3px;
--_radius: 10px;
/* アニメーション関連 */
--_duration: 0.75s;
--_easing: cubic-bezier(0.4, 0, 0.2, 1);
--_start-angle: 0deg; /* リングアニメーションの開始角度 */
/* レイヤー構成(前面(--_foreground)と後面(--_background)) */
--_foreground: conic-gradient(
from var(--_start-angle),
var(--_gradient-from) 0,
var(--_gradient-to) var(--ring-angle),
transparent var(--ring-angle) 360deg
);
--_background: var(--_border-color);
/* スタイル適用 */
display: inline-block;
background: var(--_foreground), var(--_background);
padding: var(--_border-width);
border-radius: var(--_radius);
transition: --ring-angle var(--_duration) var(--_easing);
}
.button__inner {
display: block;
background: var(--_bg);
padding: 12px 24px;
border-radius: calc(var(--_radius) - var(--_border-width));
}
@media (any-hover: hover) {
.button:hover {
--ring-angle: 360deg; /* リングアニメーションの終了角度 */
}
}
.button[data-type="gradient-ring"] {
--_gradient-from: #3b82f6;
--_gradient-to: #8b5cf6;
}
.button[data-type="gradient-border"] {
--_border-color: linear-gradient(
135deg,
var(--_gradient-from),
var(--_gradient-to)
);
--_gradient-from: #10b981;
--_gradient-to: #3b82f6;
}

仕組みの解説

ボタンの構造とアニメーションの仕組みについて解説します。

多重背景による枠線の表現

今回の枠線はborderプロパティではなく、背景の多重レイヤーで実装しています。
仕組みは以下の通りです:

  1. ボタン要素に背景色を設定する
  2. ボタン要素にpaddingを設定して内側に余白を作る(これが枠線の幅になる)
  3. 内側のspan要素で白背景を配置し、中央部分を覆う

この結果、paddingの余白部分だけボタンの背景色が見え、それが枠線のように見えます。

style.css
.button {
background: var(--_foreground), var(--_background);
padding: var(--_border-width); /* 枠線の幅 */
border-radius: var(--_radius);
}
.button__inner {
display: block;
background: var(--_bg); /* 内側を白で覆う */
padding: 12px 24px;
border-radius: calc(var(--_radius) - var(--_border-width));
}

background-clipbackground-originを使ったborder-box/padding-boxの手法でも同様の表現が可能ですが、今回はシンプルさを優先してspan要素を使用しています。

背景の2層構造

ボタン要素にはbackgroundプロパティで2つの背景レイヤーを重ねています。

style.css
.button {
--_foreground: conic-gradient(
from var(--_start-angle),
var(--_gradient-from) 0,
var(--_gradient-to) var(--ring-angle),
transparent var(--ring-angle) 360deg
);
--_background: var(--_border-color);
background: var(--_foreground), var(--_background);
}
レイヤー役割内容
前面(--_foregroundアニメーションconic-gradient()で生成される円形グラデーション
後面(--_background通常の枠線単色またはグラデーションの背景色

最初は後面のbackgroundを表示しておき、アニメーション時に上に重ねたconic-gradient()を表示しています。

conic-gradient()とはCSSの関数の一つで、MDNによると中心点の周りを回りながら色が変化する画像を生成します。円柱を真上から見たような色の変化を表現することができます。

conic-gradient()関数で表現した色の変化
conic-gradient()関数で表現した色の変化

@propertyによるアニメーションの実現

アニメーションの核心は、conic-gradient()のカラーストップ位置である--ring-angle0度から360度まで変化させることです。

style.css
@property --ring-angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.button {
--_foreground: conic-gradient(
from var(--_start-angle),
var(--_gradient-from) 0,
var(--_gradient-to) var(--ring-angle), /* ← ここが変化する */
transparent var(--ring-angle) 360deg
);
transition: --ring-angle var(--_duration) var(--_easing);
}
@media (any-hover: hover) {
.button:hover {
--ring-angle: 360deg; /* 0deg → 360deg に変化 */
}
}

通常、CSS変数(カスタムプロパティ)は文字列として扱われるため、transitionでアニメーションさせることができません。

style.css
/* これだけではアニメーションしない */
.button {
--ring-angle: 0deg;
transition: --ring-angle 0.75s;
}
@media (any-hover: hover) {
.button:hover {
--ring-angle: 360deg; /* 瞬時に切り替わる */
}
}

しかし、@propertyを使って型(syntax)と初期値を定義することで、ブラウザがその値を数値として認識し、中間値を補間してアニメーションできるようになります。

プロパティ説明
syntax: "<angle>"この変数は角度の値であることを定義
inherits: false子要素に継承しない
initial-value: 0deg初期値を0度に設定

これにより、--ring-angleが0度から360度までスムーズに変化し、円周に沿ってグラデーションが進むアニメーションが実現します。

注意点

今回のデモでは--_start-angle: 0degとして、アニメーションの開始位置を12時の方向(真上)に設定しています。 この角度を変更することで、開始位置を調整できます:

角度開始位置
0deg12時(真上)
90deg3時(右)
180deg6時(真下)
270deg または -90deg9時(左)

ただし、ボタンの左上角など、特定のコーナーから常に開始するという実装は困難です。

これはconic-gradient()要素の中心を基準に円形のグラデーションを生成するためです。ボタンの幅(テキストの長さ)が変わると、中心位置も変わるため、角の位置も相対的に変化してしまいます。

固定幅のボタンであれば、個別に--_start-angleを微調整することで、視覚的に角から始まっているように見せることは可能です。ただし、レスポンシブデザインやテキストの可変幅には対応できないため、実用的には12時や9時などの特定の基準位置から開始した方が良さそうです。

参考サイト