CSSのborderにグラデーションと動きをつけたい時にハマった話と解決策まとめ

はじめまして、DXOでUI/UXやフロントエンドを担当しているkoyaです。

フロントエンド開発をそこそこ経験してきたつもりでしたが、最近まで`border`にグラデーションやアニメーションは適用できないということを知りませんでした…

この記事では、私が実際にハマった失敗談と、その解決方法を整理しています。同じような問題に直面している方の参考になれば嬉しいです。


---

## 1. ハマったポイント：`border` はグラデーション非対応

最初に以下のように書きました。

```css
.box {
  border: 4px solid linear-gradient(to right, #82BEF0, #D2A5EB);
}
```

結果、何もエラーが出ずにborderも表示されませんでした。CSSの仕様では、`border` はレイアウト領域であり、装飾は `background` プロパティに任されています。

---

## 2. 試したけど微妙だった方法：`border-image`

次に、画像として扱える `border-image` を試しました。

```css
.box {
  border: 2px solid transparent;
  border-image: linear-gradient(to right, #82BEF0, #D2A5EB) 1;
}
```

しかし、以下の問題がありました。

- 点線や破線（`dashed`/`dotted`）が使えない
- 角丸がきれいに表示されない
- アニメーションができない

静的なデザインであれば使えますが、動的なデザインには向きませんでした。

---

## 3. 最終的に辿り着いた方法：擬似要素 + `mask`

擬似要素を使い、グラデーション背景を設定してマスクで枠線だけを残す方法です。

これにより、

- 点線や破線を自由に作れる
- アニメーションも可能
- 角丸にも対応できる

といった柔軟なデザインが実現できました。

---

## 4. 実際の実装例

### グラデーション下線付きリンク

リンクの下線をグラデーション+アニメーションにするのも意外と苦労しました。当初は 疑似要素で簡単に設定できると思っていましたが、結局うまくいかず、試行錯誤の末にたどり着いたのがこの方法です。

`background-image` を使ってグラデーション下線を作成し、色をアニメーションさせるために、`@keyframes` を使う必要があります。

#### HTML

```html
<div class="gradient">
  <a href="#">これはダミーテキストです。</a>
</div>
```

#### CSS

```css
.gradient a {
  color: #82BEF0;
  position: relative;
  text-decoration: none;
  background-image: linear-gradient(90deg, #82BEF0, #82BEF0);
  background-repeat: no-repeat;
  background-position: left bottom;
  background-size: 0 1px;
  transition: background-size 0.3s;
  animation: randomColor 10s infinite ease-in-out;
}

.gradient a:hover {
  background-size: 100% 1px;
}

@keyframes randomColor {
  0% { color: #82BEF0; background-image: linear-gradient(90deg, #82BEF0, #82BEF0); }
  50% { color: #AADCB9; background-image: linear-gradient(90deg, #AADCB9, #AADCB9); }
  100% { color: #D2A5EB; background-image: linear-gradient(90deg, #D2A5EB, #D2A5EB); }
}
```

---

### グラデーション × 点線

擬似要素を縦横それぞれに用意し、縞模様のグラデーションを作成。マスクを使用して枠線のみ表示しました。

SVGの方が無難に対応出来そうな気もしますが、CSSのみで柔軟に対応できたのはこの方法だけでした。

#### HTML

```html
<div class="dotted-box">
  <p>これはダミーテキストです。</p>
</div>
```

#### CSS

```css
.dotted-box {
  margin: 30px 0;
  width: 100%;
  height: auto;
  border-radius: 16px;
  position: relative;
  border: 2px solid transparent;
}

.dotted-box::before {
  content: '';
  position: absolute;
  top: 0;
  left: 3px;
  right: 3px;
  bottom: 0;
  border-radius: 16px;
  padding: 3px;
  background: linear-gradient(270deg, #D2A5EB, #AADCB9, #82BEF0);
  -webkit-mask: repeating-linear-gradient(
    90deg,
    #fff 0px 2px,
    rgba(255, 255, 255, 0) 2px 6px
  );
  mask: repeating-linear-gradient(
    90deg,
    #fff 0px 2px,
    rgba(255, 255, 255, 0) 2px 6px
  );
  -webkit-mask-composite: destination-out;
  mask-composite: exclude;
  transition: opacity 0.3s ease-out;
  z-index: -1;
}

.dotted-box::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 0;
  right: 0;
  bottom: 3px;
  border-radius: 16px;
  padding: 3px;
  background: linear-gradient(270deg, #D2A5EB, #AADCB9, #82BEF0);
  mask: repeating-linear-gradient(
    0deg,
    #fff 0px 2px,
    rgba(255, 255, 255, 0) 2px 6px
  );
  pointer-events: none;
  z-index: -1;
}

.dotted-box p {
  position: relative;
  padding: 16px;
  z-index: 1;
}

.dotted-box p::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  right: 2px;
  bottom: 2px;
  border-radius: 17px;
  background: #fff;
  pointer-events: none;
  z-index: -1;
}

.dotted-box p {
  margin: 0;
}
```

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="raaBOBd" data-pen-title="sample,border-image" data-user="dxo-koya" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/dxo-koya/pen/raaBOBd">
  sample,border-image</a> by koya (<a href="https://codepen.io/dxo-koya">@dxo-koya</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

---

## 5. 実際に実装したサイト

実際の挙動は、以下のサイトで確認できます。
細かいアニメーションもグラデーションを加えたりしているので、各種リンクをhoverしてみてください！

[サイトを見る](https://dxo.co.jp/blog)

---

## 6. まとめ

`border` に直接グラデーションやアニメーションを適用できませんが、擬似要素とマスクを組み合わせることで表現できます。
ちょっと力技感がありますが、SVG以外だとこれくらいしか思いつきませんでした。
静的な場合は `border-image`、動的で複雑な表現には擬似要素+マスクの方法がおすすめです。

この記事が誰かの参考になれば幸いです。他に良い方法をご存知の方は、ぜひコメントで教えてくださると助かります。