2015年10月9日金曜日

NavigationBar非表示の左エッジスワイプ


UINavigationControllerは左端からのスワイプで前の画面に戻るが、NavigationBar非表示だと戻らなくなる。画面を覆っているViewがTouchイベントを吸収してしまうからのようです。

その場合は、そのViewにUIScreenEdgePanGestureRecognizerをセットして、自分で前の画面に戻してやればいい。
  • UIViewControllerにUIScreenEdgePanGestureRecognizerをドラッグ&ドロップで追加。
  • ViewからControl+ドラッグでUIScreenEdgePanGestureRecognizerをコネクト。
  • gestureRecognizersに追加する。

  • UIScreenEdgePanGestureRecognizerのプロパティーでLeftをチェック。

  • ViewのプロパティーでUser Interaction Enabledをチェック。

ソースコードの例
最低限の追加はUIScreenEdgePanGestureRecognizerのアクションを受けとるメソッド。
前の画面に戻るので、UIViewControllerに実装するのがよいでしょう。
- (IBAction)handleLeftEdgeGesture:(id)gesture
{
    [self.navigationController popViewControllerAnimated:YES];
}

これをIBでUIScreenEdgePanGestureRecognizerのアクションにセットする。

delegateメソッドは必須ではないが、必要な場合は<UIGestureRecognizerDelegate>をヘッダに追加し(これもなくても動いてしまうが)、適宜delegateメソッドを実装。

ステイタスバーを隠す場合は、UIViewControllerに次のメソッドを追加。
- (BOOL)prefersStatusBarHidden
{
    return YES;
}

NavigationBarを隠すにはUIViewControllerのviewDidLoadなどに次のメソッドを追加。
    [self.navigationController setNavigationBarHidden:YES];

これを行ったあとは前の画面に戻ったときもNavigationBarが隠れたままになるので、そちらでsetNavigationBarHidden:NOに戻す。

ちなみに、UINavigationControllerの左端からのスワイプ動作が都合の悪い時は、次のメソッドで無効にできる。

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

2015年10月1日木曜日

SDメモリにコピーしたXcodeが起動できない

MacBook Air 128Gの残り容量が少なくなり、Xcodeをアップデートするときに以前のバージョンを残しておくスペースがない。そこで、SDメモリにコピーしておいて、そこからも起動可能にしておこうと、試してみました。

すると、こんなエラーが発生。
Xcode is running from a volume that does not support ownership. Please move Xcode to one that does.

ちなみにUSBメモリではこのエラーは発生しませんでした。

Finderで当該SDを選択 => 情報を見る
ダイアログの下の方の鍵を外し、「このボリューム上の所有権を無視」のチェックを外す。
こんなコマンドでも良いようです。
sudo diskutil enableOwnership /dev/disk0s2

再度起動を試すと別のエラーが発生。
/Volumes/...../Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/dyld_sim is not owned by root.

ターミナルで次のコマンドを実行。

ls -la /Volumes/

目的のXcodeのフォルダを探し、次のコマンドを実行。
sudo chown -R -v root xcpde-app-path

これで起動できるようになりました。ヘビーな使い方は無理でしょうが、以前はどうだったかな?と思ったときには役立つでしょう。

参考:

2015年9月29日火曜日

AppSotreからのXocdeアップデートが失敗する

追記:AppSotreからのXocde 8へのアップデートが失敗する

アップデートの度に容量不足が発生するが、8へのアップデートでは20Gほど空いていないとアップデートできない。

ディスク使用量をチェックしたら~/Library/Developer/Xcode/iOS DeviceSupportに古いバージョンのものが残っていおり、大量にスペースを消費していた。
これらはシンボルが入っているだけで、必要であればXcodeがデバイスからダウンロードするという書き込みがあったので、古いものを削除した。


AppSotreからのXocde 7へのアップデートが失敗する

アップデート失敗後に「”購入済みページ”からダウンロードし直してください。」とメッセージが表示される。
そのとおりにしてみても、ダウンロード完了直前に同じことが再発する。

結果的にディスクの残容量不足でした。9G程度必要なようです。
このエラーが発生したときに、どうも途中までダウンロードしたものが残っているようで、残容量を更に圧迫していたようです。

途中でAppStoreにDebugメニューを表示し、Reset Applicationを行っています。もしかしたら、これも効果があったのかもしれません。

Debugメニューを表示するにはタミーナルから次のコマンドを実行します。
defaults write com.apple.appstore ShowDebugMenu -bool true

追加:iMovieアップデートでも同様のことが発生。なんどかアップデートを試しても失敗。AppStoreのReset Application後に成功した。

参考:
[Sy] Xcodeアップデート時に「”購入済みページ”からダウンロードし直してください。」とエラーになる場合の対処方法
Xcode failed to download. Use the Purchases page to try again


おまけ:"アプリケーション"フォルダのサイズがえらく大きくなっていたので調べたところ、Chromeが古いバージョンが残っていた。古いバージョンを消しても今のところ問題なし。

- /Applications/Google Chromeを右クリック
- "パッケージの内容を表示"を選択
- Contents/versionsへ移動
- 古いバージョンを削除


ビルド時のsdkのディレクトリが見つからないエラー
  • Project選択
  • Build Settings
  • Framework Search Paths検索
  • 該当する設定を削除
私の場合はTest Projectにこのパスが含まれていました。

Directory not found for option '-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/Developer/Library/Frameworks'

参考:
Getting Framework related warning in Xcode 7.0

Blockの引数エラー

エラーメッセージ
/Users/my/Desktop/Proj/WebTest/WebTest/NLWebView.m:103:35: Conflicting parameter types in implementation of 'webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:': 'void (^ _Nonnull __strong)(void)' vs 'void (^__strong _Nonnull)()'

後ろのほうの型宣言部分の
void (^__strong _Nonnull)()'
は、実際には
void (^)()'
となっています。

必要な修正は
void (^)(void)'
とすることですが、メッセージ通りに
void (^ _Nonnull __strong)(void)
としておいた方が無難かもしれません。

iOS Simulatorエラー

WKWebViewのテストプログラムをiOS Simulatorで何度か起動していたら、次のエラーが発生して、再起動できなくなった。

The operation couldn’t be completed. FBSOpenApplicationErrorDomain Code=3

