Creating web page for iPhone

iPhone 対応 web page の作成

のびのびになっていた iPhone 対応の web page 作成方法をまとめてみる。
なかなかきれいにまとまったサイトが無かったので、
http://wiki.sohaya.com/index.php/%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%83%AF%E3%83%BC%E3%82%AF
で紹介されている動いているサンプルのページを調べてみる。

http://groupaware.mobi/iphone/#_Samples

このページにアクセスしてみると iPhone の UI の様な振る舞いを(ある程度)見せるページになっている。

このサンプルの head 部分は次のような感じ。


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>iPhone Samples</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>

<link rel="apple-touch-icon" href="images/apple-touch-icon.png"/>

<style type="text/css" media="screen">@import "iphonenav.css";</style>
<style type="text/css" media="screen">@import "samples.css";</style>
<script type="text/javascript" language="javascript" src="iphonenav.js"></script>
<script type="text/javascript" language="javascript" src="samples.js"></script>
<script type="text/javascript" language="javascript" src="canvasProgress.js"></script>
</head>


まずは CSS から。

CSSNetBeans で開いてみると黄色のアンダーラインがついている行があるので、そこを足がかりに -webkit-text-size-adjust 等で検索。

http://travel-lab.info/tech/pblog/article.php?id=131

比較的古いけど、入門には良さそうなページだ。

box-sizing でも iPhone 関連のページがよく見つかる。これは element のサイズを border を含めて計算するか、含めないで計算するかの指定になる。

読んでいく上で忘れていたのは .panel の様に . で始めるのは class の定義で#homeButton の様に # で始めるのは id に関する定義という事。

また body > div のように書くと body 直下の div という意味。

中には解釈しにくい記述もあるが、それはまたの機会に。

サンプルのリストをクリックすると Cocoa アプリのようにスライドして次の View に移るような動作を見せる。html でみていくと、これは別のページに移動している訳ではなく、同じファイルの中の div 要素を切り替えているらしい事がわかる。


ここからはどうやら javascript でやっているようなので javascript をみていく。

まず、先頭が


(function() {
で始まって

})();

で終わっている事に面食らう。この厳密な意味はおそらくグローバル変数の設定やイベントハンドラの設定のためだろうか。


window.onload = function() {
...
}

のようなサンプルは見かけるのだが...


変数の定義のほかに次のようなハンドラの設定がある。
load のハンドラも色々な事をやっているのでその前の変数等の確実な初期化かなとも思ったが、最初と最後の行をコメントにしても問題なく動作したので、何か特殊な処理系に関する計らいなのかもしれない。


