12周年記念パーティ開催! 2024/5/10(金) 19:00

しっかり学ぶ、a要素 第1回 a要素の書き方とCSS

Webの特徴的な操作のひとつに、ハイパーリンクがあります。この記事ではハイパーリンクを実現するa要素について紹介します。まずはその記述の特徴を見てみましょう。

発行

著者 矢倉 眞隆 フロントエンド・エンジニア
しっかり学ぶ、a要素 シリーズの記事一覧

このシリーズは仕様の変更に基づき、メンテナンスしています。(最終更新2022年4月)

ハイパーリンクを記すa要素

このシリーズでは、HTMLのa要素について詳しく紹介します。

詳細に入る前に、いまいちどa要素の目的についておさらいしましょう。

a要素はリンク、より厳密にいえばハイパーリンクを記述するための要素です。ハイパーリンクは、現在のHTMLの仕様書では次のように定義されています

These are links to other resources that are generally exposed to the user by the user agent so that the user can cause the user agent to navigate to those resources, e.g. to visit them in a browser or download them.

他のリソースへのリンクのうち、そのリソースへナビゲート(ページの移動やダウンロードなど)ができるよう、ユーザーエージェントによってその効果を表現したものが、ハイパーリンクです。

クリックしたら別のリソースに移動したり、何かをダウンロードしたり。シンプルな操作ですが、私たちはこれにより情報を得る新しい手段を手にしました。HTML、もといWebを語る際に、ハイパーリンク*は外せないのです。

*注:ハイパーリンク

本記事ではこれ以降、ハイパーリンクを「リンク」と表記します。

a要素の基本

a要素は基本的に、文章の一部をくくってリンクを表現します。

Web開発のお供に<a href="https://www.codegrid.net/">CodeGrid</a>を。

href属性の中にURLを記述すると、その文字列部分がリンクになり、クリックやタップで移動できるようになります。こう見ると、リンクをリンクたらしめているのは、a要素というよりはhref属性なのかもしれません。

とはいえ、href属性のないa要素も記述できるようになっています。hrefのないa要素は、リンクのプレースホルダを表すとされています。

例として仕様書にも挙げられているのが、Webサイトのナビゲーションにある「現在地」です。リンクはその見た目や挙動から「今見ているものと違う箇所に移動する」と見られがちです。Webサイトの構造と、現在どこにいるかを伝えるために、現在地だけリンクしないというつくりのページが存在します。

そういった、「他のページではリンクとして使われているが、何らかの都合でリンクとして機能させたくない場合」に、hrefを省いたa要素が認められています。次は、HTML仕様書のマークアップ例です。

<nav>
 <ul>
  <li> <a href="/">Home</a> </li>
  <li> <a href="/news">News</a> </li>
  <li> <a>Examples</a> </li>
  <li> <a href="/legal">Legal</a> </li>
 </ul>
</nav>

後述するCSSの書き方もあるため現在もそこまで広まっている印象はありませんが、一部だけをspan要素にするなどの必要がないため、いくぶんスマートに見えます。

a要素内に書けるもの、a要素を書けるところ

先ほど挙げた例では、a要素内にプレーンテキストのみが書かれていました。しかしもちろん、a要素内には他の要素も含められます。

ある要素と、その中に記述できるもの(要素、テキストなど)との関係を、「コンテントモデル(content models)」と呼びます。

以前のa要素のコンテントモデルは、インライン要素に限定されていました。emやstrong、spanなどはインライン要素なのでa要素の中に記述できますが、pやdiv、h1などはブロック要素なので、a要素の中に記述できませんでした。

<a href="...">
  <p>以前はこう書けませんでした。</p>
</a>

HTML5以降は、各要素のコンテントモデルが細かくなり、インライン要素・ブロック要素といった大きなカテゴリはなくなりました。a要素のコンテントモデルも更新され、それまでブロック要素と呼ばれていた要素も含められるようになりました。

たとえば、商品一覧画面で、各商品が次のようにマークアップされているとします。

<div class="card-item item">
  <h3 class="item-title">製品名</h3>
  <img class="item-image" ...>
  <p class="item-description">...</p>
</div>