iOSシミュレータの次のメニューからコンテンツをリセットする。
[iOS Simulator]-[Rest Contents and Settings...]-[Reset]

参考:
Unable to run app in Simulator エラーの対応方法


WKWebViewのhtmlロードでエラー発生

こんなエラーが発生。
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.

Xcodeをアップデートしたらメッセージが変わった?
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

これまでのコーディングでレスポンスが戻らず無反応状態になっている場合は
- (void)webView:didFailProvisionalNavigation:withError:
を実装し、errorをチェックしてみる。

iOS9からApp Transport Security(ATS)が導入され、デフォルトではHTTPでの通信ができなくなっている。
App Transport Security policyはplistで変更することが可能で、従来の動作に戻すには次の設定を追加する。

<key>NSAppTransportSecurity</key>  
     <dict>  
          <key>NSAllowsArbitraryLoads</key><true/>  
     </dict>  

不特定なサイトにアクセスする必要がある場合は、この設定にするしかなさそう。
特定のサイトに限定できる場合はドメイン指定でアクセス可能にする。

参考:
これに関連したGoogleのページもありました。

The launch image set "LaunchImage" has 5 unassigned children.

どうやら、以前のProjectでlaunch image setを2つ作ってしまったようです。

Project選択
 => Target
 => App Icons and Launch Images
 => Launch Image Sources

のプルダウンリストを見ると"Launchimage"が選択状態になっているが、もうひとつLaunchimage−1がある。
こちらに変更すると以前の状態にもどった。

もともとのLaunchimageが適切に設定されておらず、別のLaunchimageを追加し、そちらを使っていた。Xcode7は元の名前の方を選択状態にしたため、ということのようです。


画面がフルスクリーンにならない

前項の続き。
Imageが適切に設定できていないと、Default@2x(640x960)のサイズが適用され、そのイメージサイズがWindowサイズに適用されているようだ。そのため、画面がフルスクリーンにならない。

iOS8以降のみのサポートであればLaunch ImageにStoryboadまたはxibを使うことができるが、iOS7以前のサポートが必要な場合は従来のstatic imageを使う必要がある。
新しいImage Setを追加すると、iPhone6(Plus)のイメージも割り当てらるようになる。

Project選択
 => Target
 => App Icons and Launch Images
 => Launch Image Sources
で設定を表示する。

画面上部がiPhone Portrait iOS8,9 / iPhone Landscape iOS 8,9になっていない場合は 右クリック => New Image Set で追加。
Launch Imagesを再設定する。古いImage Setは削除可能。
App Icons and Launch Images => Launch Image Sourcesのプルダウンで、追加したImage Setを選択する。

各々のイメージフォルダには適切なサイズのImageを割り当てないと有効にならない。
その場合、こんなWarningが出る。
launchimage/Default-568h@2x-1.png is 640x1136 but should be 750x1334.

また、Xcodeから起動するデバイスに適用可能なImageがひとつもないと、次のエラーが発生する。
The launch image set named "LaunchImage-1" did not have any applicable content.

そのデバイスに対応するImageが未設定あるいは不適切でも、適用可能なものがあればそれが割り当てられる。

たとえばiPhone 6用だけにiPhone5のImageをセットすると上のエラーになる。その状態でiPhone 5用のImageもセットするとエラーは消え、iPhone5のサイズが適用される。

参考:
Launch Image not showing up in iOS application (using Images.xcassets)

iOS 6もサポートする場合はこんなエラーもでる。

iPhone Retina (4-inch) launch image for iOS 6.x and prior is required when targeting releases prior to 7.0.

この場合は、こちらをご覧ください。

marble seijin の開発日記 まだまだある、xcode5での変更点


2015年9月26日土曜日

JQueryMobile listview サンプル 言語切り替え付き

jQuery Mobileのlistviewを使ったサンプルです。


JQueryMobile listview サンプル に言語切り替えを加えたものです。
デモサイト

言語切り替えなしバージョンと基本的に同じですが、表示文字列を"jp"、"en"のペアで設定し、言語選択に応じて表示を切り替えます。
言語選択はメインメニュー画面のラジオボタンでJapanese、Englishを選択することができます。

HTML一式ダウンロードできます。

index.html のタグ構造

<body onload="init();">
          <div data-role="page" id="topPage">
            <div data-role="header">
                <h1 class="headerText" id="topPageHeaderText">ホームページ</h1>
            </div>
            <!-- メインメニューを埋め込むdiv -->
            <div id="topMenu"></div>
            <!-- 言語切り替えラジオボタン -->
            <div id="langDiv">
                <input type="radio" name="lang" id="radio_jp" onclick="setLang('jp');">
                    <label for="radio_jp" id="label_jp">Japanese</label>
                <input type="radio" name="lang" id="radio_en" onclick="setLang('en');">
                    <label for="radio_en" id="label_en">English</label>
            </div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
        
        <!-- メインメニューのページ1をタップしときに遷移するpage。サブメニューを表示する。 -->
        <div data-role="page" id="subMenuPage">
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">subMenuPage</h1>
            </div>
            <!-- サブメニューを埋め込むdiv -->
            <div id="subMenu"></div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>

        <!-- コンテンツ表示。メインメニューのページ3、およびサブメニューをタップしときに遷移しコンテンツを表示するpage。 -->
        <div data-role="page" id="contentPage" >
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">contentPage</h1>
            </div>
            <div data-role="main" class="ui-content" id="iframeDiv">
                <!-- コンテンツをロードするiframe -->
               <iframe id="iframe"></iframe>
            </div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
    </body>

言語設定javascipt

//言語設定。ブラウザの言語設定が"ja"なら日本語、さもなければ英語。
            //index.htmlにハッシュ値がある場合は"ja"なら日本語、さもなければ英語。
            //index.htmlアクセス時にハッシュ値で言語設定可能。ハッシュ値をLangにセットする。
            //index.html#jpまたはindex.html#enでアクセス。
            var Lang = (navigator.language.indexOf("ja") < 0 ? "en" : "jp");
            if (location.hash.length > 0) {
                Lang = (location.hash.substr(1) == "jp" ? "jp" : "en");
            }
var Lang = location.hash.length == 0 ? "jp" : location.hash.substr(1);

