krzm.jp
黒住浩司 Webサイト

夏休み自由研究: タブレットの表示

内容

はじめに

近頃は、Webサイト閲覧に使用する機器が、従来のパソコン一辺倒から、タブレットやスマートフォンへと多様化しています。私は、パソコン派なのですが、HTML/CSSを人様に教える身である以上、他の機器でもどのように表示されるか、やはり気になってきました。

用意したタブレット

もちろん、様々な意味でプライバシーを侵害する機器であるスマートフォン(や携帯電話)を持つ気にはなれないので、とりあえずはタブレットを探求してみることにしました。昨年、記念すべきMicrosoft純正ハードウェアとして登場したSurface Proを皮切りに、この夏までに計3台のタブレットを購入。以下の表が、その概要です。

  Microsoft Surface Pro Lenovo Thinkpad 8 Asus MeMO Pad7 (ME176C)

画面サイズとdpi値は当方で算出したものなので、実際の製品仕様とは異なる場合があります。

Surface Proのデフォルトのデバイスピクセル比は1.5ですが、1に変更して使用しています。

 

画面 10.6型 8.3型 7型
画面サイズ 9.24 x 5.20 in 7.04 x 4.40 in 5.94 x 3.71 in
画面解像度 1920 x 1080 px 1920 x 1200 px 1280 x 800 px
dpi 207 272 215
使用解像度 1920 x 1080 px 1280 x 800 px 960 x 600 px
デバイスピクセル比 1 1.5 1.33125
GPU インテル HD Graphics 4000 インテル HD Graphics インテル HD Graphics
CPU インテル Core i5-3317U インテル Atom Z3770 インテル Atom Z3745
動作周波数(バースト時) 1.70GHz (2.6GHz) 1.46GHz (2.39GHz) 1.33GHz (1.86GHz)
コア数(スレッド数) 2 (4) 4 (4) 4 (4)
メモリ 4GB 2GB 1GB
ストレージ 256GB 64GB 16GB
TDP/SDP 17W 2W 2W
OS Windows 8.1 Pro 64bit Windows 8.1 32bit Android 4.4.2

まあ、Surface Proはタブレットというよりパソコンに近いのですが、Lenovo Thinkpad 8は、Windowsタブレットとしての性格のほうが強いと言えるでしょう。また、Androidタブレットでの表示も確認するため、Asus MeMO Pad7も仲間に加えました。

これらは、それぞれ10.6型、8.3型、7型、dpiで表す解像度が200以上であり、現時点での主要な表示能力は押さえることができました。Windows、Androidタブレットの双方を含み、主要なタブレット環境は網羅したといえます。林檎? それはもちろん対象外です。

表示サイズの検出

そして、これらのタブレットの表示機能を、概括的にチェックするため、以下のページをサイトに加えました。
https://krzm.jp/test/

JavaScriptによる検出

すなわち、JavaScriptを使用し、以下の手段で各サイズを検出しています(サンプルコード1 参照)。

デバイスピクセル比
window.devicePixelRatio
画面の幅と高さ
screen.width
screen.height
画面有効領域の幅と高さ
screen.availWidth
screen.availHeight
ウインドウ(ビューポート)の幅と高さ
window.innerWidth
window.innerHeight

なお検出は、イベントリスナーを使ってDOMContentLoaded時に実行しているので、Internet Explorer 8以下では動作しません。同様に、ウインドウ サイズの変更は、rsizeイベントで更新しています。さらに、画面および画面有効領域の幅/高さを、タブレットを回転させたとき(DeviceOrientationイベント時)に更新しているのですが、このイベントは回転を検知するセンサーによって発生が伝達されるので、タブレットでしか実行されません。しかも、Windowsタブレット上のIE 11、Chrome/Operaでは動作するのですが、AndroidタブレットであるMeMO Pad 7上のChrome/Operaでは反応しませんでした。なお、Firefoxは、いずれのタブレットでも、動作しません。まあ、そんなものでしょう。

CSSによる検出

