New Article: CSSのみで外部リンクにアイコンをつける

This commit is contained in:
moris 2025-03-02 17:03:23 +09:00
parent 91bd721451
commit d3e1d9a393

133
Posts/external-link-icon.md Normal file
View File

@ -0,0 +1,133 @@
---
title: CSSのみで外部リンクにアイコンをつける
description:
thumbnail: 'data:image/svg+xml;utf-8,<svg viewBox="0 0 199 149" xmlns="http://www.w3.org/2000/svg"><path d="m61 124c-6 0-11-5-11-11v-77c0-6 5-11 11-11h39v11h-39v77h77v-38h11v38c0 6-5 11-11 11zm26-29l-8-8l51-51h-20v-11h39v39h-11v-20z"/></svg>'
emoji:
date: 2025-03-02
category: Tech
tags: [CSS]
index: true
published: true
---
<details open>
<summary> 完成品 </summary>
https://mi.moris.day/@moris
[ソースコード](https://git.moris.day/moris/day.moris.blog/src/commit/9bbd127c791bbc93cbe1b5d4fb2d115cc3de4998/src/lib/components/Markdown.css#L44)
</details>
## 要件
1. 外部リンクにのみアイコンをつける
2. 訪問済みのリンクは色を変える
3. とにかく軽量 → Font Awesomeなどのwebフォント、JavaScript等は使わずにhtml&CSSのみで実装
## 表示するアイコン
[Google Material Symbols](https://fonts.google.com/icons)のOpen In Newというアイコンを使いました。人力圧縮でオリジナルの300Bから190Bまで圧縮されています。
<svg style="display:block; width:128px; height:128px; margin:auto" viewBox="0 0 99 99" xmlns="http://www.w3.org/2000/svg"><path d="M10,99C5,99 0,94 0,89V10C0,5 5,0 10,0H50V10H10V89H89V50H99V89c0 5-5 10-10 10ZM37,70l-7-7L80,10H60V0H99V39H89V19Z"/></svg>
```
<svg viewBox="0 0 99 99" xmlns="http://www.w3.org/2000/svg"><path d="M10,99C5,99 0,94 0,89V10C0,5 5,0 10,0H50V10H10V89H89V50H99V89c0 5-5 10-10 10ZM37,70l-7-7L80,10H60V0H99V39H89V19Z"/></svg>
```
オリジナル: https://fonts.google.com/icons?selected=Material+Symbols+Outlined:open_in_new:FILL@0;wght@400;GRAD@0;opsz@24
## 外部リンクに一致するCSSセレクタ
このブログは[rehype-external-links](https://github.com/rehypejs/rehype-external-links)というパッケージで外部リンクに`target="_blank"`を設定しています。大体のサイトで同じだと思うので、これを属性セレクタで一致させます。正確には`target="_blank"`は新規タブで開くことを意味しているのでサイト内リンクの可能性もあるのですが、アイコンの名前もOpen In Newですし、まぁいいでしょう。
```css
a[target="_blank"] {
/* ここにスタイル */
}
```
## アイコン画像をつける
CSSセレクタは完成したのでアイコンを付けましょう。これは`::after`擬似要素一択ですね、`content`に`<svg>`要素を入れて.....
![svgがテキストとして表示される](https://moris.day/files/img/external-link-1.avif)
😭😭😭
どうやら`content`プロパティにhtml要素を入れることはできないようです。
https://developer.mozilla.org/ja/docs/Web/CSS/content
しかし`url()`で画像を入れることはできるようなのでdataURLでsvgを入れると
![svgが正しく表示される](https://moris.day/files/img/external-link-2.avif)
成功しました✨
ですが実はこれ、2つ目の要件「訪問済みのリンクは色を変える」を達成できません。(詳細は後述)
そのため、試行錯誤をした結果`background-color`を設定して`mask-image`で切り取ることにしました。
```css
a[target="_blank"]::after {
background-color: blue;
mask-image: url('data:image/svg+xml;utf-8,<svg viewBox="0 0 99 99" xmlns="http://www.w3.org/2000/svg"></svg>');
}
```
## 訪問済みのリンクで色を変える
当初、CSSで訪問済みの要素に一致させるにはそう`:visited`ということで、こんな感じのCSSにしたのですが、全て未訪問として表示されてしまいました。
```css
a::after {
content: url('data:image/svg+xml;utf-8,<svg fill="未訪問のときの色"></svg>');
}
a:visited::after {
content: url('data:image/svg+xml;utf-8,<svg fill="訪問済みのときの色"></svg>');
/* 効かない */
}
```
MDNによると、プライバシー保護のため`:visited`擬似クラスで適用されたスタイルには制限があり、利用可能なプロパティは一部の色に関する物のみのようです。
https://developer.mozilla.org/ja/docs/Web/CSS/:visited#プライバシー上の制約
`color`プロパティは使えるのでFont Awesomeなどのアイコンフォントなら`:visited`で色を変えることができますが、要件の通りwebフォントはナシなので却下。
また、`fill`プロパティも使えますが`background-image`などで設定されたsvgには適用できないのでこれも却下。
そのため先述の`background-color`で背景色を設定して`mask-image`で切り取るという方針になったのです。
```css
a[target="_blank"]::after {
content: '';
mask-image: url('data:image/svg+xml;utf-8,<svg viewBox="0 0 99 99" xmlns="http://www.w3.org/2000/svg"></svg>');
background-color: blue;
}
a[target="_blank"]:visited::after {
background-color: purple
}
```
## 実装
これで必要なものは揃ったので微調整して完成です✨
```css
a {
color: blue;
}
a:visited {
color: purple;
}
a[target="_blank"]::after {
content: '';
display: inline-block;
width: .5em;
height: .5em;
margin-inline: 2px;
background-color: currentColor;
mask-image: url('data:image/svg+xml;utf-8,<svg viewBox="0 0 99 99" xmlns="http://www.w3.org/2000/svg"><path d="M10,99C5,99 0,94 0,89V10C0,5 5,0 10,0H50V10H10V89H89V50H99V89c0 5-5 10-10 10ZM37,70l-7-7L80,10H60V0H99V39H89V19Z"/></svg>');
vertical-align: super; /* テキストよりちょっと上に配置 */
}
```
### currentColor!?
突然出てきた`currentColor`ですが、これは`color`プロパティの値を表すキーワード値で、要するに文字と一緒の色にするよってことです。
「テキストとアイコンで別々に色を設定」ではなく「テキストで色を設定し、アイコンは`currentColor`で追従」とすることでちょっとだけシンプルに記述できます。
このcurrentColor、borderやsvgのfillなどに便利なのでぜひ覚えておいてください。