Three.jsのコードは間違い！ WebGLが使える環境かどうかのホントの判定方法 Android対応版

「[Three.js](http://threejs.org/)」はWebGLを使ったリッチな3D表現が使用できる人気のライブラリ。ところが、Three.js公式サイトで紹介しているWebGL有効判定は**Android 4系のWebView向けのWebGL有効判定が不十分なので、WebGLコンテンツを表示させようとすると真っ黒になる**という最悪の事態が発生します(2016/3/2現在 ver74)。Android 4.03からアップデートした4.4の一部でも同様の現象を確認しました。2016/3/2現在、Android　４系のユーザーは62%(参考：「[Android Developers]( http://developer.android.com/about/dashboards/index.html)」)もいますので、スマートフォン向けWebGLコンテンツを作るならば決して無視できないバグです。

このエントリーではこの現象の原因と解決策を紹介します。

#何が起こっているのか

下記はAndroid 4.1.2の標準ブラウザ、iOS 9.2.1のMobile Safariで[Three.jsのサンプルコード](http://threejs.org/examples/#webgl_animation_cloth)を見た結果です。Androidの方は、3Dのコンテンツ表示部分が真っ黒になっています。WebGLが非対応の旨の表示などもありません。

<img width="６３８" alt="Android 4.1.2のブラウザでThree.jsのサンプルコードを見た結果 " src="https://qiita-image-store.s3.amazonaws.com/0/67884/ad7652f0-a951-90a7-2e3c-bcefc6ae6e94.png">

# 原因はDetector.js

[Detector.js](http://threejs.org/examples/js/Detector.js)でWebGLが使えるかどうかを判定している部分です。コードは下記です。

```js
var Detector = {
	webgl: ( function () {
		try {
			var canvas = document.createElement( 'canvas' ); 
			return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
		} catch ( e ) {
			return false;
		}
	} )(),
// 以下省略

```

このコードを使うと、Android 4.0〜4.2の一部端末で下記のようなエラーが発生します。

```
Uncaught TypeError: Object #<WebGLRenderingContext> has no method 'getShaderPrecisionFormat'
```

現象が発生しているAndroid端末では、`WebGLRenderingContext`オブジェクトはWebGLの暫定仕様版である`experimental-webgl`によって取得されます。`experimental-webgl`には`getShaderPrecisionFormat`メソッドが存在しません。

```js:WebGLRenderingContextとgetShaderPrecisionFormatの存在確認
// in Android 4.1.2

var canvas = document.createElement( 'canvas' ); 
!!canvas.getContext( 'webgl' ); // false
!!canvas.getContext( 'experimental-webgl' ); // true

!!canvas.getContext( 'experimental-webgl' ).getShaderPrecisionFormat; // false
```

**Three.jsライブラリ内部では`getShaderPrecisionFormat`メソッドを使用しているのでAndroid 4ではエラーが発生**し、正しく動作しません。しかし、WebGLが有効（`Detector.webgl == true`）と見なされているため、本来ならば表示されるべきWebGL非対応表示もできず、真っ黒になってしまいます。

したがって、**WebGLが使えるどうかの判定に`getShaderPrecisionFormat`メソッドが存在するかどうかの判定を追加する**必要があります。

#正しい判定式
Detector.jsを修正し、`getShaderPrecisionFormat`メソッドの有無を判定します。

```js
var Detector = {
	webgl: (function () {
		try {

			var canvas = document.createElement('canvas');
			// 修正コード開始
			var webGLContext = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
			return !!( window.WebGLRenderingContext && webGLContext && webGLContext.getShaderPrecisionFormat );
			// return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
			// 修正コード終了
                        
		} catch (e) {

			return false;

		}

	})()
};

// test code
alert(Detector.webgl);
```

# 意図したWebGL非対応表示となる
判定式を修正すると、今回のエラーが発生するAndroidの環境はWebGL非対応環境とみなされ、WebGL非対応画面が表示されます。なお、非WebGL環境でのスタイルは、可視性を上げるため調整してあります。

<img width="360" alt="" src="https://qiita-image-store.s3.amazonaws.com/0/67884/d7fe3877-183f-5088-7b85-ee74194ba7dc.png">

# Three.jsを使うならWebGLが使えない環境にも配慮を
今回の現象はAndroid4系で発生しうる可能性があります。2016/3/2現在、Android　４系ユーザーは62%(参考：「[Android Developers]( http://developer.android.com/about/dashboards/index.html)」)もいるので、Three.jsを使うならば、このような環境のユーザーで正しい表示がされるかどうかに配慮しましょう。