JavaScriptでコントラストのアクセシビリティ適合レベルを求める

アクセシビリティの記事やツールで良く見かける『`AA`』や『`AAA`』を自分で求める方法です。

![color_a11y.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227791/1ffae547-46cb-6187-3cad-48f040c6c3fe.gif)

## TL;DR

一応クラス使ってそれっぽく書いてみました。

```js:a11y.js
class PartOfRGB {
    constructor (param) {
        this.param = param
    }

    // 16進数
    toHex () {
        return this.param.toString(16)
    }

    // 8bitをsRGBに変換する
    toSRGB () {
        return this.param / 255
    }

    // 相対輝度計算に使うためのRGB
    getRGBForRelativeLuminance () {
        const sRGB = this.toSRGB()

        if (sRGB <= 0.03928){
            return sRGB / 12.92;
        }

        return Math.pow(((sRGB + 0.055) / 1.055), 2.4);
    }
}

class RGB {
    constructor (strRGB) {
        const rgb = this.convStrToRGB(strRGB)
        this.R = new PartOfRGB(rgb[0])
        this.G = new PartOfRGB(rgb[1])
        this.B = new PartOfRGB(rgb[2])
    }

    // 16進数の文字列
    toStr () {
        return `#${this.R.toHex()}${this.G.toHex()}${this.B.toHex()}`
    }

    // #fffff → [255, 255, 255]
    convStrToRGB (strRGB) {
        return strRGB.match(/[^#]{2}/g).map(rgb => parseInt(rgb, 16))
    }

    // 相対輝度
    getRelativeLuminance () {
        const R = this.R.getRGBForRelativeLuminance();
        const G = this.G.getRGBForRelativeLuminance();
        const B = this.B.getRGBForRelativeLuminance();
        return 0.2126 * R + 0.7152 * G + 0.0722 * B;
    }
}

class ColorContrast {
    constructor (strC1, strC2) {
        this.c1 = new RGB(strC1)
        this.c2 = new RGB(strC2)
    }
    
    // コントラスト比
    getContrastRatio () {
        const l1 = this.c1.getRelativeLuminance()
        const l2 = this.c2.getRelativeLuminance()
        const bright = Math.max(l1, l2)
        const dark = Math.min(l1, l2)
        return Math.round((bright + 0.05) / (dark + 0.05) * 100) / 100
    }
    
    // コントラスト比がアクセシブルかを判定する
    judgeA11y () {
        const contrastRatio = this.getContrastRatio()

        if (contrastRatio >= 7) {
            return 'AAA'
        } else if (contrastRatio >= 4.5) {
            return 'AA'
        }
        return 'NG'
    }
}

const CC1 = new ColorContrast('#FFE6E6', '#ffffff')
const CC2 = new ColorContrast('#c7a999', '#1d1616')

console.log(`${CC1.c1.toStr()}, ${CC1.c2.toStr()}`)
console.log(`コントラスト比: ${CC1.getContrastRatio()}`)
console.log(`アクセシビリティ: ${CC1.judgeA11y()}`)
console.log()
console.log(`${CC2.c1.toStr()}, ${CC2.c2.toStr()}`)
console.log(`コントラスト比: ${CC2.getContrastRatio()}`)
console.log(`アクセシビリティ: ${CC2.judgeA11y()}`)
```

### 出力

```:出力
#ffe6e6, #ffffff
コントラスト比: 1.19
アクセシビリティ: NG

#c7a999, #1d1616
コントラスト比: 8.11
アクセシビリティ: AAA
```

## 簡単な説明

1. 2つの色それぞれの相対輝度を求める
1. 2つの色のコントラスト比を求める
1. コントラスト比によって適合レベルを判別する
    1. コントラスト比 >= 7.0 : `AAA`
    1. コントラスト比 >= 4.5 : `AA`

相対輝度を求める過程がちょっと複雑ですが、それ以外は分かりやすいと思います。

## 応用

これを応用して、３色のアクセシビリティをチェックするツールをReactで作ってみました。
https://a11y-contrast-tool.vercel.app/

## 参考
https://waic.jp/docs/WCAG20/Overview.html
W3C勧告のWCAG公式ドキュメントです。

https://lifehackdev.com/ZakkiBlog/articles/detail/web15
コードはここを参考にしました。

https://ja.wikipedia.org/wiki/Help:%E9%85%8D%E8%89%B2%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%88%E3%83%A9%E3%82%B9%E3%83%88%E6%AF%94
Wikipediaのa11yヘルプです。かなり分かりやすくまとまっています。