サイトマップ

Webサイトをスマートフォン対応にする

Andoid向けのアプリケーション開発を始めたものの、自社サイトがスマートフォン対応してないのも格好がつきません。かといって、PCとスマートフォンのアウトプットを出しわけするのも、DokuWikiではなかなか難しいようです。

で、ちょっと考えました。スマートフォンといってもまっとうなフルブラウザを持っています。つまり、CSSやJavaScriptもPCのそれと遜色ありません。違うのは画面の大きさだけと言ってもいいでしょう。

ならば、JavaScriptでレイアウトを書き換えるようにしてしまえば良いのではないか?そういう発想でちょこちょことやってみたら、案外簡単で効果的な結果が得られることがわかりました。

DokuWikiに組み込むには

  • conf/userscript.js - 標準のスクリプトに追加したい内容がある場合、このファイルを作成しておけばDokuWikiにより自動的に読み込まれるようになっています。ウインドウサイズを判定して、レイアウトを組み替えるスクリプトを記述します。
  • lib/tpl/テンプレート名/main.php - スマートフォンではviewportをきちんと指定していないと、勝手に仮想的な画面サイズを広げてしまうので、ヘッダ領域に追加しておきます。この記述はPCには影響しないと思います。
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />

方針

  • ウインドウ幅によってページを組み替える
    User-Agentの値を見て処理をするのがよくある手法ですが、ここではUser-Agentを使わず、ウインドウの幅を基準にページの組み換えをすることにします。これによって、PCのブラウザでもウインドウを小さくしたときに上手く表示できるようになります。
  • 左右のサイドバーの部分は最後に持っていく
    左右のサイドバーに表示されるのはナビゲーションや追加的な情報なので、本文の後に表示されるようにします。そういうのが携帯サイトでの常道のようです。
  • 大きな画像
    大きな画像も画面に収まるように縮小することも考えたのですが、小さすぎて見えなくなっては元も子もありません。ならばタップしたら拡大ということもやってみましたが、スクロールするときについつい画像をタップしてしまい、大変に邪魔くさいことになってしまいました。で、サイズはそのままにして、左右にスクロールさせて見られるようにしました。
  • preタグに囲まれた領域
    プログラムコードの記述にpreを多用しています。これは途中で改行しないため、ほとんどの場合はみだしてしまって右側を見ることができません。そこで、画像と同様に左右スクロールするようにしました。
  • 大きなテーブル
    画像と同様に左右スクロールするのが良さそうですが、本サイトではさほど幅の広いテーブルは使ってないので、今回は対応しないことにします。

conf/userscript.js