addEventListener("load", function(event)
{
//window.console.log("addEventListener load");
var body = document.getElementsByTagName("body")[0];
...

addEventListener("click", function(event)
{
// default の動作を抑制、これが無いと時々表示が変になる。
event.preventDefault();

var link = event.target;
// a tag を見つけるまで親をたどる
// localName は DOM でタグの名前をさすらしい
while (link && link.localName.toLowerCase() != "a")
link = link.parentNode;

// logging 機能はあまり長いものは出力できないようだが
// 動作を確認するには便利。ただ、これを使うと Firefox
// ではうまく動く動かない
window.console.log("click hdlr event:" + event + " srce:" + event.srcElement );

// link.hash はページ中のアンカのこと。サンプル内には
// #Windows, #Buttons の様にアンカが定義されている。
// (id="Buttons" の様な div がある。)
if (link && link.hash)
{
var page = document.getElementById(link.hash.substr(1));
// homeButton が押された場合には右にスライドしてほしかったので
// いろいろ試した結果、これで動作する事を確認。
// prototype.js では Event.element(event) で event source を
// 得られるようだ。
if (document.getElementById("homeButton") == event.srcElement) {
showPage(page, true);
} else {
showPage(page);
}
}
}, true);

この click のハンドラが Cocoa view の遷移の様な動作の起点となっている。

showPage(page, backwd)
page は実際には div 要素になっていて、backwd で上位のページに戻る事を示す事ができる。ページ遷移に関する部分は以下のようになっている。


location.href = currentHash = hashPrefix + page.id;
pageHistory.push(page.id);

var fromPage = currentPage;
currentPage = page;

var pageTitle = document.getElementById("pageTitle");
pageTitle.innerHTML = page.title || "";

var homeButton = document.getElementById("homeButton");
if (homeButton)
{
// 遷移先によって左上の back 相当のボタンの表示を決める。
homeButton.style.display = (page.id) == "Samples" ? "none" : "inline";
var parentName = page.getAttribute("parentName");
if(parentName != undefined)
{
homeButton.innerHTML = parentName || "";
homeButton.href="#"+parentName;
}
}
if(fromPage) {
// タイマを使ってページ遷移のアニメーションをおこなう
setTimeout(swipePage, 0, fromPage, page, backwards);
}


function swipePage(fromPage, toPage, backwards)
{
//window.console.log("swipePage f:" + fromPage + " t:" + toPage + " bkwd:" + backwards);
toPage.style.left = "100%";
toPage.setAttribute("selected", "true");
scrollTo(0, 1); // 意味不明...

// loop で徐々に from から to の表示比率をかえる
// style.left を変化させる事で遷移アニメーションを
// 実現
var percent = 100;
var timer = setInterval(function()
{
percent += animateX;
if (percent <= 0)
{
percent = 0;
fromPage.removeAttribute("selected");
clearInterval(timer);
}

fromPage.style.left = (backwards ? (100-percent) : (percent-100)) + "%";
toPage.style.left = (backwards ? -percent : percent) + "%";
}, animateInterval);
}


HTML に戻って最初に表示される画面付近に相当する部分は次のようになっている。


<body onorientationchange="updateOrientation()">
<h1 id="pageTitle"></h1>
<a id="homeButton" class="button" href="#Samples">Samples</a>
<!-- <a class="button" href="#searchForm">Search</a> -->
<ul id="Samples" title="Samples" selected="true">
<li><a href="#Windows">Windows</a></li>
<li><a href="#Buttons">Buttons</a></li>
<li><a href="#Canvas">Canvas</a></li>
<li><a href="#Fonts">Fonts</a></li>
<li><a href="#Text">Text</a></li>
...

一見 a href=... の部分は普通の HTML の様にみえるが、href= の後に URL を書いてもページはきり買わない。なぜなら、click ハンドラは #anchor の様なケースしか想定していないからだ。


HTML のほかの部分をみると window.location.href (DOM?) を onclick で書き換える事でそのページに移動できるようだ。


<li><a href="#" onclick="window.location.href = 'expr.html'">Experiments
</a></li>


Window 関連処理

ウィンドウを開く処理は onclick から JS 関数を呼び出している。


<a class="borderImageBtn blueButton" href="#" onclick="showChild();">New Child</a>


var win = null;
function showChild() {
var loc = "cld.html";
var winStr = "resizable=1,status=yes,scrollbars=yes,toolbar=no,location=no,menu=no";
var winName = "iPhone";
win = window.open(loc, winName , winStr);
}

次のボタンで消せる。


<a class="borderImageBtn blueButton" href="#"
onclick="if(windowCheck()){ win.close(); }">Close Child</a>

ウィンドウが開いている間はその情報を得られる。


<a class="borderImageBtn blueButton" href="#"
onclick="alert('Position: (' + win.screenX + ', ' +
win.screenY + '), Name: ' + win.name);">Show Info</a>

通常のブラウザのように簡単なダイアログ表示ができる。


<a class="borderImageBtn blueButton" href="#"
onclick="alert('Hello iPhone');">Alert</a>
<a class="borderImageBtn blueButton" href="#"
onclick="confirm('Hello iPhone?');">Confirm</a>
<a class="borderImageBtn blueButton" href="#"
onclick="prompt('Hello...','iPhone!');">Prompt</a>

prompt() ではユーザーの入力が得られる訳だが、その内容は prompt()の戻り値として得られる。文字コードはどうなるんだろ?

class の記法の厳密なものを知らないのだが、みる限り複数のクラスをスペースで区切って指定できるようだ。


.borderImageBtn
{
margin: 0;
height: 30px;
width:110px;
text-align: center;
font: bold 12px/30px Helvetica, sans-serif;
text-decoration: none;
display: block;
}
.blueButton
{
border-left: 12px;
border-right: 12px;
color: white;
text-shadow: #000 0px 1px 1px;
-webkit-border-image:url("images/blueButton.png") 0 14 0 14;
}

ボタンのサンプルは今ひとつわかりにくいが上半分に薄く白い領域をもうけたイメージと background-color の組み合わせで色のついたキラキラした感じのボタンがつくれる。これは特に iPhone 固有ではなさそうだが。


.glossyButton
{
height: 24px;
width: 43px;
border: 0px;
font: bold 18px/24px Helvetica, sans-serif;
background-color: green;
background-image: url("images/glossy.png");
background-repeat: no-repeat;
text-decoration: none;
color: white;
display: block;
padding: 6px;
text-align: center;
text-shadow: #000 -2px -2px 1px;
vertical-align: bottom;
white-space: nowrap;
-webkit-border-radius: 9px;
}


トグルボタンは class を入れ替える事で on/off スイッチを実現。


<a class="togButton togButtonOn noHighlight" href="#"
onclick="toggleBtn(this);"></a>

  • webkit-tap-highlight-color のサンプルは次の属性でイメージボタンが

押されても要素のハイライト(取り囲む部分がグレーになる)がなくなる事を
示しているらしい。


-webkit-tap-highlight-color:rgba(0,0,0,0.0);


Canvas のサンプルはあーこんな事もできるのかといった程度。canvas タグは Safari が導入したものらしいが、mozilla はサポートしつつも仕様に文句をつけていたりする。HTML 5 で正式採用か? OpenGL ES で3次元もいけるようになるかも。


Font のサンプルは JS 関数で要素のフォントサイズを変えるぐらいで、目新しいところは無い。


Text のサンプルは contentEditable="true" 属性のついた div が表示されている。MacSafari からは対象部分が編集できるのだが iPhone/iPod touch からはうまくうごかない。操作が悪いのか?


Orientation hook のサンプルは orientationBucket という id の div があるのみ。body に onorientationchange の指定がありupdateOrientation の中で orientationBucket の中身を window.orientation に従って更新している。


<body onorientationchange="updateOrientation()">



function toggleBtn(which)
{
which.className = (which.className.indexOf('togButtonOff') > 0) ?
'togButton togButtonOn noHighlight'
: 'togButton togButtonOff noHighlight';
}

load handler で checkOrientAndLocation() という関数をよんで body の orient attribute をportrait or landscape にセットしている。これは何か意味があるのだろうか?


Console のサンプルは window.console.log, warn, error でログをボタンから出すサンプル。


Application Integration のサンプル。
画面の見た目は iPhone の設定画面のようになっている。
下地のストライプは次のようなクラスがきいている。


.panel {
box-sizing: border-box;
-webkit-box-sizing: border-box;
padding: 10px;
background: #c8c8c8 url(pinstripes.png);
}

項目は list 要素で、下地を白、境界線を書いて、全体を丸まった枠で表示するのは次の指定。


ul
{
padding: 0px;
width: 298px;
margin: 0px 0px;
margin-right: 10px;
background-color: white;
-webkit-border-radius: 8px;
border: 1px solid rgb(217,217,217);
}

li
{
line-height: 17px;
list-style-type: none;
color:black;
border-bottom: 1px rgb(217,217,217) solid;
padding: 10px 10px 17px 10px;
}

click 時の動作は window.location.href を onclick= で書き換える事で行っている。


地図の表示の URL としては次のようなものが使われている。


http://maps.google.com/maps?q=1+Infinite+Loop,+Cupertino,+CA+95014
http://maps.google.com/maps?daddr=Santa+Cruz,+CA&saddr=1+infinite+loop,+cupertino,+ca

mail, 電話は次のよう。


foo@example.com?cc=bar@example.com&bcc=bot@example.com&subject=Greetings%20from
%20Santa%20Cruz!&body=Wish%20you%20were%20here!
tel:+1 (800) 555-5555

外部ドキュメントのサンプルはボタンを使い window.location.href を使ったリンク。


Design Pattern のサンプルは全て理解していないが、Dynamic List で iTunes Store の押すとエントリが増えるようなリストをあつかっているが、突き詰めてみると JS でリスト要素を追加している。その操作の際に more のリンクの右端にspinner が表示される。これはまた canvas を使って書いているようだ。


サンプルで学んだ事をもとに既存のページを書き換えてみたが、今ひとつ合点のいかないところもあった。


CSSiphone とそれ以外で次のように切り替えられる。


<link media="only screen and (max-device-width: 480px)"
href="iPhone.css" type="text/css" rel="stylesheet" />
<link media="screen and (min-device-width: 481px)"
href="not-small-device.css" type="text/css" rel="stylesheet" />


JavaScript では User Agent の判定ができるし、それを使って別ページに移動させることもできる。


head 内で次のようにする事で専用ページに飛ばす事ができる。


<script type="text/javascript">
<!--
function updateUserAgent() {
document.getElementById('user-agent').innerHTML = navigator.userAgent;
}
if (navigator.userAgent.indexOf('iPhone') != -1) {
location.href = 'ipn/index.html';
}
if (navigator.userAgent.indexOf('iPod Touch') != -1) {
location.href = 'ipn/index.html';
}
//-->
</script>


デフォルトでは 980px 幅を縮小表示という作りなので、とくに Cocoa っぽいリスト動作をさせたい場合には viewport でデバイス相応の拡大率、幅にしておかないと見栄えが悪い。


CSS の選択的指定、JavaScript による Agent 判定はできるけど、viewport 指定はどうやったら選択的にできるのか少し悩んでしまった。でも、よく考えるとviewport のメタタグは Safari (for iPhone?) のみだとすると何の問題も無いのかもしれない。


サンプルに元々あった viewport meta tag.


<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>


user-scalable=0 とするとピンチ操作の拡大縮小ができなくなるので、物足りない感じもする。それを外して maximum-scale=2.0 のようにすると拡大できるのだが、画面トップに配置したナビゲーションバーらしきものまでサイズが変わるのであまりよろしくない。


次のようなやり方も紹介されていた。
Holy Grail 手法のおまじない
http://iphonewebdev.com/examples/viewports/viewport-width-480-maximum-scale-6667.html


<meta name="viewport" id="iphone-viewport" content="width=480, maximum-scale=0.6667" />


その他の参考


一度よく読んどいた方がよさそう。
http://developer.apple.com/jp/internet/webcontent/bestwebdev.html


この本は買っといた方がいいかも。
http://www.openspc2.org/reibun/JavaScript_technique/