リンクカードを実装するなら代替リンクも残さないか?

2025-11-05


リンクカードを実装した。

この行は変換されます。

https://github.com/ras0q

この行は変換されません。

[@ras0q](https://github.com/ras0q)

が、以下のように変換される。

<p>この行は変換されます。</p>

<p class="sr-only"><strong>ras0q - Overview</strong></p>
<p class="sr-only">🦖🦖🦖💨. ras0q has 142 repositories available. Follow their code on GitHub.</p>

<p><a href="https://github.com/ras0q" target="_blank" rel="noopener">https://github.com/ras0q</a></p>

<figure class="linkcard bg-neutral-200 dark:bg-neutral-700 bg-opacity-50!">
  <img src="https://avatars.githubusercontent.com/u/66677201?v=4?s=400" alt="ras0q - Overviewのサムネイル" loading="lazy">
  <figcaption>
    <a href="https://github.com/ras0q" target="_blank" rel="noopener">ras0q - Overview</a>
    <p>🦖🦖🦖💨. ras0q has 142 repositories available. Follow their code on GitHub.</p>
  </figcaption>
</figure>

<p>この行は変換されません。</p>

<p><a href="https://github.com/ras0q" class="decoration-underline" target="_blank" rel="noopener">@ras0q</a></p>
設定しているCSS
figure.linkcard {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  margin: 1.25rem 0;

  & > img {
    width: 100%;
    height: auto;
    margin: 0;
    object-fit: contain;
    aspect-ratio: 2 / 1;
  }

  & > figcaption {
    padding: 0.5rem 1rem;

    & > a::after {
      position: absolute;
      inset: 0;
      content: "";
    }

    & > p {
      margin: 0;
      overflow: hidden;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 3;
      line-clamp: 3;
    }
  }
}

この変換が上手くいっていれば、直接リンクを貼ったときだけリンクカードが表示されているはず。↓


この行は変換されます。

ras0q - Overview

🦖🦖🦖💨. ras0q has 141 repositories available. Follow their code on GitHub.

https://github.com/ras0q

ras0q - Overviewのサムネイル
ras0q - Overview

🦖🦖🦖💨. ras0q has 141 repositories available. Follow their code on GitHub.

この行は変換されません。

@ras0q


ここからは実装の話。アクセシビリティを意識したつもりだが、多分間違っているので指摘してほしい。

変換はRemarkプラグインとして実装した。HTMLに変換済みの各ページを愚直にVisitして該当するリンクをリンクカードに変換している。

blog/_config.ts at 9edd5ef85613aa6b8d7fd3814d48c5e86dfbdf72 · ras0q/blog

Contribute to ras0q/blog development by creating an account on GitHub.

https://github.com/ras0q/blog/blob/9edd5ef85613aa6b8d7fd3814d48c5e86dfbdf72/_config.ts#L36-L128

blog/_config.ts at 9edd5ef85613aa6b8d7fd3814d48c5e86dfbdf72 · ras0q/blogのサムネイル
blog/_config.ts at 9edd5ef85613aa6b8d7fd3814d48c5e86dfbdf72 · ras0q/blog

Contribute to ras0q/blog development by creating an account on GitHub.

Remark

Use Remark to render the markdown content.

https://lume.land/plugins/remark/

Remarkのサムネイル
Remark

Use Remark to render the markdown content.

ちなみに <https://github.com/ras0q> のように <> で囲んでも同じく変換される。Remarkでは <> で囲んでもそうでなくても構造に違いがないため、これらを区別できなかった。

実装方針を調べると <a> タグの中に ブロック要素を入れて大きなカードリンクとする方法と、ブロック要素の中に入れた <a> タグのクリック範囲をCSSで広げる方法の2つがあった。後者はBootstrapでStretched linkとして採用されている方法で、今回はこちらを使うことにした。Stretched linkを使うとカード全体がリンクとなるため、カード内のテキストを選択できなくなることには注意。

Stretched link

Make any HTML element or Bootstrap component clickable by “stretching” a nested link via CSS.

https://getbootstrap.com/docs/5.3/helpers/stretched-link/

Stretched linkのサムネイル
Stretched link

Make any HTML element or Bootstrap component clickable by “stretching” a nested link via CSS.

リンクカードは <figure><figcaption> を使って実装した。これはリンクカードが本文の補足情報であることを示すため。

筆者はリーダー (アプリによってリーディングモードやリーダービューやイマーシブリーダーとも呼ぶ) で記事を読むことが多いのだが、世のWebページに実装されているリンクカードはことごとくこのリーダーからは見えなくなってしまう。これが大変不便で、せめて自分だけは...とZennにリンクカードと共に生リンクを貼る抵抗をしている。

参考:

READMEを実行するGo製タスクランナー「xc」のススメ

https://zenn.dev/trap/articles/af32614c07214d

READMEを実行するGo製タスクランナー「xc」のススメのサムネイル
READMEを実行するGo製タスクランナー「xc」のススメ

リーダーによって抽出のアルゴリズムが異なるため、リンクカードが消えるのを許容し、リンクカードとは別に生のリンクを描画することにした。また、.sr-only クラスで記事のタイトルと説明文を隠し、リーダーを使う際にはリンクカードの代わりにテキスト情報が出るようにした。