goquery でアクセシビリティをチェックする1 代替文字の空文字チェック編
- 掲載日
- 更新日
はじめに
このサイトは記事作成時にアクセシビリティのチェックを行い、エラーが出たら修正するまで保存できないようにしています。
Golangでアクセシビリティのチェック用のAPIを作成したので、その際の備忘録です。
画像の代替文字(altタグ)の空チェックを行う
アクセシビリティを確保するために必要なチェックはいろいろありますが、今回は「非テキストコンテンツ」に関する項目の一つである、「img要素のalt 属性を使用する」を解決します。
これはGoogleのlighthouseを使用しているとおなじみの方も多いと思いますが、ざっくり説明すると「画像コンテンツは目の不自由な方には見えないので、文字で説明文を書いておこうね」(altに書かれた内容は、スクリーンリーダーなどで読み上げられるので、目の見えない方でも画像の内容を知ることができます。)という話です。
そのため、以下の検証が必要です。
- コンテンツに含まれる img 要素それぞれを調べる。
- 意味を伝える img 要素それぞれが、alt 属性を含んでいることを確認する。
- 画像にコンテンツを理解するために重要な単語が含まれている場合、その単語がテキストによる代替に記述されていることを確認する。
3については実際に画像の内容を見て人力で判断する必要がありますが、1, 2 はシステムでチェックできることなので、以下の要件で実装します。
(最近はAIが画像の内容を理解できるので、3もチェックできるかも?)
- 本文からimgタグを抽出する。
- alt属性が存在することを確認し、なければエラーを吹き出しで表示する。
- 内容は入力されていることが望ましいが、アイコンなどの意味を持たない画像は空文字で構わないので警告の吹き出しを表示する。
HTMLパーサーでimgタグを抽出する
GolangでHTMLを扱おうとするととてもつらいので、jQueryと同じようにHTMLを扱えるgoqueryというライブラリを使用します。
以下のように処理すれば、あとはdocをほぼjQueryのように扱える非常にありがたいライブラリです。
sentence := "<figure><img src=\"\" alt=\"\"></figure><figure><img src=\"\"></figure>"
stringReader := strings.NewReader(string(sentence))
doc, _ := goquery.NewDocumentFromReader(stringReader)
doc.Find("img").Each(func(i int, s *goquery.Selection) {
// 必要な処理
})
属性が存在するかしないかを取得
ほぼjQueryなので、属性を取得すること自体は Attr 関数で簡単に行えますが、地味に詰まったのが「alt属性が存在するか」の情報。(要件の通り、空文字かそもそも存在しないかの差が大事なため。)
結論として、Attr時の2つめの戻り値に入っているので以下の方法で処理できます。
sentence := "<figure><img src=\"\" alt=\"\"></figure><figure><img src=\"\"></figure>"
stringReader := strings.NewReader(string(sentence))
doc, _ := goquery.NewDocumentFromReader(stringReader)
doc.Find("img").Each(func(i int, s *goquery.Selection) {
alt, hasAttribute := s.Attr("alt")
if (hasAttribute) {
// altが存在する場合の処理
} else {
// altが存在しない場合の処理
}
})
エラーを表示する。
最後に、場合分けしてエラー・警告を表示します。
自分のサイトは編集にCKEditor5というエディタを使用しているので、画像には必ずfigureタグがラップされるので、そこに以下のようにClassの付与と、エラーの文言を追加します。
jQueryのように扱えるので、AddClassでクラスの追加、SetAttrで属性を追加し、最後にdocでHTML関数を実行することでクラスや属性が修正されたHTMLを取得することができます。
sentence := "<figure><img src=\"\" alt=\"\"></figure><figure><img src=\"\"></figure>"
stringReader := strings.NewReader(string(sentence))
doc, _ := goquery.NewDocumentFromReader(stringReader)
doc.Find("img").Each(func(i int, s *goquery.Selection) {
alt, hasAttribute := s.Attr("alt")
p := s.Parent()
if (hasAttribute) {
if alt == "" {
// 空文字なら警告表示
p.AddClass("correct-tooltip correct-tooltip-1")
p.SetAttr("data-tooltip", "画像の意味を説明する文章を設定してください。アイコンの場合空のままで構いません。")
}
} else {
p.AddClass("correct-tooltip correct-tooltip-2")
p.SetAttr("data-tooltip", "代替文字を設定してください。")
}
})
html, _ := doc.Find("body").Html()
fmt.Println(html)
実際はこのHTMLを受け取ってNuxt側でエディタに設定や、cssによる吹き出し表示の処理などします。
その際の動きは添付GIF動画の通りです。

代替文字への誤解
余談ですが、代替文字については結構誤解が多いように思います。
例えば以下のような画像があった場合に「建物の案内図」のようなaltを設定するケースが散見されます。

このアクセシビリティ基準の意図をおさらいすると、「画像コンテンツは目の不自由な方には見えないので、文字で説明文を書いておこうね」ということです。
建物の案内図であることはaltで分かりました。では、何階に何の店舗があるのかは伝わるでしょうか?多分伝わりません。
この場合、以下のどちらかの対策をすると親切かなと思います。
- altタグに「1階にAAという店舗があり2階には…」と記述する。
画像の上下に以下のようなテキスト情報を用意する。
個人的にはこっちの方がいいと思います。
画像だと文字列検索できませんが、テキストならできるので、目の見える方でもある店舗がどこにあるか調べたい、となった際にページ内の文字列を検索して一発で辿りつけるので誰にとってもお得です。(入力者は大変かもですが。)
階数 店舗 1階 AA店
BB店
2階 CC店
DD店
各階の店舗
参考リンク

この記事を書いた人
- A.W.
- 茨城県在住Webエンジニアです。 PHPなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。