これは何?  †
- スマホ向け 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 | ☓ | ☓ | ☓ |  |  
 
<!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 が変更されなければ、現在表示中の画面の表示倍率は変更されない
- う〜んどうしたものか ?
- いいこと思いついた  ![[bigsmile]](image/face/bigsmile.png) 初期パラメータ設定画面の initial-scale を 2.0 にすりゃイイんだ 初期パラメータ設定画面の 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