//ページ読み込み完了後に動くように、DokuWikiのaddInitEvent()関数を使って呼び出すようにする。
addInitEvent(function() {
        if (typeof(window.innerWidth) != 'number') return;
        if (window.innerWidth > 500) return;
 
        Function.prototype.bind = function(target) {
                var method = this;
                return function() {
                        return method.apply(target, arguments);
                }
        };
 
        var addscroller = function(wrapper) {
                wrapper.ontouchstart = function(evt) {
                        //console.log(this);
                        //console.log(evt.touches[0].pageX + ',' + evt.touches[0].pageY);
                        this.oldX = evt.touches[0].pageX;
                        this.oldY = evt.touches[0].pageY;
                }.bind(wrapper);
                wrapper.ontouchmove = function(evt) {
                        if (this.offsetWidth >= this.scrollWidth) return;
                        var dx = this.oldX - evt.touches[0].pageX;
                        var dy = this.oldY - evt.touches[0].pageY;
                        if (Math.abs(dx) > Math.abs(dy)) {
                                evt.preventDefault();
                                evt.stopPropagation();
                                //console.log('ontouchmove:' + this.oldX + '<=>' + evt.touches[0].pageX);
                                this.scrollLeft = this.scrollLeft + this.oldX - evt.touches[0].pageX;
                        }
                        this.oldX = evt.touches[0].pageX;
                        this.oldY = evt.touches[0].pageY;
                }.bind(wrapper);
                wrapper.ontouchend = function(evt) {
                        //console.log(this);
                        this.oldX = undefined;
                        this.oldY = undefined;
                }.bind(wrapper);
        };
 
        var getabs = function(node) {
                var p = node;
                var result = { x:0, y:0 };
                while (p) {
                        result.x += p.offsetLeft;
                        result.y += p.offsetTop;
                        p = p.offsetParent;
                }
                return result;
        };
 
        var page = document.querySelector('div.page');
        page.style.padding = '5px';
        var table = document.querySelector('table.layout-hbox');
 
        //mainカラムの中身を全部先頭に出す。
        var el = document.querySelectorAll('td.layout-body > *');
        for (var i = 0; el && i < el.length; i++) {
                page.appendChild(el[i]);
        }
 
        //toc
        var e = document.querySelector('div.toc');
        if (e) {
                e.style.cssFloat = 'none';
                e.style.width = 'auto';
                e.style.margin = 0;
        }
 
        //spacer除去
        var el = document.querySelectorAll("table.layout-hbox td.spacer");
        for (var i = 0; el && i < el.length; i++) {
                el[i].parentNode.removeChild(el[i]);
        }
 
        //search
        var e = document.querySelector('div.search');
        if (e) {
                page.appendChild(e);
                e.style.cssFloat = 'none';
                e.style.textAlign = 'left';
        }
 
        var e = document.querySelector('form#dw__search');
        if (e) {
                e.style.cssFloat = 'none';
                e.style.textAlign = 'left';
        }
 
        //カラムを下へ
        var el = document.querySelectorAll("table.layout-hbox > tbody > tr > td");
        if (el && el[2]) {
                for (var i = el[2].childNodes.length - 1; i >= 0; i--) {
                        var item = el[2].childNodes.item(i);
                        page.appendChild(item);
                        item.style.width = '';
                }
        }
        if (el && el[0]) {
                for (var i = el[0].childNodes.length - 1; i >= 0; i--) {
                        var item = el[0].childNodes.item(i);
                        page.appendChild(item);
                        item.style.width = '';
                }
        }
 
        //お問い合わせは最後へ
        var e = document.querySelector('div#bar__topright');
        if (e) {
                page.appendChild(e);
                e.style.cssFloat = 'none';
        }
 
        //top bar
        var e = document.querySelector('div#bar__top');
        if (e) {
        if (e) {
                var wrapper = document.createElement('div');
                e.parentNode.insertBefore(wrapper, e);
                wrapper.appendChild(e);
                wrapper.style.overflow = 'hidden';
                wrapper.style.whiteSpace = 'nowrap';
                addscroller(wrapper);
        }
 
        //bottom bar
        var e = document.querySelector('div#bar__bottom');
        if (e) {
                var wrapper = document.createElement('div');
                e.parentNode.insertBefore(wrapper, e);
                wrapper.appendChild(e);
                wrapper.style.overflow = 'hidden';
                wrapper.style.whiteSpace = 'nowrap';
                addscroller(wrapper);
        }
        var e = document.querySelector('div#bar__bottomright');
        if (e) {
                e.style.cssFloat = 'none';
        }
 
        //pre
        var el = document.querySelectorAll('pre');
        for (var i = 0; el && i < el.length; i++) {
                addscroller(el[i]);
        }
 
        //layout-tree
        var el = document.querySelectorAll('table.layout-tree');
        for (var i = 0; el && i < el.length; i++) {
                var wrapper = document.createElement('div');
                el[i].parentNode.insertBefore(wrapper, el[i]);
                wrapper.appendChild(el[i]);
                wrapper.style.overflow = 'hidden';
                wrapper.style.whiteSpace = 'nowrap';
                addscroller(wrapper);
        }
 
        //img.media
        var el = document.querySelectorAll('img.media, img.mediacenter');
        for (var i = 0; el && i < el.length; i++) {
                var wrapper = document.createElement('div');
                el[i].parentNode.insertBefore(wrapper, el[i]);
                wrapper.appendChild(el[i]);
                wrapper.style.overflow = 'hidden';
                wrapper.style.whiteSpace = 'nowrap';
                addscroller(wrapper);
        }
 
 
        //レイアウトテーブルを削除
        table.parentNode.removeChild(table);
});

結論

  • 小一時間ほどの少ない手間で、PC向けWebサイトをスマートフォンに対応させることができた。
  • この方法は、よほどデザインの凝ったサイトでなければかなり有効である。
  • かといって、レイアウトの組み換えはサイト単位に個別に実装する必要があり、スクリプトを汎用化させるのは難しい。
  • スマートフォン対応していない各サイトも同様のスクリプトを組み込んで欲しい。
 
research/1332291593.txt · 最終更新: 2012/04/16 06:09 by Kazuyuki Matsuda
特に明示されていない限り、本サイトの内容は次のライセンスに従います:Copyright(C) 2011 Shorindo, Inc. All Rights Reserved
Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki