diff --git a/Posts/external-link-icon.md b/Posts/external-link-icon.md new file mode 100644 index 0000000..d63058f --- /dev/null +++ b/Posts/external-link-icon.md @@ -0,0 +1,133 @@ +--- +title: CSSのみで外部リンクにアイコンをつける +description: +thumbnail: 'data:image/svg+xml;utf-8,' +emoji: +date: 2025-03-02 +category: Tech +tags: [CSS] +index: true +published: true +--- + + + +
+ 完成品 + +https://mi.moris.day/@moris +[ソースコード](https://git.moris.day/moris/day.moris.blog/src/commit/9bbd127c791bbc93cbe1b5d4fb2d115cc3de4998/src/lib/components/Markdown.css#L44) +
+ + +## 要件 +1. 外部リンクにのみアイコンをつける +2. 訪問済みのリンクは色を変える +3. とにかく軽量 → Font Awesomeなどのwebフォント、JavaScript等は使わずにhtml&CSSのみで実装 + + + +## 表示するアイコン +[Google Material Symbols](https://fonts.google.com/icons)のOpen In Newというアイコンを使いました。人力圧縮でオリジナルの300Bから190Bまで圧縮されています。 + + +``` + +``` +オリジナル: 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がテキストとして表示される](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,'); +} +``` + + + +## 訪問済みのリンクで色を変える +当初、CSSで訪問済みの要素に一致させるにはそう`:visited`!ということで、こんな感じのCSSにしたのですが、全て未訪問として表示されてしまいました。 +```css +a::after { + content: url('data:image/svg+xml;utf-8,'); +} + +a:visited::after { + content: url('data:image/svg+xml;utf-8,'); + /* 効かない */ +} +``` + +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,'); + 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,'); + vertical-align: super; /* テキストよりちょっと上に配置 */ +} +``` + +### currentColor!? +突然出てきた`currentColor`ですが、これは`color`プロパティの値を表すキーワード値で、要するに文字と一緒の色にするよってことです。 +「テキストとアイコンで別々に色を設定」ではなく「テキストで色を設定し、アイコンは`currentColor`で追従」とすることでちょっとだけシンプルに記述できます。 + +このcurrentColor、borderやsvgのfillなどに便利なのでぜひ覚えておいてください。 \ No newline at end of file