ウェブページにおけるアイコンの実装方法はさまざまです。マークアップに img
要素を配置する方法もあるし、CSS から背景画像やアイコン・フォントを使う方法もあります。そういった中からどの方法を採用すべきかを判断するには、HTML Standard の Requirements for providing text to act as an alternative for images にあるとおり、「そのアイコンの意味を伝えるテキストが付随するかどうか」を考える必要があります。
テキストが付随しないアイコン
家のアイコンだけでホームページへのリンクを表す場合など、ラベルとしてのテキストが存在しないアイコンは、自分自身でその意味を伝える必要があります。こういったアイコンの実装方法は限られていて、ほぼ 1 つしかありません。alt
属性に代替テキストとしてラベルを指定した img
要素をマークアップに配置する方法です。
<a href="/">
<img alt="Home" src="icon-home.png">
</a>
CSS による背景画像やアイコン・フォントで表現する方法もありますが、その画像やフォントが意図どおりに読み込まれなかった場合に意味がまったく伝わらなくなってしまいます。必ず、適切な alt
属性をともなった img
要素をマークアップに配置すべきです。
なお、HTML 文書にアイコン画像を埋め込む方法としては、img
要素のほかにインラインの svg
要素も考えられます。ここでは詳しく触れませんが、その場合、title
や desc
といった要素による代替テキストのほか、foreignObject
要素などによるフォールバックも考慮する必要があるでしょう。
テキストが付随するアイコン
家のアイコンのすぐ下に Home という文字列があるような、その周囲にラベルとしてのテキストがあるアイコンはどうでしょうか。この場合、ユーザーに意味を伝える目的はテキストが果たしており、アイコンの役割はテキストを補助することであると言えます。そのため、アイコンの実装方法の自由度は高いです。
まず、マークアップに画像を埋め込む方法。この場合、代替テキストは空文字列である必要があります。
<a href="/">
<img alt="" src="icon-home.png">
Home
</a>
CSS を利用するなら、擬似要素とスプライト画像による方法が手軽です。マークアップにはテキスト以外に追加の要素や属性は必要ありません。
<style>
a[href="/"]:before {
background: url(sprites.png) no-repeat 0 -160px;
content: "";
display: inline-block;
height: 32px;
width: 32px;
}
</style>
<a href="/">
Home
</a>
アイコン・フォントも利用できますが、スクリーン・リーダーを考慮した追加のマークアップなど、安全でアクセシブルな実装を実現するにはそれなりの手間がかかります。
<style>
.icon-home:before {
font-family: "icomoon";
content: "\e608";
}
</style>
<a href="/">
<span class="icon-home" aria-hidden="true"></span>
Home
</a>
これら CSS を利用した実装は、マークアップに画像を埋め込む手法に比べ、パフォーマンスやメンテナビリティに優れています。しかし、画像やアイコン・フォントが利用できない環境ではアイコンは表示されないため、あくまでラベルとしてのテキストが付随する場合にのみ採用すべき方法です。
レスポンシブなラベル
いわゆるレスポンシブ・デザインでは、こういったアイコンに付随するテキストの有無を、画面のサイズなどによって切り替えたい場合があります。画面が狭いときはアイコンのみで、十分に広ければテキストも表示、というように。
結論から言うと、これを HTML と CSS だけで実装するとどうしても問題の生じる可能性があるため、JavaScript によって DOM を操作するのがより良い実装と考えます。以下、それぞれについて検証してみます。
CSS でテキストの表示を切り替える
テキストが表示されていない状態を考慮し、マークアップには alt
属性にラベルを指定した img
要素を配置します。ラベルとなるテキストは data-*
属性などに保存しておき、メディア・クエリに応じて擬似要素と content
プロパティで表示を切り替える、というのが CSS による実装パターンです。
<style>
@media (min-width: 481px) {
[data-label-text]:after {
content: attr(data-label-text);
}
}
</style>
<a href="/">
<img alt="Home" src="icon-home.png">
<span aria-hidden="true" data-label-text="Home"></span>
</a>
マークアップの alt
属性と CSS の生成内容で同じテキストが重複することになり、スクリーン・リーダーのユーザーを混乱させる可能性がありますが、この問題は aria-hidden
属性を持った要素を利用することで回避できます。
しかし、視覚系ブラウザーで画像が読み込まれなかった場合、同じテキストが繰り返し表示されてしまいます (この例では Home Home という表示になる)。
逆に、img
要素の alt
属性を空文字列とし、マークアップにあらかじめ含めたテキストの表示を CSS で切り替えるという実装でも、視覚系ブラウザーで画像が読み込まれなかった場合になにも表示されなくなってしまい、やはり問題があります。
このように、HTML と CSS だけによるアプローチでは、画像が利用できない環境に対応しきれません。
JavaScript で DOM を書き換える
理想的な実装は、画面が狭いときは alt
属性の指定された img
要素のみ、広いときは alt
が空文字列の img
とテキスト、というふうにマークアップを切り替える、ということになります。
<!-- 画面が狭いときは alt ありの img のみ -->
<a href="/">
<img alt="Home" src="icon-home.png">
<span class="text"></span>
</a>
<!-- 画面が広いときは alt が空の img とテキスト -->
<a href="/">
<img alt="" src="icon-home.png">
<span class="text">Home</span>
</a>
そこで以下のような JavaScript を考えてみました。
<!-- matchMedia() polyfill - https://github.com/paulirish/matchMedia.js -->
<script src="matchMedia.js"></script>
<script src="matchMedia.addListener.js"></script>
<script>
/**
* Show/hide icon labels using media queries.
*
* @param {Object} images NodeList of icon images
* @param {String} mqString Media query string
* @param {Boolean} [fallback] Show label text for no-media-queries browsers
* @example
* var navIcons = document.querySelectorAll('nav img');
* responsiveIconLabel(navIcons, '(min-width: 801px)', true);
*/
function responsiveIconLabel (images, mqString, fallback) {
var mqSupported = window.matchMedia && window.matchMedia('only all').matches,
textTemplate = document.createElement('span'),
mql,
mqListeners = [],
i,
len;
textTemplate.className = 'text';
for (i = 0, len = images.length; i < len; i++) {
(function (img) {
var alt = img.alt,
txt = textTemplate.cloneNode(false);
img.parentNode.insertBefore(txt, img.nextSibling);
if (mqSupported) {
mqListeners.push(swapAltText);
} else if (fallback) {
swapAltText(true);
}
function swapAltText (match) {
if (match) {
img.alt = '';
txt.innerHTML = alt;
} else {
img.alt = alt;
txt.innerHTML = '';
}
}
})(images[i]);
}
if (mqSupported) {
mql = window.matchMedia(mqString);
mql.addListener(updateIconLabel);
updateIconLabel(mql);
}
function updateIconLabel (mql) {
var i,
len,
match = mql.matches;
for (i = 0, len = images.length; i < len; i++) {
mqListeners[i](match);
}
}
}
</script>
使い方は、まずマークアップとして alt
属性にラベルのテキストを指定した img
要素を用意します。
<a href="/">
<img alt="Home" src="icon-home.png" class="icon-home">
</a>
アイコンの画像と、テキストを表示するメディア・クエリ文字列を引数にして、responsiveIconLabel
関数を呼び出します。3 つめの引数ではメディア・クエリをサポートしない環境でテキストを表示するかどうかを指定しています。
var homeIcon = document.querySelectorAll('.icon-home');
responsiveIconLabel(homeIcon, '(min-width: 801px)', true);
MediaQueryList
オブジェクトの matches
プロパティにより、指定したメディア・クエリにマッチしていれば img
の alt
を空にして、代わりにテキストを表示します。これは addListener
メソッドにより、メディア・クエリの結果が変更されるたびに更新されます。
<a href="/">
<img alt="" src="icon-home.png" class="icon-home">
<span class="text">Home</span>
</a>
なお window.matchMedia
をサポートしない環境のために matchMedia()
polyfill を読み込んでいます。