また、サイズを数値で検出するほかに、CSSのメディアクエリーによって、現在の表示サイズを色分けしています。たとえば、
@media (min-width:961px) and (max-width:1024px)
のように記述することで、ウインドウの幅が961~1024ピクセルである状態を検出し、body要素に背景色を指定しています(サンプルコード2 参照)。これによって、1024ピクセル以下の際は、ページの背景がクリムゾン色になります。

高解像度かつ小さな画面のタブレットでは、実際の画面サイズと、ウインドウサイズは異なります。そのあたりを調べるために、データを収集しようと目論んでいるわけです。というわけで、夏休みももう終わりですが、研究は始まったばかりで、まだ検証結果にはたどり着いていません…。大人ですから、8月31日に夏休みの宿題に追われるようなことはせず、秋の夜長に気長に調べていくつもりです。

おまけ: 回転の検出

この過程で、ちょっと思いついたのが、「タブレットの回転検知」ページです。ここでは、やはりDeviceOrientationイベント時にX、Y、Z軸の回転角度を検出し、それらをCSS transformプロパティで指定することで(X軸であれば、値はrotateX(角度))、SVG画像を各軸で3D回転させています(サンプルコード3 参照)。

もちろん、これもWindowsタブレットのIE 11やChrome/Operaでしか実行できません。

なお、DeviceOrientationについては、「Internet Explorer デベロッパー センター」の「デバイスの向きのイベント」ページに、詳しい解説が日本語で掲載されています。

サンプル ソースコード

サンプルコード1

var VALUE = new Array;
var obj;
(function () {
	if (!document.addEventListener) {
		window.onload = function () {
			document.getElementById("main").innerHTML = "<hr /><p>IE 8以下では表示されません。</p><hr />";
		}
		return;
	} else {
		document.addEventListener("DOMContentLoaded", function (){
			obj = document.querySelectorAll("main section:first-of-type dd");
			VALUE[0] = window.devicePixelRatio;
			VALUE[1] = screen.width;
			VALUE[2] = screen.height;
			VALUE[3] = screen.availWidth;
			VALUE[4] = screen.availHeight;
			VALUE[5] = window.innerWidth;
			VALUE[6] = window.innerHeight;
			for (var i = 0; i <= 6; i++) {
				if (i >= 1) {
					var TEXT = VALUE[i] + " px";
				} else {
					var TEXT = VALUE[i];
				}
				obj.item(i).innerHTML = TEXT;
			}
		}, false);
		window.addEventListener("deviceorientation", function (){
			VALUE[0] = window.devicePixelRatio;
			VALUE[1] = screen.width;
			VALUE[2] = screen.height;
			VALUE[3] = screen.availWidth;
			VALUE[4] = screen.availHeight;
			for (var i = 0; i <= 4; i++) {
				if (i >= 1) {
					var TEXT = VALUE[i] + " px";
				} else {
					var TEXT = VALUE[i];
				}
				obj.item(i).innerHTML = TEXT;
			}
		}, false);
		window.addEventListener("resize", function (){
			VALUE[5] = window.innerWidth;
			VALUE[6] = window.innerHeight;
			for (var i = 5; i <= 6; i++) {
				obj.item(i).innerHTML = VALUE[i] + " px";
			}
		}, false);
	}
}) ();
サンプルコード2