//文字列のjp、enペアデータ。メインメニューのヘッダ、Backボタンの表示名。
           var label = {
                    "topPageHeader": {"jp":"ホームページへようこそ", "en":"Welcome to My Homepage"},
                    "backBtnText": {"jp":"戻る", "en":"Back"}
                };
//言語切り替えラジオボタンタップ/クリックで呼ばれ、メインメニューを再設定する。
           function setLang(lang) {
                Lang = lang;
                setMenu('topMenu', 'topMenuList');
           }

index.htmlのonloadイベントハンドラ

//body.onloadで呼ばれる。iframeのheight設定。
            //ブラウザのreloadボタンクリック時はindex.htmlをリロードする。
            function init() {
                if (document.getElementById("topMenuList") == null || $('#topMenuList').length == 0) {
                    location = "index.html";
                } else {
                    var h = $(window).height() - 20;
                    $('#iframe').css("height", h + "px");
                }
                $('#radio_' + Lang).prop('checked', true);
                $('input[name=lang]').checkboxradio('refresh');
            }

メインメニューを生成するjavascript

            //メインメニューのデータ。
            //text: リスト表示名
            //page: 遷移先ページ ID
            //src: iframeにロードするhtml、またはサブメニューのjson変数名。
            var topMenuList = [
                    {"jp":"ページ1 - サブメニュー", "en":"Page1 - SubMenu", "page":"#subMenuPage", "src":"subMenuList1"},
                    {"jp":"ページ2 - サブメニュー", "en":"Page2 - SubMenu", "page":"#subMenuPage", "src":"subMenuList2"},
                    {"jp":"ページ3 - コンテンツ", "en":"Page1 - Contents", "page":"#contentPage", "src":"Contents/page3"}
                    ];

            //index.htmlロード時に呼ばれ、メインメニューを生成。
            $(document).on('pagebeforeshow', '#topPage', function(){
                if ($('#topMenuList').length == 0) {
                    setMenu('topMenu', 'topMenuList');
                }
            });

メインメニュータップ/クリックで呼ばれサブメニューを生成するjavascript

//サブメニューのデータ。項目はメインメニューと同じ。 var subMenuList1 = [ {"jp":"1. 項目11", "en":"1. Item11", "page":"#contentPage", "src":"Contents/item11"}, {"jp":"2. 項目12", "en":"2. Item12", "page":"#contentPage", "src":"Contents/item12"}, {"jp":"3. 項目13", "en":"3. Item13", "page":"#contentPage", "src":"Contents/item13"} ]; //サブメニューのデータ。項目はメインメニューと同じ。 var subMenuList2 = [ {"jp":"1. 項目21", "en":"1. Item21", "page":"#contentPage", "src":"Contents/item21"}, {"jp":"2. 項目22", "en":"2. Item22", "page":"#contentPage", "src":"Contents/item22"}, {"jp":"3. 項目23", "en":"3. Item23", "page":"#contentPage", "src":"Contents/item23"} ]; //サブメニュー生成。異なるサブメニューで同じDIV構成を強要するため、表示のたびにlistviewを作り直す。 function setMenu(menuDivId, menuDataName) { if (menuDivId == "topMenu") { $('#topPageHeaderText').text(label.topPageHeader[Lang]); } var menuListId = '#' + menuDivId + 'List'; var ul = $(menuListId); if (ul.length > 0) { ul.empty(); } else { ul = $('<ul>').attr({'id':menuDivId + 'List','data-role':'listview'}).appendTo('#' + menuDivId); } var menuItemList = eval(menuDataName); for(var i=0; i < menuItemList.length; i++) { var li = $('<li>').appendTo(menuListId); var item = menuItemList[i]; $('<a data-transition="slide" href="' + item.page + '">') .attr('onclick', 'linkClicked("' + item.page + '","' + item[Lang] + '","' + item.src + '")') .text(item[Lang]).appendTo(li); } $(menuListId).listview().listview('refresh'); }

メニュー項目タップ/クリックで呼ばれ画面遷移を行うjavascript

//画面遷移の履歴。 //iframeを使用するとBackボタンを二度クリック/タップしないと画面遷移しなくなるため、独自に履歴を管理する。 var pageArray = new Array(""); //listviewの項目がクリック/タップされたときに呼ばれる。 //遷移先のヘッダのタイトル設定。現在のページをpageArrayにpushする。 function linkClicked(page, title, src) { if (page == "#subMenuPage") { setMenu("subMenu", src); } else if (src.length > 0) { $('#iframe').attr('src', src + '_' + Lang + '.html'); } $(page).find('.headerText').text(title); //'data-back-btn-text'を変更しても要素が再作成されるわけではないので表示が変わらない。 //refreshする方法が見つからなかったのでボタンのaタグのtextを直接変更する。 //$(page).find('div[data-role="header"]').attr('data-back-btn-text', label.backBtnText[Lang]); $(page).find('a[role="button"]').text(label.backBtnText[Lang]);
                pageArray.push(findParentPage($(event.srcElement)));
            }
        
            //paretnElementを辿り、data-role="page"の要素を返す。見つからない場合はnullを返す。
            function findParentPage(elm) {
                if (elm == null || elm.attr('data-role') == 'page') return elm;
                return findParentPage(elm.parent());
            }

戻るボタンで画面遷移を行うjavascript

//Backボタンタップ/クリック。 //戻り先pageIdがあればchangePageをよびfalseを返す。さもなけばtrueを返し、frameworkに任せる。 function backClicked(elm) { var page = pageArray.pop(); if (page == null) return true; var pageId = page.attr('id'); if (pageId.length == 0) return true; $.mobile.changePage('#'+pageId, { transition: "slide", reverse: true } ); return false; }
参照
JQueryMobile listview サンプル
HTML5+JavaScriptで画面をスライドさせて切り替える方法

JQueryMobile listview サンプル

jQuery Mobileのlistviewを使ったサンプルです。

自前で作ったメニュー表示/画面スライドをJQueryMobileで作り直したものです。
デモサイト
日本語/英語の言語切り替え付きもあります。

メインメニュー/サブメニュー/コンテンツといった多階層の画面遷移を組み込んでいます。
メニューのlistviewをjsonデータからjavascriptで動的に生成し、コンテンツ表示部分は各項目で共有し、コンテンツはiframeに外部のhtmlを読み込む構成になっています。
これにより画面遷移に関わる部分のhtmlがシンプルになり、コンテンツのメンテナンスも容易になります。

