最近家で言い放った「オレはアレクサじゃないッ!」が気に入って口癖のように使う毎日。
※アレクサ
前回の記事で制作したパノラマビューア。形になったものの、まだまだ完成度は低い。ローディングまわりの挙動をしっかりと作り込む。
今回作ったもの
<div class="canvas_box"> <canvas id="canvas_main"></canvas> <div class="canvas_loader" id="canvas_loader"></div> </div>
まずhtml側にローディング要素を追加する。ローディングのCSSはこちらから拝借、便利な世の中である。テストするとローディング要素がマウス/タップイベントを奪ってしまうのでpointer-events:none;を追加しておく。
前回は<canvas>まわりの要素もID名で管理していたが、同ページに複数のビューア設置する機会もあるかもしれないので共通要素はCLASS名での管理に変更。
デモページの方はローディングのテストを繰り返すため画像をキャッシュしないようファイル名に乱数を付与。ついでに社長が「マウス/タップ操作の方向をYoutubeに合わせてほしい」とかぼやいてたので操作時のスピードに*-1してやる。
ファイルサイズの小さい画像を先に表示させる
メインのパノラマ画像(大)に加え、ファイルサイズの小さい画像(小)も用意する。
今回の目標をまとめると
- <canvas>にローディング要素を重ねる
- パノラマ画像(小)のロードが完了したタイミングで<canvas>に描画、ローディング要素はまだ消さない。
- パノラマ画像(大)のロード完了でローディングとパノラマ画像(小)のオブジェクトを非表示に。
JS側ではローダー用配列をふたつ用意、画像(小)のプリロード用と画像(大)のメイン用。今回に限ると単純な変数でもOKなのだが、複数の要素があっても対応できるよう連想配列を使用。
//loader var loader={}; var loader_pre={};
各要素についてローディング開始前にローダー配列にfalseを登録していく。Loader関数の完了イベントでtrueを上書きする。
↓プリロードオブジェクト
if(prm.src_pre!=""){ loader_pre[prm.src_pre]=false; //ローダー配列にfalseを登録 var gmPrebase=new THREE.SphereBufferGeometry(900,60,40); //メインよりはっきり小さく gmPrebase.scale(-1,1,1); var mtrPrebase; var texture_pre=new THREE.TextureLoader().load(prm.src_pre,function(){ loader_pre[prm.src_pre]=true; //読み込みが完了したタイミングでローダー配列にtrueを上書き }); mtrPrebase=new THREE.MeshBasicMaterial({ map : texture_pre }); texture_pre.minFilter=texture_pre.magFilter=THREE.LinearFilter; texture_pre.mapping=THREE.UVMapping; } var prebase=new THREE.Mesh(gmPrebase,mtrPrebase); prebase.rotation.y=(prm.rotationY*Math.PI/180)*-1; group.add(prebase);
↓メインオブジェクト
var gmBase=new THREE.SphereBufferGeometry(1000,60,40); gmBase.scale(-1,1,1); var mtrBase; if(prm.src==""){ mtrBase=new THREE.MeshNormalMaterial(); }else{ loader[prm.src]=false; //ローダー配列にfalseを登録 var texture=new THREE.TextureLoader().load(prm.src,function(){ loader[prm.src]=true; //読み込みが完了したタイミングでローダー配列にtrueを上書き checkLoading(); //メインのロードがすべて完了したかチェック }); mtrBase=new THREE.MeshBasicMaterial({ map : texture }); texture.minFilter=texture.magFilter=THREE.LinearFilter; texture.mapping=THREE.UVMapping; } var base=new THREE.Mesh(gmBase,mtrBase); base.rotation.y=(prm.rotationY*Math.PI/180)*-1; group.add(base);
オブジェクトを用意したらrunLoading()を動かす。プリロードの状況を監視し、画像(小)のロード完了後はメイン画像(大)のロードが終わってなくてもオブジェクトを描画し、操作を受け付ける。
runLoading(); //プリロード状況をチェック function runLoading(){ var flag_loading=false; Object.keys(loader_pre).forEach(function(key){ if(!loader_pre[key]){ flag_loading=true; } },loader_pre); if(flag_loading){ setTimeout(function(){ runLoading(); //プリロードが完了してなければやり直し },500); }else{ scene.add(group); runAnimate(); //プリロードが完了したら描画開始 } }
メイン画像(大)のロードが完了するたびにcheckLoading()を動かす。すべてのロードが完了していればプリロードオブジェクトとローディング要素を非表示にする。
function checkLoading(){ var flag_loading=false; Object.keys(loader).forEach(function(key){ if(!loader[key]){ flag_loading=true; } },loader); if(!flag_loading){ if(prm.src_pre!=""){ prebase.visible=false; //メインのロードが完了したらプリロードオブジェクト非表示 } cv._loader.hide(); //ローダーも非表示 } }
まぁまぁしっかりした挙動で満足。
使用したパノラマ画像は上士幌町が誇る絶景スポット「ナイタイ高原牧場」からの空撮である。許可をいただきさらに奥からドローンを飛ばし上士幌町側から然別湖を捉えた非常にレアな写真なのだ。
本件とは違うが、空撮した方が出演している番組回「ニッポンぶらり鉄道旅 JR富良野線」の再放送が今週土曜日にあるとの情報をキャッチしたのだが本当だろうか。知り合いがテレビに映ると爆笑してしまう。あと瀬戸さおりさんが超キレイなので要チェック。
※前回:使えるパノラマビューア
※続編:DeviceOrientationControlsを制御してThree.jsスマホVRゲームの基本構造を考える(2019.03.11追記)
【PR】 Amazonで「Three.js」を探す!