商品一覧をカード状のUIとして提供する場合、このブロック全体をリンクとできればうれしいです。しかし、HTML4時代のHTMLでは、a要素はブロックを囲めませんでした。結果、商品名だけにリンクを張ったり、画像や詳細にそれぞれリンクを張ったり、カードのdiv要素内をクリックしたらリンク先に移動するようなJavaScriptを書いたりといったことをしていました。

HTML5以降ではそういった制限がなくなったため、a要素でシンプルに商品のブロックを包めます。

<a href="/products?pid=****">
  <div class="card-item item">
    <h3 class="item-title">製品名</h3>
    <img class="item-image" ...>
    <p class="item-description">...</p>
  </div>
</a>

ただし、子孫に別のa要素やbutton要素など、操作を受け付ける要素を置くことはできないとされています。先ほどの例でいうと、商品一覧のカードに「カートに追加」というボタンは置けないことになります。

<a href="/products?pid=****">
  <div class="card-item item">
    ...
    <button class="item-add" ...>カートに追加</button>
  </div>
</a>

ここでの「置けない」、つまり「書けない」というのは製作者の要件で、ブラウザは特にa要素の中にある別のa要素やボタンを無効にすることはありません。とはいえ、押す場所によってボタンの挙動が違うのはUIとしては親切ではないように感じます。ブロックを包むのであれば、内側にはボタンやリンクを置かないようにしましょう。

a要素のコンテントモデルとHTMLのトランスパレントコンテント

HTML5以降はブロック要素となっていたものを囲めるようになったと述べましたが、具体的な要素は書きませんでした。これはa要素のコンテントモデルが少し特殊だからです。

現在のHTMLで、a要素のコンテントモデルは「トランスパレントコンテント(transparent content)」というものになっています。これは、それ自身のコンテントモデルに決まったカテゴリを定めず、親のコンテントモデルを継承するというものです。ですので、a要素がブロックを包むことができるかは、そのa要素の親次第ということになります。詳細は後述しますが、基本的には包むことができると考えて大丈夫です。

たとえば、p要素を囲ったa要素が、div要素の直下にあるとします。

<div>
  <a ...>
    <p>こんにちは。</p>
  </a>
</div>

このマークアップが正しいかを調べるには、aの親であるdiv要素と、aの子であるp要素の関係に注目します。要は、div要素直下にp要素があるケースが、正しいかどうかをチェックすればよいのです。

<!-- コメントアウトや、ソースから削られているとして考える -->
<div>
  <!-- <a ...> -->
    <p>こんにちは。</p>
  <!-- </a> -->
</div>

div要素のコンテントモデルは、基本的にフローコンテント(flow content)とされています。フローコンテントとは基本的にbody内で使われる要素を表すカテゴリで、以前のブロック要素とインライン要素が合わさったようなものです。body内なので、head要素内に書かれるtitle要素やstyle要素などはフローコンテントではありません。

p要素はもちろん、フローコンテントになります。div要素の直下にp要素がある状態は、まったく問題ありません。ですので、div > a > pというマークアップも、問題ないことになります。

フローコンテントにはかなり多くの要素が属しており、また多くの要素がフローコンテントもしくはフレージングコンテント(phrasing content)(従来のインライン要素とおおよそ同等です)をコンテントモデルとしているため、a要素はかなり多くの要素を囲めるようになっています。

HTML仕様書にある各カテゴリのベン図から。フローコンテントは一部を除き、ほとんどのカテゴリを包含していることがわかる。

a要素を書けないところ

それでは、逆はどうでしょうか。次のマークアップが正しいかをチェックしてみます。

<p>
  <a ...>
    <div>さようなら。</div>
  </a>
</p>

先ほどと同じように、p要素の直下にdiv要素が書けるかを調べればよいわけです。

pのコンテントモデルは、フレージングコンテントという、昔でいうとインライン要素に近いものです。しかしdiv要素はフレージングコンテントではないため、pの直下にdivは書けません。よって、p直下のaもdivを包めず、これはエラーになります。

また、要素によっては、コンテントモデルの制約がとても厳しいものがあります。そういった要素においては、a要素をその中に記述できないケースがあります。たとえば、ul要素とli要素です。

「a要素でli要素を囲めないかな?」と思った方はいないでしょうか。次のようなマークアップです。

<ul>
  <a>
    <li>...</li>
  </a>
  ...
</ul>

残念ながら、これはエラーになります。ulのコンテントモデルは、「0個以上のli要素」と、かなり限定されているからです。

書いてしまった場合はどうなる?

p > a > divul > a > liはエラーになると書きましたが、これも制作者に対する要件です。もしそれを知らずマークアップしてしまった場合、どうなるのでしょうか。

たとえば、p > a > divは、次のようなDOMツリーとなってしまいます。

├ P
│ └ A
├ DIV
│ └ A
├ A
├ P

入れ子にしたはずなのに、div要素がpの兄弟要素となってしまっています。かなり不思議に思えますが、p要素の終了タグを省略できる仕様との兼ね合いで、このような結果となってしまいます。

ul > a > liについては、簡単に試したところ特に問題はありませんでしたが、li要素の中に入る要素によっては、p > a > divのようにおかしなDOMツリーになるかもしれません。

リンクとCSS

ここまではa要素の書き方について紹介しました。ここからは、リンクの装飾に使うCSSの機能について紹介します。

CSS仕様には、リンクに特化したセレクタがいくつか存在します。代表的なものは、:link:visitedでしょう。:link未訪問を、:visited訪問済みのリンクにマッチします。

:visitedについては、悪意のあるスクリプトが訪問済みのページを取得する手段として使われ、プライバシーに関する懸念があったため、使えるプロパティが次のものに限定されています。

  • color
  • background-color
  • border-color とサブプロパティ(border-top-colorなど)
  • outline-color
  • column-rule-color
  • fill-color
  • stroke-color

以前はbackground-imageを使い、未訪問と訪問済みリンクに別のアイコンを指定するといったCSSがありましたが、現在はできなくなっています。

すべてのリンクを示す:any-link

:linkはその名に反し、未訪問のリンクを表します。このためリンクすべてにマッチするセレクタは、:link:visitedを併記するしかありませんでした。

/* これくらいならまだ書けそうだが…… */
:link, :visited { ... }

/* 他の擬似クラスと組み合わせる場合にとても面倒 */
:link:hover, :visited:hover, :link:active, :visited:active { ... }

書きづらさからか、要素型セレクタを使うケースがほとんどのように思います。

/* 要素型セレクタを使ったほうが楽 */
a { ... }
a:hover, a:active { ... }

しかし、aだけの要素型セレクタでは、プレースホルダとしてのリンクにもマッチしてしまいます。a.currentとクラスをつけたり、a:not([href]):not()擬似クラスを使い打ち消すなどしないといけませんが、だいぶ面倒です。

Selectors Level 4仕様では新たに、未訪問と訪問済みいずれのリンクにもマッチするセレクタとして、:any-link擬似クラスが定義されました。

:any-linkを使うと、aという要素型セレクタを使うのと同じ感覚で、リンクのみに適用されるスタイルを記述できます。プレースホルダとしてのa要素にはスタイルが適用されないので、純粋にリンクのスタイルを指定できます。

:any-link { ... }
:any-link:hover, :any-link:active { ... }

ブラウザの実装ですが、長らく:-moz-any-link:-webkit-any-linkといった、接頭辞付きの実装が存在しています。接頭辞なしの実装も、Firefox 50以降、Safari 9以降、Chrome 65以降でサポートしています。

しかし、EdgeとIEでの対応がまだなことや、aによるリンクのスタイル指定が普及した今、ブラウザが内部で持つスタイルシートなどを除いて、うまい使い所はないかもしれません。

:local-link擬似クラス

Selectors Level 4にはもうひとつ、:local-linkという「link」と名のついた擬似クラスが定義されています。

これは、リンクのうち、文書のURLとリンク先のURLがマッチするものと定義されています。ページ内リンクに対しても適用されるため、Webサイトのナビゲーションで使い所があるかもしれません。

現在の仕様は2018年1月に追加されたもののため、まだ実装はありません。

まとめ

今回はリンクとa要素について、HTMLとCSSの観点から紹介しました。

とはいえ、リンクをリンクたらしめる、「クリックして移動」という性質については何ら説明していません。次回はその移動に関するあれこれを紹介しようと思います。