タブレットやモバイル向けにWebサイトをデザインするには

今ではずいぶん当たり前になってきているけれども。
実際に自分でやったことはなかったので、少し調べてみた。
必要な手順をメモしておく。


PC向けに、特に横長にデザインされたWebサイトは、スマートデバイスで表示したときにレイアウトが崩れてしまうことがある。昨今では、PC向けのレイアウトとは別に、モバイル向けのページを用意しているサイトが多い。

それを実際にどうやるか。

サーバーはJava、クライアントはiPhoneとして考えてみる。

HTMLを構成するサーバーサイドスクリプトを別々に用意する

サーバー側では、PC向けと、iPhone向けのページを別々に用意する。ファイル名で分けても良いが、ディレクトリを別にして同一ファイル名にするのが分かりやすい気がする。

・WEB-INF/content/index.jsp
・WEB-INF/content/mobile/index.jsp


User-Agentヘッダやリクエストパラメータでレイアウトを判断する

User-Agentの値を参照すれば、クライアントのデバイスやブラウザの種類を判定することができる。しかし、単純にUser-Agentの値だけをもってページレイアウトを決定すべきではないかもしれない。

たとえば、場合によっては、モバイル向けのページは、PC向けのページに比べて情報量が少なかったりする。iPhoneでもPC向けのページを表示したいということはあるだろう。

そう考えると、必ずしも、User-Agentでモバイルだと判定して、固定的にモバイル向けのページに遷移させるのが良いとは言い切れない。より柔軟にサイトを設計するには、リクエストパラメータやセッション変数を利用するのが良いと考えてみた。


・リクエストパラメータの指定を最優先する
たとえば、layout=pc という指定があれば、PC向けのページを表示する。

・リクエストパラメータの指定がない場合は、セッション変数を参照する
こうしておけば、一度 layout=pc と指定されたセッションでは、以降、リクエストパラメータの指定がなくても、PC向けのページを表示できるし、改めて layout=mobile とされた場合はそちらを優先できる。

・どちらも指定がない場合はUser-Agentの値で判断する
User-Agentの判断は、単純な文字列検査になる。

Struts2のActionクラスなら、以下のようにすれば良いだろう。

HttpServletRequest request = ServletActionContext.getRequest();
String userAgent= request.getHeader("User-Agent");
boolean isMobile = 0 <= userAgent.indexOf("iPhone");

iPhone以外にも、AndroidWindows Phoneなども考慮するならば、もう少し検査が必要になる。

【参考】
http://www.openspc2.org/userAgent/


サーバーサイドでページを振り分ける

PC向けのページを表示するか、モバイル向けのページを表示するかの判断ができたら、後はフォワード先を切り替えてやるだけでよい。

これもStruts2の例で。

@Results({
    @Result(name = "success", location = "index.jsp"),
    @Result(name = "success_m", location = "mobile/index.jsp"),
})
public class IndexAction {
    public String execute() throws Exception {
        // ...
        
        return isMobile ? "success_m" : "success";
    }
}

上記の例では、アクションクラスでフォワード先の切り替えをしているが、JSP側でセッション変数を判断(アクションクラスでセッション変数にページレイアウトを記憶しておく)した上でフォワードするようにしておいて、そのJSPを各ページでincludeするようにしても良いと思う。そのようにする場合は、アクションクラス側ではResultの定義を個別に設定することはせずに、"success"を返すだけで良い。


METAタグでビューポートを指定する

モバイル向けのHTMLページのヘッダ部に、以下のようにブラウザの表示幅を指定してやる。

<html>
<head>
<meta name="viewport" content="width=320" />
</head>
<body></body>
</html>

iPad向けにページをデザインする場合はwidth=1024にしてやるとよい。

あとは最大幅に合わせて、普通にHTMLとCSSをデザインする。


まとめ

以上の手順で試したところ、うまくPC, iPhone, iPadでページを切り替えることができた。

サーバーサイドにJavaStruts2を使うならば、これまで提示したコード例をまとめてTemplate Methodの形にしておくと使いやすいかもしれない。

これまでのまとめとして、以下にサンプルコードを掲載しておく。