iframeを使っているため、backボタンの動作に手を加えています。これが最善かどうかはわかりませんが、ともかくも期待通りに動作しています。

HTML一式ダウンロードできます。

index.html のタグ構造

<body onload="init();">
        <div data-role="page" id="topPage">
            <div data-role="header">
                <h1 class="headerText">ホームページ</h1>
            </div>
            <!-- メインメニューを埋め込むdiv -->
            <div id="topMenu"></div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
        
        <!-- メインメニューのページ1をタップしときに遷移するpage。サブメニューを表示する。 -->
        <div data-role="page" id="subMenuPage">
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">subMenuPage</h1>
            </div>
            <!-- サブメニューを埋め込むdiv -->
            <div id="subMenu"></div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>

        <!-- コンテンツ表示。メインメニューのページ3、およびサブメニューをタップしときに遷移しコンテンツを表示するpage。 -->
        <div data-role="page" id="contentPage" >
            <div data-role="header" data-add-back-btn="true" data-back-btn-text="戻る" onclick="return backClicked(this);">
                <h1 class="headerText">contentPage</h1>
            </div>
            <div data-role="main" class="ui-content" id="iframeDiv">
                <!-- コンテンツをロードするiframe -->
               <iframe id="iframe"></iframe>
            </div>
            <div data-role="footer" class="footer">
                <h6 class="footerText">Footer Text</h6>
            </div>
        </div>
    </body>

index.htmlのonloadイベントハンドラ
//body.onloadで呼ばれる。iframeのheight設定。
            //ブラウザのreloadボタンクリック時はindex.htmlをリロードする。
            function init() {
                if (document.getElementById("topMenuList") == null || $('#topMenuList').length == 0) {
                    location = "index.html";
                } else {
                    var h = $(window).height() - 20;
                    $('#iframe').css("height", h + "px");
                }
            }

メインメニューを生成するjavascript
//メインメニューのデータ。
            //text: リスト表示名
            //page: 遷移先ページ ID
            //src: iframeにロードするhtml、またはサブメニューのjson変数名。
            var topMenuList = [
                    {"text":"ページ1 - サブメニュー", "page":"#subMenuPage", "src":"subMenuList1"},
                    {"text":"ページ2 - サブメニュー", "page":"#subMenuPage", "src":"subMenuList2"},
                    {"text":"ページ3 - コンテンツ", "page":"#contentPage", "src":"Contents/page3.html"}
                    ];

            //index.htmlロード時に呼ばれ、メインメニューを生成。
            $(document).on('pagebeforeshow', '#topPage', function(){
                if ($('#topMenuList').length == 0) {
                    var ul = $('<ul>').attr({'id':'topMenuList','data-role':'listview'}).appendTo('#topMenu');
                    for(var i=0; i < topMenuList.length; i++) {
                    var li = $('<li>').appendTo('#topMenuList');
                    var item = topMenuList[i];
                    $('<a data-transition="slide" href="' + item.page + '">')
                        .attr('onclick', 'linkClicked("' + item.page + '","' + item.text + '","' + item.src + '")')
                        .text(item.text).appendTo(li);
                    }
                    $('#topMenuList').listview().listview('refresh');
                }
            });


メインメニュータップ/クリックで呼ばれサブメニューを生成するjavascript
//サブメニューのデータ。項目はメインメニューと同じ。
            var subMenuList1 = [
                   {"text":"1. 項目11", "page":"#contentPage", "src":"Contents/item11.html"},
                   {"text":"2. 項目12", "page":"#contentPage", "src":"Contents/item12.html"},
                   {"text":"3. 項目13", "page":"#contentPage", "src":"Contents/item13.html"}
                   ];

            //サブメニューのデータ。項目はメインメニューと同じ。
            var subMenuList2 = [
                   {"text":"1. 項目21", "page":"#contentPage", "src":"Contents/item21.html"},
                   {"text":"2. 項目22", "page":"#contentPage", "src":"Contents/item22.html"},
                   {"text":"3. 項目23", "page":"#contentPage", "src":"Contents/item23.html"}
                   ];

            //サブメニュー生成。異なるサブメニューで同じDIV構成を共用するため、表示のたびにlistviewを作り直す。
            function setMenu(name) {
                var ul = $('#subMenuList');
                if (ul.length > 0) {
                    ul.empty();
                } else {
                    ul = $('<ul>').attr({'id':'subMenuList','data-role':'listview'}).appendTo('#subMenu');
                }
                var menuList = eval(name);
                for(var i=0; i < menuList.length; i++) {
                    var li = $('<li>').appendTo('#subMenuList');
                    var item = menuList[i];
                    $('<a data-transition="slide" href="' + item.page + '">')
                    .attr('onclick', 'linkClicked("' + item.page + '","' + item.text + '","' + item.src  + '")')
                    .text(item.text).appendTo(li);
                }
                $('#subMenuList').listview().listview('refresh');
            }

メニュー項目タップ/クリックで呼ばれ画面遷移を行うjavascript
            //サブメニューのデータ。項目はメインメニューと同じ。
            //画面遷移の履歴。
            //iframeを使用するとBackボタンを二度クリック/タップしないと画面遷移しなくなるため、独自に履歴を管理する。
            var pageArray = new Array("");
            
            //listviewの項目がクリック/タップされたときに呼ばれる。
            //遷移先のヘッダのタイトル設定。現在のページをpageArrayにpushする。
            function linkClicked(page, title, src, backTo) {
                if (page == "#subMenuPage") {
                    setMenu(src);
                } else if (src.length > 0) {
                    $('#iframe').attr('src', src);
                }
                $(page).find('.headerText').text(title);
                pageArray.push(findParentPage($(event.srcElement)));
            }
        
            //paretnElementを辿り、data-role="page"の要素を返す。見つからない場合はnullを返す。
            function findParentPage(elm) {
                if (elm == null || elm.attr('data-role') == 'page') return elm;
                return findParentPage(elm.parent());
            }