@media only screen and (min-width:2561px) {
	body{
		background-color:rgb(255,64,32);
	}
	#frame {
		width:2496px;
	}
}
@media only screen and (min-width:1921px) and (max-width:2560px) {
	body{
		background-color:rgb(255,96,255);
	}
	#frame {
		width:1856px;
	}
}
@media only screen and (min-width:1367px) and (max-width:1920px) {
	body{
		color:rgb(255,255,255);
		background-color:rgb(32,32,255);
		text-shadow:0px 0px 6px rgba(255,255,255,0.8);
	}
	#frame {
		width:1302px;
	}
	a {
		color:rgb(255,255,255);
	}
}
@media only screen and (min-width:1281px) and (max-width:1366px) {
	body{
		background-color:rgb(96,255,255);
	}
	#frame {
		width:1216px;
	}
}
@media only screen and (min-width:1201px) and (max-width:1280px) {
	body{
		background-color:rgb(96,255,96);
	}
	#frame {
		width:1136px;
	}
}
@media only screen and (min-width:1081px) and (max-width:1200px) {
	body{
		background-color:rgb(255,255,64);
	}
	#frame {
		width:1016px;
	}
}
@media only screen and (min-width:1025px) and (max-width:1080px) {
	body{
		background-color:rgb(255,128,32);
	}
	#frame {
		width:960px;
	}
}
@media only screen and (min-width:961px) and (max-width:1024px) {
	body{
		background-color:rgb(153,51,102);
	}
	#frame {
		width:896px;
	}
}
@media only screen and (min-width:801px) and (max-width:960px) {
	body{
		background-color:rgb(204,153,51);
	}
	#frame {
		width:736px;
	}
}
@media only screen and (min-width:641px) and (max-width:800px) {
	body{
		background-color:rgb(153,102,255);
	}
	#frame {
		width:576px;
	}
}
@media only screen and (min-width:601px) and (max-width:640px) {
	body{
		font-size:14px;
		background-color:rgb(153,153,102);
	}
	#frame {
		width:536px;
	}
}
@media only screen and (min-width:481px) and (max-width:600px) {
	body{
		font-size:14px;
		background-color:rgb(128,128,128);
	}
	#frame {
		width:416px;
	}
	main {
		display:block;
	}
	main section {
		width:100%;
		margin:1em;
	}
}
@media only screen and (min-width:381px) and (max-width:480px) {
	body{
		font-size:14px;
		background-color:rgb(255,255,255);
	}
	#frame {
		width:316px;
	}
	main {
		display:block;
	}
	main section {
		width:100%;
		margin:1em;
	}
	main section:last-of-type {
		box-shadow:
			0px 0px 0px 2px rgb(128,128,128),
			0px 0px 8px 2px rgba(0,0,0,0.4),
			inset 4px 4px 8px rgba(0,0,0,0.4),
			inset -2px -2px 6px rgba(128,128,128,0.4);
	}
}
@media only screen and (max-width:380px) {
	body{
		font-size:14px;
		color:rgb(255,255,255);
		background-color:rgb(0,0,0);
		text-shadow:0px 0px 6px rgba(255,255,255,0.8);
	}
	#frame {
		width:316px;
	}
	main {
		display:block;
	}
	main section {
		width:100%;
		margin:1em;
	}
	a {
		color:rgb(255,255,255);
	}
}
サンプルコード3

var VALUE = new Array;
var evt;
(function () {
	if (!window.addEventListener) {
		window.onload = function () {
			document.getElementById("main").innerHTML = "<hr /><p>IE 8以下では表示されません。</p><hr />";
		}
		return;
	} else {
		window.addEventListener("deviceorientation", function (evt){
			var scW = screen.width;
			var scH = screen.height;
			var obj = document.querySelectorAll("main dd");
			var IMAGE = document.querySelectorAll("main img");
			VALUE[0] = evt.beta;
			VALUE[1] = evt.gamma;
			VALUE[2] = evt.alpha;
			for (var i=0; i<=2; i++) {
				obj.item(i).innerHTML = VALUE[i] + "度";
			}
			if (scW > scH ) {
				IMAGE.item(0).style.transform = "rotateX(" + (0 - VALUE[0]) + "deg)";
				IMAGE.item(1).style.transform = "rotateY(" + VALUE[1] + "deg)";
				IMAGE.item(2).style.transform = "rotateZ(" + VALUE[2] + "deg)";
			} else {
				IMAGE.item(0).style.transform = "rotateX(" + (0 - VALUE[1]) + "deg)";
				IMAGE.item(1).style.transform = "rotateY(" + (0 - VALUE[0]) + "deg)";
				IMAGE.item(2).style.transform = "rotateZ(" + (90 - VALUE[2]) + "deg)";
			}
		}, false);
	}
}) ();