public enum BrowseLayout {
    PC,
    Tablet,
    Mobile;

    public static final BrowseLayout Default = PC;

    public static BrowseLayout userAgentOf(String userAgent) {
        if (0 <= userAgent.indexOf("iPhone")) {
            return Mobile;
        }
        else if (0 <= userAgent.indexOf("iPod")) {
            return Mobile;
        }
        else if (0 <= userAgent.indexOf("iPad")) {
            return Tablet;
        }
        else if (0 <= userAgent.indexOf("Tablet")) {
            return Tablet;
        }
        else if (0 <= userAgent.indexOf("Android")) {
            return Mobile;
        }
        else if (0 <= userAgent.indexOf("Windows Phone")) {
            return Mobile;
        }

        return Default;
    }
}



abstract class AbstractAction extends ActionSupport implements SessionAware {
    protected static final String SUCCESS_TABLET = "success_t";
    protected static final String SUCCESS_MOBILE = "success_m";
    protected static final String EXPIRED = "expired";

    // ページレイアウト用のセッションキーを適当に決めておく
    private static final String SESSION_LAYOUT = "html.layout";

    // ページレイアウト用のリクエストパラメータ名を適当に決めておく
    private static final String REQUEST_PARAMETER_LAYOUT = "layout";

    private Map<String, Object> sessionMap;

    // サブクラスでオーバーライドする
    protected String executeMyAction() throws Exception {
        return SUCCESS;
    }

    @Override
    public void setSession(Map<String, Object> sessionMap) {
        this.sessionMap = sessionMap;
    }
    
    // テンプレートメソッド
    public String execute() throws Exception {
        // クライアントのブラウザに応じてHTMLレイアウトを判断する
        reserve.distinguishLayout();

        try {
            // サブクラスのアクションを実行する
            String result = executeMyAction();

            // アクションの実行ログを出したりなんなり
            // ...

            return result;
        }
        catch (Exception e) {
            // エラーページに遷移させたりなんなり
        }
    }

    // サブクラスでページ切り替えを判断するためのメソッドを用意しておく
    protected final BrowseLayout getHtmlLayout() {
        BrowseLayout layout = (BrowseLayout)sessionMap.get(SESSION_LAYOUT);

        if (null == layout) {
            layout = BrowseLayout.Default;
        }

        return layout;
    }

    private void distinguishLayout() {
        //
        // クライアントに応じてHTMLレイアウトを判断する
        //
        BrowseLayout layout = null;

        // リクエストパラメータの指定を最優先する
        HttpServletRequest request = ServletActionContext.getRequest();
        String requestValue = request.getParameter(REQUEST_PARAMETER_LAYOUT);

        if (null != requestValue) {
            // リクエストパラメータ値は、Enum値の名前(toString)そのものとする
            layout = BrowseLayout.valueOf(requestValue);
        }

        // 次にセッション属性の値を優先する
        if (null == layout) {
            layout = (BrowseLayout)sessionMap.get(SESSION_LAYOUT);
        }

        // 最後にリクエストヘッダのユーザーエージェントで判断する
        if (null == layout) {
            String userAgent= request.getHeader("User-Agent");

            if (null != userAgent) {
                layout = BrowseLayout.userAgentOf(userAgent);
            }
            else {
                layout = BrowseLayout.Default;
            }
        }

        // レイアウト情報をセッションに保存する
        sessionMap.put(SESSION_LAYOUT, layout);
    }
}

あえてモバイル向けのページを別途作るようなことをしないという選択も

Appleとかいう企業は、Webサイトにモバイル向けのページを用意しないそうだ。

PCでもモバイルでも同じようにHTMLを表示し、モバイルで見たときにもそれなりにインパクトを与えられるサムネイル(画像+短いアピール文言など)を用意しておく。関心を引かれたユーザーは、その箇所をダブルタップ(拡大)して、より詳細を確認すれば良い、という寸法だ。

【参考】
http://www.pxt.jp/ja/diary/article/257/

まあ、広告的なサイトであれば、この手法も悪くないだろう。

機能的なサイトを構築するのであれば、やはりモバイル向けには別途デザインが必要だと思う。





おしまい。