戻るボタンで画面遷移を行うjavascript
            //Backボタンタップ/クリック。
            //戻り先pageIdがあればchangePageをよびfalseを返す。さもなけばtrueを返し、frameworkに任せる。
            function backClicked(elm) {
                var page = pageArray.pop();
                if (page == null) return true;
                var pageId = page.attr('id');
                if (pageId.length == 0) return true;

                $.mobile.changePage('#'+pageId, { transition: "slide", changeHash: true, reverse: true } );
                return false;
            }

参照
JQueryMobile listview サンプル 言語切り替え付き
HTML5+JavaScriptで画面をスライドさせて切り替える方法

2015年6月9日火曜日

Cardboard実験サイト2.3 / HTML (div構成 / Photo1 - ステレオ写真)

概要
あらかじめ作ったステレオ写真を表示します。
これはステレオ写真をハコスコで見られるようにしようと作ったもので、画面分割は行っていません。

画面レイアウト設定
index.htmlで画面レイアウトのためのタグ構造を作り、画面サイズに応じてcardboard.jsでレイアウト設定を行います

タグ構造 
bodyタグ内は次のようにdivを数階層積み重ねたものです。
javascript、cssはdivのidを使用するので、idはこのとおりにしておきます。

  <body onload="init();" ontouchmove="blockMove(event)" >
    <div id="container">
      <div id="outerBox" class="photo" onclick="changeImage()">
      </div>
    </div>
  </body>

