これは何? †
- スマホ向け Web ページで、Javascript を使って拡大率を 1.0 倍にしたい
- スマホ向け Web ページでは、View Port という考え方を使って狭い画面で大きな Web ページを表示している。大きな画面を View Port という虫眼鏡から覗いているという感じ
+-------+............
|View | :
| Port| :
|320x480| :
| | :
+-------+ :
: :
: Web Page :
: 1200x1800 :
: :
:...................:
- で、View Port は、拡大率を設定できる
- 静的な View Port の指定方法 → HTML ヘッダの META タグで定義
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
- えーっと、本来は CSS に記述されるべきもの。Apple が iOS 版 Safari を作った時にやらかした
- 普通は View Port 指定は静的でいいんだけど、時にはやむを得ず動的に変更したい時がある。Javascript からどうやって変更すりゃいいんだっけ?
ViewPort? を Javascript から変更する †
- http://stackoverflow.com/questions/3588628/can-i-change-the-viewport-meta-tag-in-mobile-safari-on-the-fly に曰く
- Update viewport
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0');
- Or Create viewport
document.getElementsByTagName('head')[0].appendChild(
'<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">');
- 実際にやってみて、いつくつか留意事項があった
- 現在の設定値と違う content を設定すると、スマホ画面に表示されてる内容の拡大率が新しい initial-scale になる
- 現在の設定値と同じ content を設定しても、スマホ画面に表示されてる内容は変わらない
- 元々 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
- ユーザが、ピンチアウトして 2 倍のスケールで表示
- viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1'); を実行
- スマホ画面の拡大率は変わらない (initial-scale で指定した 1.0 倍になることを期待していたけれども画面に変化なし。max は関係ないみたい)
- わざと変な値を設定してから、setTimeout(function(){ ... }, 100); とかで目的の値に変更するテクニックは、Safari でしか有効でない
調査結果 †
- 調査方法
http://hondou.homedns.org/ViewPortExam/
- rewind ボタンで initial-scale=1.0 に変更
- expand ボタンで initial-scale=2.0 に変更
- 次のプログラムを準備
- 次が機能するか?
- 1. (初期) → expand → rewind
- 2. (初期) → ピンチアウト → rewind
- 3. (初期) → ピンチアウト → expand → rewind
- 調査対象
- iPhone5s iOS8.2 Safari 8.0
- iPhone5s iOS8.2 Chrome 41.0
- HTC Evo 3D Android 4.0.3 Chrome 41.0
- Android ブラウザは、Google のサポート対象外になったので除外してもいいだろう (4.4 から標準はChrome、4.3 以前はサポート対象外)
- cf. Mobile/Tablet Browser Market Share, Market Share Reports, Feb 2015, http://marketshare.hitslink.com/
OS ごとのブラウザシェアは資料がなかったけど、これを見ると、だいたいみんなデフォルトブラウザを使っているのね
iPhone ユーザは、Safari を使っているし、4.4 以降の Android ユーザは Chrome (4.3以前の Android ユーザは Android ブラウザ)。わざわざ別のブラウザをインストールする人はあまりいないんだ
- 調査結果
| iOS+Safari | iOS+Chrome | Android+Chrome | 備考 |
A1 | ◯ | ◯ | ◯ | |
A2 | ☓ | ☓ | ☓ | |
A3 | ☓ | ☓ | ◯ | |
B1 | ◯ | ◯ | ◯ | |
B2 | ◯ | ☓ | ☓ | |
B3 | ◯ | ☓ | ◯ | |
C1 | ◯ | ◯ | ◯ | |
C2 | ☓ | ☓ | ☓ | |
C3 | ☓ | ☓ | ◯ | |
D1 | ◯ | ◯ | ◯ | |
D2 | ☓ | ☓ | ☓ | |
D3 | ☓ | ☓ | ◯ | |
- 本来やりたかった、ユーザがピンチアウトしたのをアプリが期待する scale にするののみを抜粋すると
| iOS+Safari | iOS+Chrome | Android+Chrome | 備考 |
A2 | ☓ | ☓ | ☓ | |
B2 | ◯ | ☓ | ☓ | |
C2 | ☓ | ☓ | ☓ | |
D2 | ☓ | ☓ | ☓ | |
iOS+Safari で、時間差を置いて変更するのしかうまくいかない
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
<title>viewport size example</title>
<style>
</style>
<script type="text/javascript">
/* ==================== SCRIPTS ==================== */
window.onload = function() {
draw();
}
function draw() {
var canvas = document.getElementById('canvassample');
if ( ! canvas || ! canvas.getContext ) {
return false;
}
var ctx = canvas.getContext('2d');
for (x = 0; x <= canvas.width; x+=20) {
for (y = 0; y <= canvas.height; y+=20) {
if (x % 100 === 0 || y % 100 === 0) {
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(x,y,5,5);
ctx.restore();
} else {
ctx.save();
ctx.fillStyle = 'lightgreen';
ctx.fillRect(x,y,5,5);
ctx.restore();
}
}
}
ctx.strokeRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '10pt monospace';
ctx.fillText('100px', 30, 115);
ctx.translate(110, 0);
ctx.rotate(0.5 * Math.PI);
ctx.fillText('100px', 30, 0);
ctx.restore();
}
function rewind() {
// 元通り、最大 5.0 倍。拡大可能にする
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1');
}
function expand() {
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=2.0, maximum-scale=5.0, user-scalable=1');
}
/* ==================== SCRIPTS ==================== */
</script>
</head>
<body>
Simple rewrite view port<br/>
<button onclick="rewind()">rewind</button>
<button onclick="expand()">expand</button><br/><br/>
<canvas id="canvassample" width="300" height="400"></canvas>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
<title>viewport size example</title>
<style>
</style>
<script type="text/javascript">
/* ==================== SCRIPTS ==================== */
window.onload = function() {
draw();
}
function draw() {
var canvas = document.getElementById('canvassample');
if ( ! canvas || ! canvas.getContext ) {
return false;
}
var ctx = canvas.getContext('2d');
for (x = 0; x <= canvas.width; x+=20) {
for (y = 0; y <= canvas.height; y+=20) {
if (x % 100 === 0 || y % 100 === 0) {
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(x,y,5,5);
ctx.restore();
} else {
ctx.save();
ctx.fillStyle = 'lightgreen';
ctx.fillRect(x,y,5,5);
ctx.restore();
}
}
}
ctx.strokeRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '10pt monospace';
ctx.fillText('100px', 30, 115);
ctx.translate(110, 0);
ctx.rotate(0.5 * Math.PI);
ctx.fillText('100px', 30, 0);
ctx.restore();
}
function rewind() {
// 1.0 倍にする
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0');
// 100ms 後に、元通り最大 5.0 倍。拡大可能にする
setTimeout(function(){
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1');
},100);
}
function expand() {
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=2.0, maximum-scale=5.0, user-scalable=1');
}
/* ==================== SCRIPTS ==================== */
</script>
</head>
<body>
Rewrite viewport with using setTimeout<br/>
<button onclick="rewind()">rewind</button>
<button onclick="expand()">expand</button><br/><br/>
<canvas id="canvassample" width="300" height="400"></canvas>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
<title>viewport size example</title>
<style>
</style>
<script type="text/javascript">
/* ==================== SCRIPTS ==================== */
window.onload = function() {
draw();
}
function draw() {
var canvas = document.getElementById('canvassample');
if ( ! canvas || ! canvas.getContext ) {
return false;
}
var ctx = canvas.getContext('2d');
for (x = 0; x <= canvas.width; x+=20) {
for (y = 0; y <= canvas.height; y+=20) {
if (x % 100 === 0 || y % 100 === 0) {
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(x,y,5,5);
ctx.restore();
} else {
ctx.save();
ctx.fillStyle = 'lightgreen';
ctx.fillRect(x,y,5,5);
ctx.restore();
}
}
}
ctx.strokeRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '10pt monospace';
ctx.fillText('100px', 30, 115);
ctx.translate(110, 0);
ctx.rotate(0.5 * Math.PI);
ctx.fillText('100px', 30, 0);
ctx.restore();
}
function rewind() {
// viewport を削除する
var viewport = document.querySelector("meta[name=viewport]");
//viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0');
if (viewport) {
var head = document.getElementsByTagName('head')[0];
head.removeChild(viewport);
}
// 100ms 後に、元通り最大 5.0 倍。拡大可能にする
setTimeout(function(){
var newViewport=document.createElement('meta');
newViewport.setAttribute('name','viewport');
newViewport.setAttribute('content','width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1');
head.appendChild(newViewport);
},100);
}
function expand() {
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=2.0, maximum-scale=5.0, user-scalable=1');
}
/* ==================== SCRIPTS ==================== */
</script>
</head>
<body>
Delete viewport and set again<br/>
<button onclick="rewind()">rewind</button>
<button onclick="expand()">expand</button><br/><br/>
<canvas id="canvassample" width="300" height="400"></canvas>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
<title>viewport size example</title>
<style>
</style>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<script type="text/javascript">
/* ==================== SCRIPTS ==================== */
window.onload = function() {
draw();
}
function draw() {
var canvas = document.getElementById('canvassample');
if ( ! canvas || ! canvas.getContext ) {
return false;
}
var ctx = canvas.getContext('2d');
for (x = 0; x <= canvas.width; x+=20) {
for (y = 0; y <= canvas.height; y+=20) {
if (x % 100 === 0 || y % 100 === 0) {
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(x,y,5,5);
ctx.restore();
} else {
ctx.save();
ctx.fillStyle = 'lightgreen';
ctx.fillRect(x,y,5,5);
ctx.restore();
}
}
}
ctx.strokeRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '10pt monospace';
ctx.fillText('100px', 30, 115);
ctx.translate(110, 0);
ctx.rotate(0.5 * Math.PI);
ctx.fillText('100px', 30, 0);
ctx.restore();
}
function rewind() {
$('#btnExpand').trigger('click');
// 100ms 後に、元通り最大 5.0 倍。拡大可能にする
setTimeout(function(){
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1');
},1000);
}
function expand() {
var viewport = document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=2.0, maximum-scale=5.0, user-scalable=1');
}
/* ==================== SCRIPTS ==================== */
</script>
</head>
<body>
occur button click event<br/>
<button onclick="rewind()">rewind</button>
<button onclick="expand()">expand</button><br/><br/>
<canvas id="canvassample" width="300" height="400"></canvas>
</body>
</html>
より実践的な例 †
- なんかのシミュレーションをする Web ページがあったと思いねぇ
- これを流用して、スマートフォン版を作ったと思いねぇ
- MENU領域の横幅を 0px にして、別途メニュー画面を用意。う〜ん、画面遷移の管理なんかをする Javascript が MENU領域にあるので、コンテンツ領域だけにするわけにはいかない
- 初期パラメータ設定画面はそのままにした。素直な TABLE
- Flash や Java Applet の結果表示アプリは、HTML5 (Enchant.js や D3.js) で作りなおした
- コレでひと通り動くんだけど、viewport に関する問題発生
- ユーザが、初期パラメータ設定画面で、ピンチアウトしたままシミュレーション開始ボタンを押すと、そのままの scale で結果表示画面に遷移してしまう
- なんとか、結果表示画面を scale=1.0 で表示できないものか
- 問題は FRAME 自体が拡大されてしまっているため、結果表示画面に scale を設定しても意味が無いこと
- 解決法
- 結果表示画面の初期化処理で、親FRAME の <meta name="viewport"> の initial-scale を 1.0 にすりゃ良いんじゃないですか?
- コレだけではうまくいかない。現在の画面の表示倍率がどうであろうと、<meta name="viewport"> の initial-scale が変更されなければ、現在表示中の画面の表示倍率は変更されない
- う〜んどうしたものか ?
- いいこと思いついた 初期パラメータ設定画面の initial-scale を 2.0 にすりゃイイんだ
- そもそも、表示内容がちっこいから、ユーザがピンチアウトして、それが問題になっているんだから、最初から 2.0 倍くらいに拡大してもいいだろう。いいに違いない ! というか、それ以外の方法が思いつかないので、いいことにしよう
iOS+Safari | iOS+Chrome | Android+Chrome |
◯ | ◯ | ◯ |
一見良さそうだけど ... オチ
- サンプルアプリ
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1" />
</head>
<frameset cols="0px,*">
<frame src="frame1.html" name="frame1">
<frame src="frame2.html" name="frame2">
</frameset>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
MENU
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
window.onload = function() {
// 親FRAME画面の初期スケール 2.0 (1.0〜5.0を許可)
var viewport = window.parent.document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=2.0, minimum-scale=1.0, maximum-scale=5.0, user-scalable=1');
}
function go() {
document.location.href = 'frame3.html';
}
</script>
</head>
<body>
SET PARAMETER <br/>
<form action="frame3.html">
<table border="0">
<tr><td>x</td><td><input type="text"/></td></tr>
<tr><td>y</td><td><input type="text"/></td></tr>
<tr><td>z</td><td><input type="text"/></td></tr>
<tr><td>v</td><td><input type="text"/></td></tr>
<tr><td>e</td>
<td>
<select>
<option>Solid</option>
<option>Liquid</option>
<option>Gas</option>
</select>
</td>
</tr>
<tr><td colspan="2"><button onclick="go()">Simulate</button></td></tr>
</table>
</form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>viewport size example</title>
<style>
</style>
<script type="text/javascript">
/* ==================== SCRIPTS ==================== */
window.onload = function() {
draw();
// 親FRAME画面の初期スケール 1.0 (拡大縮小不可)
var viewport = window.parent.document.querySelector("meta[name=viewport]");
viewport.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=1');
}
function draw() {
var canvas = document.getElementById('canvassample');
if ( ! canvas || ! canvas.getContext ) {
return false;
}
var ctx = canvas.getContext('2d');
for (x = 0; x <= canvas.width; x+=20) {
for (y = 0; y <= canvas.height; y+=20) {
if (x % 100 === 0 || y % 100 === 0) {
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(x,y,5,5);
ctx.restore();
} else {
ctx.save();
ctx.fillStyle = 'lightgreen';
ctx.fillRect(x,y,5,5);
ctx.restore();
}
}
}
ctx.strokeRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '10pt monospace';
ctx.fillText('100px', 30, 115);
ctx.translate(110, 0);
ctx.rotate(0.5 * Math.PI);
ctx.fillText('100px', 30, 0);
ctx.restore();
}
function back() {
document.location.href = 'frame2.html';
}
/* ==================== SCRIPTS ==================== */
</script>
</head>
<body>
<button onclick="back()">back</button><br/>
<canvas id="canvassample" width="300" height="400"></canvas>
</body>
</html>
- オチ iOS で select ボックスを選択すると画面が拡大する。そうすると結果画面の scale 書き換えが効かなくなる
iOS+Safari | iOS+Chrome | Android+Chrome |
◯ | ☓ | ☓ |
→ う〜ん。もう
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
にするしかないかな。initial、maximum を 1 にすると、select ボックス選択中も画面が拡大されない
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
</head>
<frameset cols="0px,*">
<frame src="frame1.html" name="frame1">
<frame src="frame2.html" name="frame2">
</frameset>
</html>
<!DOCTYPE html>
<html>
<head>
</head>
<body>
MENU
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
window.onload = function() {
}
function go() {
document.location.href = 'frame3.html';
}
</script>
</head>
<body>
SET PARAMETER <br/>
<form action="frame3.html">
<table border="0">
<tr><td>x</td><td><input type="text"/></td></tr>
<tr><td>y</td><td><input type="text"/></td></tr>
<tr><td>z</td><td><input type="text"/></td></tr>
<tr><td>v</td><td><input type="text"/></td></tr>
<tr><td>e</td>
<td>
<select>
<option>Solid</option>
<option>Liquid</option>
<option>Gas</option>
</select>
</td>
</tr>
<tr><td colspan="2"><button onclick="go()">Simulate</button></td></tr>
</table>
</form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>viewport size example</title>
<style>
</style>
<script type="text/javascript">
/* ==================== SCRIPTS ==================== */
window.onload = function() {
draw();
}
function draw() {
var canvas = document.getElementById('canvassample');
if ( ! canvas || ! canvas.getContext ) {
return false;
}
var ctx = canvas.getContext('2d');
for (x = 0; x <= canvas.width; x+=20) {
for (y = 0; y <= canvas.height; y+=20) {
if (x % 100 === 0 || y % 100 === 0) {
ctx.save();
ctx.fillStyle = 'red';
ctx.fillRect(x,y,5,5);
ctx.restore();
} else {
ctx.save();
ctx.fillStyle = 'lightgreen';
ctx.fillRect(x,y,5,5);
ctx.restore();
}
}
}
ctx.strokeRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = '10pt monospace';
ctx.fillText('100px', 30, 115);
ctx.translate(110, 0);
ctx.rotate(0.5 * Math.PI);
ctx.fillText('100px', 30, 0);
ctx.restore();
}
function back() {
document.location.href = 'frame2.html';
}
/* ==================== SCRIPTS ==================== */
</script>
</head>
<body>
<button onclick="back()">back</button><br/>
<canvas id="canvassample" width="300" height="400"></canvas>
</body>
</html>
HTML