javascript
index.html


      function init() {
          //divの配置
          Config.setContainerSize();
          //最初の画像表示
          changeImage();
      }

      //画像名配列
      var arr = new Array("image1.jpg", ""image2.png", ...);
      var ix = -1;

      //clickまたはtouchstartイベントで呼ばれ、画像を切り替える。
      function changeImage() {
          //右画面で次へ進み、左が画面で前に戻る。
          if (event.pageX == null) 
              ix = 0;
          else
              ix += (event.pageX > (Config.winW / 2) ? 1 : -1);
          if (ix >= arr.length)
              ix = 0;
          else if (ix < 0)
              ix = arr.length - 1;
          //divのbackgroundImageにセット。
          with(document.getElementById("outerBox").style) {
              backgroundImage = "url(Images/" + arr[ix] + ")";
              backgroundSize = "100%";
          }
      }

      //スワイプされても画面を動かさないようにする。
      function blockMove(event) {
        if (Config.isIOS) event.preventDefault();
      }

Config.setContainerSize();
  デバイス横位置の大きさに合わせて画面をリサイズ、イベント設定を行なう。
  また、デフォルトで次のclickまたはtouchstartイベントハンドラをセットする。
  左画面ダブルクリック 上の階層に戻る
  右画面ダブルクリック location.reload();
  シングルクリック   Config.onclickにfunctionが設定されていればそれを実行、
                                   さもなければなにもしない。

 イベントハンドラが不要な場合はsetContainerSizeを呼ぶ前に次の行を追加。
 Config.addClickEvent = false;

 Photo1ではdivにonclickイベントハンドラを設定することで上記設定を置き換えている。

Cardboard実験サイト 2.2 / HTML (iframe構成)

概要
iPhoneを横位置にしたときに画面を二分割し、視差のあるイメージを表示します。
iframeを使用しています。

二画面が各々独立したwindowとなり、translateの結果divからはみ出した部分はクリッピングされます。

一方、独立したwindowのためタイマーイベントのループも別々タイミングで行われるため、単純にループ内で定数を足すような方法では、時間経過とともに同期のズレが発生します。Dateの時刻を利用することで左右のズレを避けることができます。

javascriptはcardboard.jsとcardboard−iframe.jsをインポートします。cardboard−iframe.jsはcardboard.jsへのfunctionを追加と一部置き換えを行います。これらは
画面レイアウト設定と、左右の画面に対する処理を一括して行う機能を提供し、また
タイマーイベントで描画関数を呼び出します。

画面レイアウト設定
index.htmlで画面レイアウトのためのタグ構造を作り、画面サイズに応じてcardboard−iframe.jsでレイアウト設定を行います

タグ構造 (iframe使用)
bodyタグ内は次のようにdivを数階層積み重ねたものです。
javascript、cssはdivのidを使用するので、idはこのとおりにしておきます。

index.html
    <body onload="init();">
        <div id="container">
            <div id="outerBox">
                <div id="boxL">
                    <iframe id="ifL" name="ifL" src="Part.html"></iframe>
                </div>
                <div id="boxR">
                    <iframe id="ifR" name="ifR" src="Part.html"></iframe>
                </div>
            </div>
        </div>
    </body>

Part.html (iframeのsrc)
    <body onload="init();">
        <div id="container">
            <div id="outerBox">
                <div id="canvas"></div>
            </div>
        </div>
    </body>


js/cssインポート
        <link rel="stylesheet" type="text/css" href="../../css/cardboard.css" />
        <script src="../../js/cardboard.js"></script>
        <script src="../../js/cardboard-iframe.js"></script>
        <script src="../../js/gyro.js"></script>

cardboard.jsの後にcardboard-iframe.jsを追加。

javascript
index.html
    //body.onloadで呼び出し、初期化を実行。
    var gyro = null;
    var startTime = new Date().getTime();
    function init() {
        //cardboad.jsのGyroオブジェクト生成。
         gyro = new Gyro();
         //div, iframeの配置。
         Config.setFrames();
    }

Part.html (ifarmeのsrc)

             //onloadイベント
             function init() {
             //iframe内のdiv配置。
             Config.setFrameCanvas(window.name);
             //iframeのサイズがjavascriptで決定できず表示位置がズレるので位置を調整。
             Config.body.scrollLeft = 0;
             //視点設定(イタリックは実際の値で置き換え)
             Config.setParspective(Config.side=="L"?-4:4, parsY, parsZ);
             //描画オブジェクト追加。
             var obj = Canvas.add(Part);
             //パラメータ設定。paramはkey/valueのArray。JSONなら{key1:value1, key2:value2, ...}。 
             obj.setParam(param);
             //タイマーイベントスタート。
             Canvas.start();
        }

Cube2.js
    //cardboard-iframe.jsのCanvas.add(func)実行中に呼ばれる。
    Cube2 = function(parentDiv, side) {
        //HTMLに配置したCubeのdivをコピーする。
        this.div = document.getElementById("divFaces").cloneNode(true);
        this.div.className = "o3d";
        this.div.style.display = "block";
        //parentDivに追加。
        parentDiv.appendChild(this.div);
    }

    //cube.htmlのaddCube(param)から呼ばれる。
    Cube2.prototype.setParam = function(param) {
        this.ty = -70;
        this.rx = this.ry = this.rz = this.tx = this.ty = this.tz = 0;
        this.dx = param["dx"] == null ? 0 : param["dx"];
        this.dy = param["dy"] == null ? 0 : param["dy"];
        this.r = param["r"] == null ? 0 : param["r"];
    }

    //Canvasのタイマーイベントで呼ばれる。
    Cube2.prototype.move = function() {
        //index.htmlのonloadが実行されるまではgyroがnull。
        if (parent.gyro == null) return;
        //Dateの時刻を利用してアニメ用の変数を設定。
        var f = new Date().getTime() / 50;
        this.rx = f * 0.8;
        this.ry = f * 0.9;
        this.rz = f;
        //gyroから値を取得。
        var gx = (parent.gyro.ox + this.dx) * Math.PI / 180;
        var gy = (Math.abs(parent.gyro.oy) + 90) * Math.PI / 180;
        //オブジェクトの座標変換。
        var tx = this.r * Math.sin(gx) + Config.centerX;;
        var tz = this.r * Math.cos(gx) * Math.cos(gy);
        var ty = tz * Math.sin(gy);
        var tt = ty;
        ty -= this.r * Math.sin(this.dy * Math.PI / 180)
        this.div.style.webkitTransform = "translateX(" + tx + "px) "
            + "translateY(" + ty + "px) "
            + "translateZ(" + tz + "px) "
            + "rotateX("+ this.rx + "deg) "
            + "rotateY("+ this.ry + "deg) ";
    }

2015年6月7日日曜日

Cardboard実験サイト 4.3 Gyro / Cube2 (iframe構成)

概要
Gyroscopeのデータを取得して、デバイスの移動に応じて画面を動かすサンプルです。

このサンプルでは左右の画面をiframeで構成し、視差を付けて3D画像を描画します。

iframeを使用することで、左右の画面各々でクリッピングが行われるようになります。
一方、各々が独立したwindowのためタイマイベントも別々にループし、アニメ描画時に単純にループ毎に定数を足すような演算では時間が経つにつれ描画にズレが生じます。
それを避けるため、ここでは親window(index.html)から時間経過(msec())を取得し、それに基づいてアニメ描画を行っています。

HTML/Cube1のdivを組み合わせて作る立方体を複数配置し、Gyroと連動して視点を移動させます。感覚的には頭の動きに合わせて自分の周りを眺めるような感じです。座標の中心から視点を離す必要があるため、空間の中心からは少しズレがでます。

iOS/Androidで基本的に同じ使い方ができますが、Androidでのテストは行っていません。動作不良の場合は若干の修正で対応できるはずです。


画面レイアウト設定

タグ構造 
<body onload="init();" ontouchmove="blockMove(event)">
    <!-- HTMLL/Cube1と同じ -->
    <div id="container">
      <div id="outerBox">
        <div id="boxR" onclick="restart()">
          <div id="canvasR"></div>
        </div>
        <div id="boxL" onclick="back()">
          <div id="canvasL"></div>
        </div>
      </div>
    </div>
    <!-- Cubeを組み立てる6面のdiv -->
    <div id="divFaces" style="display:none">
        <div id="cube">
            <div class="face" id="f1">One</div>
            <div class="face" id="f2">Two</div>
            <div class="face" id="f3">Three</div>
            <div class="face" id="f4">Four</div>
            <div class="face" id="f5">Five</div>
            <div class="face" id="f6">Six</div>
        </div>
    </div>
</body>

is/cssインポート
        <script src="../../js/cardboard.js"></script>
        <script src="../../js/gyro.js"></script>
        <script src="../../HTML/js/Cube.js"></script>
        <script src="Cube1.js"></script>
        <link rel="stylesheet" type="text/css" href="../../css/cardboard.css" />
        <link rel="stylesheet" type="text/css" href="Cube1.css" />

javascript
index.html
        var gyro = null;
        function init() {
              //Gyroオブジェクト生成
              gyro = new Gyro();
              //デバイス横位置の大きさに合わせて左右の画面をリサイズ、イベント設定を行う。
              Config.setBoxSizes();
              //視点の設定
              Config.setParspective(4, 0, 500);
             //Cubeオブジェクト追加
              var c1 = Canvas.add(Cube1);
              //タイマーイベントスタート
              Canvas.start(50);
        }
        //スワイプで画面が動かないようにする。
        function blockMove(event) {
              if (Config.isIOS) event.preventDefault();
        }

Cube1.js
    //コンストラクタ
    Cube1 = function(parentDiv, side) {
        if (parentDiv == null) return;
        this.div = document.getElementById("divFaces").cloneNode(true);
        if (this.div == null) throw 'Missing <div id="divFaces">';
        this.div.className = "o3d";
        this.div.style.display = "block";
        parentDiv.appendChild(this.div);
    }

   //パラメータ設定
    Cube1.prototype.setParam = function(param) {
        this.ty = -70;
        this.rx = this.ry = this.rz = this.tx = this.ty = this.tz = 0;
        this.dx = param["dx"] == null ? 0 : param["dx"];
        this.dy = param["dy"] == null ? 0 : param["dy"];
        this.r = param["r"] == null ? 0 : param["r"];
        this.rand = param["rand"] == null ? 0 : param["rand"];
    }

    //Canvasのタイマーイベントから呼ばれる。アニメ描画。
    Cube1.prototype.move = function() {
        if (parent.gyro == null) return;
        var f = parent.msec() / 10000;
        this.rx += f * 0.8;   //+ this.rand;
        this.ry += f * 0.9; // + this.rand;
        this.rz += f;   // + this.rand;
        
        var gx = (parent.gyro.ox + this.dx) * Math.PI / 180;
        var gy = (Math.abs(parent.gyro.oy) + 90) * Math.PI / 180;
        
        var tx = this.r * Math.sin(gx) + Config.centerX;;
        var tz = this.r * Math.cos(gx) * Math.cos(gy);
        var ty = tz * Math.sin(gy);
        var tt = ty;
        ty -= this.r * Math.sin(this.dy * Math.PI / 180)
        this.div.style.webkitTransform = "translateX(" + tx + "px) "
            + "translateY(" + ty + "px) "
            + "translateZ(" + tz + "px) "
            + "rotateX("+ this.rx + "deg) "
            + "rotateY("+ this.ry + "deg) ";
    }

2015年6月6日土曜日

Cardboard実験サイト 4.2 Gyro / Cube1 (div構成)

概要
Gyroscopeのデータを取得して、デバイスの移動に応じて画面を動かすサンプルです。

このサンプルではdivで構成した左右二つの画面に視差を付けて3D画像を描画します。


HTML/Cube1のdivを組み合わせて作る立方体を中央に置き、Gyroと連動してCubeを回転させます。感覚的には頭の動きに合わせて視点が動くような感じです。


iOS/Androidで基本的に同じ使い方ができますが、Androidでのテストは行っていません。動作不良の場合は若干の修正で対応できるはずです。


画面レイアウト設定

タグ構造 
<body onload="init();" ontouchmove="blockMove(event)">
    <!-- HTMLL/Cube1と同じ -->
    <div id="container">
      <div id="outerBox">
        <div id="boxR" onclick="restart()">
          <div id="canvasR"></div>
        </div>
        <div id="boxL" onclick="back()">
          <div id="canvasL"></div>
        </div>
      </div>
    </div>
    <!-- Cubeを組み立てる6面のdiv -->
    <div id="divFaces" style="display:none">
        <div id="cube">
            <div class="face" id="f1">One</div>
            <div class="face" id="f2">Two</div>
            <div class="face" id="f3">Three</div>
            <div class="face" id="f4">Four</div>
            <div class="face" id="f5">Five</div>
            <div class="face" id="f6">Six</div>
        </div>
    </div>
</body>


index.htmlのjavascript
js/cssインポート
        <script src="../../js/cardboard.js"></script>
        <script src="../../js/gyro.js"></script>
        <script src="../../HTML/js/Cube.js"></script>
        <script src="Cube1.js"></script>
        <link rel="stylesheet" type="text/css" href="../../css/cardboard.css" />
        <link rel="stylesheet" type="text/css" href="Cube1.css" />

javascript
index.html
        var gyro = null;
        function init() {
              //Gyroオブジェクト生成
              gyro = new Gyro();
              //デバイス横位置の大きさに合わせて左右の画面をリサイズ、イベント設定を行う。
              Config.setBoxSizes();
              //視点の設定
              Config.setParspective(4, 0, 500);
             //Cubeオブジェクト追加
              var c1 = Canvas.add(Cube1);
              //タイマーイベントスタート
              Canvas.start(50);
        }
        //スワイプで画面が動かないようにする。
        function blockMove(event) {
              if (Config.isIOS) event.preventDefault();
        }

Cube1.js
//Cubeのコンストラクタを継承
Cube1 = function(parentDiv, side, div) {
            Cube.apply(this, arguments);
        }

        //Cubeのプロトタイプを継承
        Cube1.prototype = new Cube();

        //Canvasのタイマイベントから呼ばれる。アニメ描画。
        Cube1.prototype.move = function() {
            var ry = gyro.ox*2;
            var rx = gyro.oy*2;
            this.div.style.webkitTransform = "translateX(100px) "
                + "translateY(100px) "
                + "rotateX("+ rx + "deg) "
                + "rotateY("+ ry + "deg) ";
        }

2015年5月12日火曜日

Cardboard実験サイト 6 / gyro.jsに含まれる関数、変数


デモサイト

ダウンロード

rotationRate(移動量)
rx = event.rotationRate.beta
ry = event.rotationRate.gamma
rz = event.rotationRate.alpha

acceleration(移動速度)
ax = event.acceleration.x
ay = event.acceleration.y
az = event.acceleration.z

orientation(初期状態に対する向き)
ox = event.alpha
oy = event.gamma

oz = event.beta

orientation(デバイスの向き)
"PU” portrait upside up
"PD” portrait upside down
"LL" landscape left
"LR" landscape right

例:
//Gyroオブジェクト生成
var gyro = new Gyro();
//x方向の相対位置
var ox  = gyro.ox;
//x方向の移動速度
var ax  = gyro.ax;

//x方向の移動量
var rx  = gyro.rx;

gyro.jsソース


devicemotion、devicemotionイベントハンドラを設定し、イベントからGyroscopeの情報を取得して変数にセットする。
accelerationの値からデバイスの縦、横位置を推測して変数にセットする。

Gyro = function() {
    try {
        if (event == null
            || typeof window.DeviceMotionEvent == "undefined"
            || typeof window.DeviceOrientationEvent == "undefined") {
            throw("Gyro not available.");
        }
        this.rx = this.ry = this.rz = this.ax = this.ay = this.az = 0;
        this.orientation = "";
        var gyro = this;
        window.ondevicemotion = function(event) {
            gyro.rx = event.rotationRate.beta;
            gyro.ry = event.rotationRate.gamma;
            gyro.rz = event.rotationRate.alpha;
            gyro.ax = event.acceleration.x;
            gyro.ay = event.acceleration.y;
            gyro.az = event.acceleration.z;
            gyro.orientation = "PU";
            if (gyro.ax < -5) gyro.orientation = "LL";
            else if (gyro.ax > 5) gyro.orientation = "LR";
            else if (gyro.ay > 5) gyro.orientation = "PD";
        }
        window. ondevicemotion = function(event) {
            gyro.ox = event.alpha;
            gyro.oy = event.gamma;
            gyro.oz = event.beta;
        }
    } catch(e) { alert(e); }

Cardboard実験サイト 5 / cardboard.jsに含まれる関数、変数

デモサイト

ダウンロード

Config
画面レイアウト、イベントハンドラ設定を行う。
コーディングを容易にするため、よく使用するオブジェクトを変数にセットする。

関数

Config.setBoxSizes()
ContainerとouterBox、およびboxL、boxRのサイズを設定する。
3DでboxL、boxRがある場合に用いる。

Config.setContainerSize()
ContainerとouterBoxのサイズを設定する。
2DでboxL、boxRがない場合に用いる。

変数

Config.conatiner
Config.outerBox
Config.boxL
Config.boxR
Config.ifL
Config.ifR
divのidに対応する変数
document.getElementIdで取得したオブジェクト。HTMLに該当のidのdivがない場合はnull。

Config.isIOS
iOSまたはAndroidの場合にtrue。
もともとiOSに対応するために設けた変数だが、Androidでも動作するのでその場合もtureに設定するようにした。

Config.winW
Config.winH
画面サイズに関する変数
ただし、isIOS==falseの場合はiPhone6のサイズ(667x375)を設定。

Config.centerX
Config.centerY
boxL、boxRの中心の位置

Config.addClickEvent
イベント設定制御
デフォルト値はtrueで、outerBoxにtouchstartまたはclickイベントハンドラをセットする。
デフォルトではダブルタップ(またはクリック)時は左画面の場合は上の階層に戻り、右画面の場合はreloadを行う。
ただしiframeを使用する構成などでイベントが渡ってこない場合は実行されない。
イベントハンドラが不要な場合はsetBoxSizesまたはsetContainerSizeを呼ぶ前にこの変数の値をfalseにセットする。

Config.onclick
シングルタップ(またはクリック)時はデフォルトでは何も行わない。
この変数にfunctionをセットしておくと、それを実行する。

------------------------------------------------------------

Canvas
描画エリアにオブジェクト追加。
タイマーイベントでアニメ描画。

関数

Canvas.add(func)
描画を行うオブジェクト(function)を追加する。
func : Partsのサブクラス。
左右の画面の各々にnew func(..)でオブジェクトを生成し、それをひとつにまとめLRPairオブジェクトを生成して返す。

Canvas.remove(obj)
Canvasに登路されたオブジェクトを削除する。
objはCanvas.addが返したLRPairオブジェクト。

Canvas.clear()
Canvasに登録されたオブジェクトを全て削除する。

Canvas.start()
アニメーションを開始する。

変数

Canvas.interval
アニメーション実行間隔(msec)。直前の描画開始から少なくともこのミリ秒が経過してから次の描画を開始する。
描画にかかった時間がこれ以上の場合はすぐに次の描画を開始する。
デフォルト値=50msec。

------------------------------------------------------------


LRPair
 左右二つの描画エリアをまとめて扱う。
 Canvas.addで追加されたfuncのオブジェクトを左右各々生成し、保持する。

setParam(param)
 保持している左右のオブジェクトの各々にパラメータをセットする。
 param: key-valueペアの配列。{key : value, ... }
 パラメータの扱いはPartsオブジェクトに依存。

move() 
 保持している左右のオブジェクトの各々のmove関数を呼ぶ。

プロパティー

innerText
 保持している左右のオブジェクトの各々が配置されているdivのinnerTextの値の設定。
 取得の場合は左側のdivのinnerText(通常は左右同じ値)。

------------------------------------------------------------

Parts
 Canvas.addの引数につかうクラス。実際の描画はサブクラスに実装する。

関数

setParam(param)
パラメータ設定。
param: Paramオブジェクト

applyParam()
setParamで設定されたパラメータをオブジェクトに反映する。
実際のロジックはサブクラスで実装する。

move()
タイマーイベントで呼ばれ、描画を実行する。
実際の描画はサブクラスに実装する。

------------------------------------------------------------

Param
 key−value辞書

Param.val(key)
 オブジェクトが保持するkey−value辞書からkeyに対応するvalueを返す。

------------------------------------------------------------

Objectへのプロパティー追加

param
 オブジェクトのdictの値の設定、取得。

------------------------------------------------------------

その他

画面がスワイプで動かないようにするため、touchmoveイベントをキャンセルする。

<body ontouchmove="blockMove(event)">

function blockMove(event) {
if (Config.isIOS) event.preventDefault();
}


Cardboard実験サイト 4.1 Gyro / Panorama

概要

Gyroscopeのデータを取得して、デバイスの移動に応じて画面を動かすサンプルです。
360°のパノラマ写真をGyro連動で回転させます。

iOS/Androidで基本的に同じ使い方ができますが、Androidでのテストは行っていません。動作不良の場合は若干の修正で対応できるはずです。

画面レイアウト設定
タグ構造 
HTML/Photo1とほぼ同じですが、styleを変更するためにclassを設定しています。
msgDivは使い方、写真タイトルを表示するエリアです。
    <body onload="init();">
        <div id="container" class="picFrame">
            <div id="outerBox" class="pic">
                <div id="msgDiv"></div>
            </div>
        </div>
    </body>

index.htmlのjavascript
js/cssインポート
        <script src="../../js/cardboard.js"></script>
        <script src="../../js/gyro.js"></script>
        <link rel="stylesheet" type="text/css" href="../../css/cardboard.css" />
        <link rel="stylesheet" type="text/css" href="Panorama.css" />

javascript
        var gyro = null;            
        function init() {
             //デバイス横位置の大きさに合わせて左右の画面をリサイズ、イベント設定を行う。
             Config.setContainerSize();
             //イベントハンドラ設定
             Config.onclick = changeImage;
             //最初の画像を表示
             changeImage();
             //Gyroオブジェクト生成
             gyro = new Gyro();
             //タイマーイベントスタート
             setTimeout(move, 33);
        }

        var posX = 0;

        function move() {
            //絶えず値が変動するので、細かすぎる動きは無視する。
            //gyroからデバイスの位置を取得
         //gyro.ox:  device orientation alpha 
            if (Math.abs(posX - gyro.ox) > 0.1) {
                posX = gyro.ox + offsetX;
                var x = (imgWidth * posX / 360).toFixed(1);
                Config.outerBox.style.backgroundPosition = x + "px";
            }
            setTimeout(move, 33);
        }

        //ダブルタップで画面チェンジ。
function changeImage(event) { with(Config.outerBox.style) { backgroundImage = "url("+ imgUrl +")"; backgroundOrigin = "0px 0px"; } //画像サイズ取得のためにimgに画像をセット。 var img = document.createElement("img"); img.addEventListener("load", imgLoaded); } var imgWidth = 0; var offsetX = 0; //img.onloadイベントハンドラ。imgから画像サイズを取得、初期表示位置(offsetX)をセット。 function imgLoaded(event) {
                var srcElm = event.srcElement;
                imgWidth = Math.round(srcElm.width * (document.body.clientHeight / srcElm.height));
                offsetX = (event.srcElement.width - Config.outerBox.clientWidth) / 2;
                event.srcElement.removeEventListener("load");
        }