- 投稿日:2019-11-26T21:40:41+09:00
要素の表示非表示は visibility:hidden の方が display:none よりも高速
はじめに
ACCESS Advent Calendar 2019 の25日目最終日です。
初めまして、今年一番ハマったソシャゲはドラクエウォークの @naohikowatanabe です。
HTML 的要素の表示非表示の基本
一般的には HTML 的に要素の表示非表示を行う際、以下のように言われます。
・要素の非表示は display:none か visibility:hidden で実現出来る
・visibility:hidden は「見えない+要素自体は存在する」
・display:none は「見えない+要素自体無し」
・visibility:hidden の方が要素の削除が無いので高速本記事ではどの程度速度に差があるのか、を見ていきます。
#組込ブラウザのお仕事をやっているとお客さんからこの質問が非常に多い。。
#なのでまとめてしまう。測定
display:none と visibility:hidden の違い の HTML をベースに、
「display:none による表示非表示100万回」
「visibility:hidden による表示非表示100万回」
を行うようにコードを変更し、測定します。html
<!doctype html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>difference between visibility hidden and display none</title> <link rel="stylesheet" href="css/style.css"> <script> const loop_count = 1000000; function visibilityhidden () { const two = document.getElementById("two"); const startTime = Date.now(); for (i = 0; i < loop_count; i++) { two.style.visibility = "hidden"; two.style.visibility = "visible"; } const endTime = Date.now(); console.log("visibility:hidden " + (endTime - startTime) + " [msec]"); } function displaynone () { const three = document.getElementById("three"); const startTime = Date.now(); for (i = 0; i < loop_count; i++) { three.style.display = "none"; three.style.display = "inline"; } const endTime = Date.now(); console.log("display:none " + (endTime - startTime) + " [msec]"); } </script> </head> <body> <div id="one" class="box"></div> <div id="two" class="box"> <h3>Visibility:hidden</h3> エレメント描画されず。けど、表示エリアは「残る」。背景色で塗りつぶした感じ。 </div> <div id="three" class="box"> <h3>display:none</h3> エレメントが表示エリアから消える。DOMとして存在はするけど描画されない。 </div> <div id="four" class="box"></div> <button onclick="visibilityhidden()">visibility:hidden * 100万回</button> <button onclick="displaynone()">display:none * 100万回</button> </body> </html>CSS
オリジナルから変えてません。
@charset "UTF-8"; .box{ width:150px; height:150px; margin:10px; border-radius: 10px; /* CSS3草案 */ -webkit-border-radius: 10px; /* Safari,Google Chrome用 */ -moz-border-radius: 10px; /* Firefox用 */ float:left; padding:20px; } #one{ background:#000; } #two{ /*visibility:hidden;*/ background:#9eccb3; } #three{ background:#f47d44; /*display:none;*/ } #four{ background:#000; clear:right; }表示結果
測定結果
やり方 時間[msec] display:none 100万回 1000 visibility:hidden 100万回 888 visibility:hidden の方が1割程度高速です。
PC 環境ではあまり気にならないですが、
組込環境ではマシンパワーが非力な場合が多いのでこういうところで少しずつ気を付けるのが良いですね。まとめ
要素の表示非表示は visibility:hidden の方が display:none よりも1割程度高速。
参考
display:none と visibility:hidden の違い
終わりに
本記事で ACCESS Advent Calendar 2019 無事終了です。
ここまで見ていただいた皆様に感謝です。それでは皆様、良いお年を!
来年もよろしくお願いします!
- 投稿日:2019-11-26T21:10:39+09:00
大きい画像をスマホで見たときに縮小する方法
趣味とポートフォリオを兼ねて自分が主催しているダンスイベントのサイトを作っています。
配置は基本的な構成なのですが、トップ画像で問題発生。
縦長のサイズのトップ画像が、PCサイズからスマホサイズに可変しない。
Googleで調べてみると
img{ width: 100%; height: auto; }
とあったんですが、これだとスマホサイズにはなるんですが、今度はPCサイズに可変しなくなりました。
色々と試行錯誤した結果
.main-view .top-img{ width: 100%; height: 0; padding-top: 50%; padding-bottom: 80%; background-image: url("画像のファイル"); background-size: cover; background-position: center center; background-repeat: no-repeat; }
これでスマホサイズにもPCサイズにも可変するようになりました。
ヘッダー下にdivタグを作成して、CSSにバックグラウンドとして画像を挿入。
パディングの余白を%で追加することで縦長のフライヤーも出現しました。
画像の出し方一つでも色々な要素が出てくるので、一つずつ理解していきたいです。
- 投稿日:2019-11-26T21:05:38+09:00
初心者によるプログラミング学習ログ 169日目
100日チャレンジの169日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
169日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) November 25, 2019
169日目
webサイトコーディング課題
ちょっとワケワカメになってきたので、全体的にもういちど見直し#100DaysOfCode #駆け出しエンジニアと繋がりたい #早起きチャレンジ
- 投稿日:2019-11-26T18:57:27+09:00
チュートリアル - 3D空間を使った簡単webサイト 5/5
Publishして誰でも見れるように公開!
プロジェクトを作成してビルドまでやってくれるのもPlayCanvasのすごいところ。
ここまで作成したものをPlayCanvasの公開環境に公開してみましょう。
左のメニュー、またはシーンの左上のManage Scenesから以下のPublish画面を開きます。
PUBLISH TO PLAYCANVASを選択して、一番下のPUBLISH NOWをクリックしてビルドは完了です。
URLでPC、スマホ、他ユーザーにもログインすることなく共有することができます。以上がチュートリアルの大枠となる説明でした。
おつかれさまでした。以降はWebサイトとして追加したいものを説明するおまけです。
おまけ
SCENEの名前を変更する
sceneの名前もUntitledのままじゃカッコがつかないですね。
これはPlayCanvasの設定から変更ができます。左上のPlayCanvasのロゴをクリックするとメニューが開きます。
その中のSettingsでインスペクター上にSETTINGSを開き、上のScene Nameから名前を変更できます。
headerの処理
headerのhtmlも追加しているので、同じようにglobal変数を使って同じように実装していきます。
addhtml.jsのinitializeに以下に書き換えます。
さらに、headerのナビゲーションをクリックするためgnavClickのイベントの処理も追加します。
headerのナビゲーションではどのsceneを選択したのかを、data-scene属性を作成して参照します。Addhtml.prototype.initialize = function() { // init globalPc.scene = 0; // どのシーンページを開いているか保管 var htmlNameArr = []; // attributesのデータの名前を配列にします var arrIndex = Addhtml.attributes.index; for(var i = 0; i < Object.keys(arrIndex).length; i++){ // attributesのデータを取得してfor文で回す if(!Object.keys(arrIndex)[i].indexOf("scene")){ // 名前にhtmlが入っているデータをif文 htmlNameArr.push(Object.keys(arrIndex)[i]); // htmlデータの名前だけ配列化する } } var head = document.getElementsByTagName("head")[0]; // headタグ取得 var body = document.getElementsByTagName("body")[0]; // bodyタグ取得 var wrapper = document.createElement("div"); // DOMを囲う要素を作成 wrapper.className = "wrapper"; // クラス名指定 body.appendChild(wrapper); // bodyタグの最後に要素を追加 container = document.createElement("main"); // DOMを囲う要素を作成 container.className = "container"; // クラス名指定 wrapper.appendChild(container); // bodyタグの最後に要素を追加 var style = document.createElement("style"); // cssのstyleタグ wrapper.insertAdjacentHTML("afterbegin", this.header.resource); // attrで追加したヘッダーを追加 // ヘッダー内のgnaviを取得 var gnavLists = document.querySelector(".gnav_lists"); gnavLists.innerHTML = ""; // .gnav_lists内のhtmlを空っぽに var gnavItem = document.createElement("div"); // gnavの各リンクのdivを作成 gnavItem.className = "gnav_item"; // gnavリンク要素のclass名を指定 var gnavItemCl = []; // gnav_itemを整理する style.append(this.css._resources[0]); head.appendChild(style); // headの最後にstyleを追加 for(var i = 0; i < htmlNameArr.length; i++){ container.insertAdjacentHTML("beforeend", this[htmlNameArr[i]].resource); // アセットから取得したhtmlを追加 // 今回はgnavのリンクの数とhtmlのアセットの数が同じなのでどう処理を行う。 gnavItemCl[i] = gnavItem.cloneNode(true); // gnavItemのクローンを作成 gnavItemCl[i].innerHTML = '<label for="menu"><span>' + htmlNameArr[i] + '</span></label>'; // innerHTMLを作成 gnavItemCl[i].setAttribute("data-scene",i+1); // scene1,scene2,scene3を切り替えるため数値をdata-sceneに追加 gnavLists.append(gnavItemCl[i]); // gnav_lists内に追加 gnavItemCl[i].addEventListener("click", gnavClick, false); // gnav_item各々にイベントをセット } };function gnavClick(e){ // gnav_itemをクリックしたら発火 if(globalPc.scene === 0) { globalPc.scene = this.getAttribute("data-scene"); // クリックしたgnav_itemのdata-sceneを取得 } }
ホバーとクリックのアクションを追加
クリックでhtmlにイベントを取得させましたが、アニメーションを追加します。
ホバーすればscaleが少し大きくなり、クリックすればワイヤーフレームになるといった感じです。
このアクションではもっと色んな広がりを見せることができます。
PlayCanvasではアニメーションを作れるTweenライブラリが公開されていますのでご参照ください。
https://developer.playcanvas.com/en/tutorials/tweening/hotspot.jsを以下にまるっと書き換えます。
var Hotspot = pc.createScript('hotspot'); // canvasのclickやhoverなどの処理を行う Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得 Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定 Hotspot.prototype.initialize = function() { // init this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な) this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能) this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時 this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時 this.localScale = this.entity.getLocalScale().clone(); // 初期のscaleを保持 this.hoverScale = this.localScale.clone().scale(1.2); // ホバー時のentityのscaleを保持 }; Hotspot.prototype.update = function(dt) { // update this.cameraPosition = this.cameraEntity.getPosition(); // cameraのポジション(座標)を取得 this.directionToCamera.sub2(this.cameraPosition, this.entity.getPosition()); // 2つの3次元ベクトルの値を互いに減算 this.directionToCamera.normalize(); // 3次元ベクトルを単位ベクトルに変換 if(globalPc.scene === 0){ // sceneが選択されていない場合 for (var i = 0; i < this.entity.model.meshInstances.length; i++) { this.entity.model.meshInstances[i].renderStyle = pc.RENDERSTYLE_SOLID; // レンダラーのスタイルをソリッドに変更 } } if(this.entity.tags._list[0].replace(/[^0-9]/g, '') === globalPc.scene) { // gnav_itemから選択されたsceneを参照 this.entity.model._model.generateWireframe(); // モデルのワイヤーフレームを準備 for (var j = 0; j < this.entity.model.meshInstances.length; j++) { // モデルのメッシュを取得 this.entity.model.meshInstances[j].renderStyle = pc.RENDERSTYLE_WIREFRAME; // ワイヤーフレームにレンダリング } } }; Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理) if (this.hitArea.intersectsRay(this.ray)) { globalPc.scene = this.entity.tags._list[0].replace(/[^0-9]/g, ''); // entityで設定したタグを取得し、s1、s2...の数字以外をreplaceで削除し数字のみ代入 this.entity.model._model.generateWireframe(); // モデルのワイヤーフレームを準備 for (var i = 0; i < this.entity.model.meshInstances.length; i++) { // モデルのメッシュを取得 this.entity.model.meshInstances[i].renderStyle = pc.RENDERSTYLE_WIREFRAME; // ワイヤーフレームにレンダリング } } }; Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時 this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換 this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換 if (this.hitArea.intersectsRay(this.ray) && globalPc.scene === 0) { // sceneが選択されていなくて、ヒットエリアとレイが交差した場合 this.entity.setLocalScale(this.hoverScale); // entityのスケールを大きく }else{ this.entity.setLocalScale(this.localScale); // entityのスケールを小さく } }; Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時 if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時 this.doRayCast(event); // レイキャストを呼ぶ } };さらにおまけで、マウスのクリックボタンの判定を以下のように取っていましたが、
CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら if (event.button === pc.MOUSEBUTTON_LEFT) {} // 左クリック if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック };以下のようにもっと簡単に左・中・右クリックのbooleanを取ることもできます。
CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら if (event.buttons[0]) {} // 左クリック if (event.buttons[1]) {} // 中クリック if (event.buttons[2]) {} // 右クリック };
3Dオブジェクトのモデルを変えたい!
PlayCanvasにはAssets Storeなるものがあり、ここから指定のプロジェクトのAssetsライブラリにアセットを追加することができます。
http://store.playcanvas.com/アセットを選択し、Downloadをするとログインしているアカウントが所有しているプロジェクトの一覧が表示されますので、指定のプロジェクトを選択することでアセットが使用できます。
- 投稿日:2019-11-26T18:57:10+09:00
チュートリアル - 3D空間を使った簡単webサイト 4/5
3Dオブジェクトに応じたHTMLのページを表示させる
マウスからイベントが取得できたので、クリックしたらHTMLを表示するようにします。
予めAssetsにmarkupというディレクトリ内に使用されるhtmlとcssのサンプルがあります。
事前にサンプルを用意していますが、作る際には自分のローカルで作成する必要があるのでご注意ください。今回は3つの3Dオブジェクトと3つのhtmlを関係を持たせて、
「3Dオブジェクトがクリックされたら、それにリンクするhtmlが表示される」 までを作ります。ここで使用するscriptはaddhtml.jsですが、これもまたEntityにADD SCRIPTする必要があります。
使用するaddhtml.jsは特定のEntityにADD SCRIPTする必要はありません。
今回はcharaというグループのEntityにADD SCRIPTします。addhtml.jsをADD SCRIPTしたら、コードエディターを開きます。
以下のコードを追加します。
Addhtml.attributes.add("css", {type: 'asset', assetType:'css', title: 'CSS Style'}); // アセットのcss読み込み Addhtml.attributes.add("header", {type: 'asset', assetType:'html', title: 'HTML Header'}); // アセットのhtmlのheader読み込み Addhtml.attributes.add("scene1", {type: 'asset', assetType:'html', title: 'HTML Scene1'}); // アセットのhtmlのscene1読み込み Addhtml.attributes.add("scene2", {type: 'asset', assetType:'html', title: 'HTML Scene2'}); // アセットのhtmlのscene2読み込み Addhtml.attributes.add("scene3", {type: 'asset', assetType:'html', title: 'HTML Scene3'}); // アセットのhtmlのscene3読み込み早速、markupディレクトリのファイルを登録していきます。
下のアセットからmarkupというディレクトリに登録するファイルがあります。
htmlとcssを登録していきます。
登録したコードを追加するために
とのエレメントを取得する必要があります。
なぜ取得するのかというと、取得したheadやbodyに対して、appendChild()やinnerHTML、insertAdjacentHTML()などでhtmlを追加するからです。以下のコードを追加していきます。
var container; Addhtml.prototype.initialize = function() { // init var htmlNameArr = []; // attributesのデータの名前を配列にします var arrIndex = Addhtml.attributes.index; for(var i = 0; i < Object.keys(arrIndex).length; i++){ // attributesのデータを取得してfor文で回す if(!Object.keys(arrIndex)[i].indexOf("scene")){ // 名前にhtmlが入っているデータをif文 htmlNameArr.push(Object.keys(arrIndex)[i]); // htmlデータの名前だけ配列化する } } var head = document.getElementsByTagName("head")[0]; // headタグ取得 var body = document.getElementsByTagName("body")[0]; // bodyタグ取得 var wrapper = document.createElement("div"); // DOMを囲う要素を作成 wrapper.className = "wrapper"; // クラス名指定 body.appendChild(wrapper); // bodyタグの最後に要素を追加 container = document.createElement("main"); // DOMを囲う要素を作成 container.className = "container"; // クラス名指定 wrapper.appendChild(container); // bodyタグの最後に要素を追加 var style = document.createElement("style"); // cssのstyleタグ wrapper.insertAdjacentHTML("afterbegin", this.header.resource); // attrで追加したヘッダーを追加 style.append(this.css._resources[0]); head.appendChild(style); // headの最後にstyleを追加 for(var i = 0; i < htmlNameArr.length; i++){ // htmlのアセットを配列管理するためにfor文でattrの名前の配列を回す container.insertAdjacentHTML("beforeend", this[htmlNameArr[i]].resource); // アセットから取得したhtmlを追加 } };これでhtmlとcssの情報を追加できました。
headの中にcssのスタイルが入り()、bodyの中にhtmlの要素が入っています。次にオブジェクトをクリックしたらそれに連動したhtmlを表示するやり方です。
attributesで登録したhtmlでscene1、scene2、scene3とありました。
この数字を3Dオブジェクトとの関係性に使います。Editorから3DオブジェクトのModelを選択し、インスペクターからTagsを登録します。
tagの名前は任意のもので大丈夫です。数字をそれぞれに入れるのを忘れないように。登録したtagをクリックした時に取得できるか確認します。
hotspot.jsのdoRayCastでtagを確認します。Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理) if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("click!! : ",this.entity.tags._list[0]); } };クリックした3Dオブジェクトから各々のtagが取得できたと思います。
これを使ってhtmlとリンクさせます。addhtml.jsに以下を追加します。
globalPc = {}; // グローバルな変数(オブジェクト) Addhtml.prototype.update = function(dt) { // update if(Number(globalPc.scene) > 0) { // いずれかのsceneが選択されている場合 if(!container.classList.contains("is-open")){ // 追加したアセットのhtmlにis-openのclassが追加されていない場合(どのhtmlも表示されていない) container.classList.add("is-open"); // is-openのclass名をcontainerに追加 var sectionElements = document.getElementsByClassName("section"); // sectionを取得 for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理 if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理 sectionElements[i].classList.remove("is-current"); // is-currentを削除 } } sectionElements[globalPc.scene-1].classList.add("is-current"); // 選択されたsceneにis-currentのclass名を追加 document.getElementsByClassName("section_close")[globalPc.scene-1].addEventListener("click", btnClose, false); // 選択されたsceneの閉じるボタンにイベントをセット } } }; function btnClose(e){ // 閉じるボタンが押されたら発火 e.preventDefault(); globalPc.scene = 0; // 閉じるボタンなので選択されたsceneはnullにするので、0を代入 container.classList.remove("is-open"); // containerのis-openのclass名を削除 var sectionElements = document.getElementsByClassName("section"); // sectionを取得 for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理 if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理 sectionElements[i].classList.remove("is-current"); // is-currentを削除 } } }リンクさせる方法として、global変数のglobalPcで今開いているページを管理できるようにします。
initializeには以下を追加します。
globalPc.scene = 0; // どのシーンページを開いているか保管先ほど確認したhotspot.jsのdoRayCastでもglobal変数に数字を与えます。
数字のみを代入するようにします。Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理) if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 globalPc.scene = this.entity.tags._list[0].replace(/[^0-9]/g, ''); // entityで設定したタグを取得し、scene1、scene2...の数字以外をreplaceで削除し数字のみ代入 } };Launch画面でリロードして3Dオブジェクトをクリックすると、それぞれ該当したhtmlが表示されるようになります。
これで簡単なwebの大枠はできました。
[ hotspot.jsのコード ]
var Hotspot = pc.createScript('hotspot'); // canvasのclickやhoverなどの処理を行う Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得 Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定 Hotspot.prototype.initialize = function() { // init this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な) this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能) this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時 this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時 }; Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理) if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 globalPc.scene = this.entity.tags._list[0].replace(/[^0-9]/g, ''); // entityで設定したタグを取得し、s1、s2...の数字以外をreplaceで削除し数字のみ代入 } }; Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時 this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換 this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換 if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("hover"); } }; Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時 if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時 this.doRayCast(event); // レイキャストを呼ぶ } };[ addhtml.js のコード ]
var Addhtml = pc.createScript('addhtml'); // htmlを追加などする処理を記入 Addhtml.attributes.add("css", {type: 'asset', assetType:'css', title: 'CSS Style'}); // アセットのcss読み込み Addhtml.attributes.add("header", {type: 'asset', assetType:'html', title: 'HTML Header'}); // アセットのhtmlのheader読み込み Addhtml.attributes.add("scene1", {type: 'asset', assetType:'html', title: 'HTML Scene1'}); // アセットのhtmlのscene1読み込み Addhtml.attributes.add("scene2", {type: 'asset', assetType:'html', title: 'HTML Scene2'}); // アセットのhtmlのscene2読み込み Addhtml.attributes.add("scene3", {type: 'asset', assetType:'html', title: 'HTML Scene3'}); // アセットのhtmlのscene3読み込み globalPc = {}; // グローバルな変数(オブジェクト) var container; Addhtml.prototype.initialize = function() { // init globalPc.scene = 0; // どのシーンページを開いているか保管 var htmlNameArr = []; // attributesのデータの名前を配列にします var arrIndex = Addhtml.attributes.index; for(var i = 0; i < Object.keys(arrIndex).length; i++){ // attributesのデータを取得してfor文で回す if(!Object.keys(arrIndex)[i].indexOf("scene")){ // 名前にhtmlが入っているデータをif文 htmlNameArr.push(Object.keys(arrIndex)[i]); // htmlデータの名前だけ配列化する } } var head = document.getElementsByTagName("head")[0]; // headタグ取得 var body = document.getElementsByTagName("body")[0]; // bodyタグ取得 var wrapper = document.createElement("div"); // DOMを囲う要素を作成 wrapper.className = "wrapper"; // クラス名指定 body.appendChild(wrapper); // bodyタグの最後に要素を追加 container = document.createElement("main"); // DOMを囲う要素を作成 container.className = "container"; // クラス名指定 wrapper.appendChild(container); // bodyタグの最後に要素を追加 var style = document.createElement("style"); // cssのstyleタグ wrapper.insertAdjacentHTML("afterbegin", this.header.resource); // attrで追加したヘッダーを追加 style.append(this.css._resources[0]); head.appendChild(style); // headの最後にstyleを追加 for(var i = 0; i < htmlNameArr.length; i++){ // htmlのアセットを配列管理するためにfor文でattrの名前の配列を回す container.insertAdjacentHTML("beforeend", this[htmlNameArr[i]].resource); // アセットから取得したhtmlを追加 } }; Addhtml.prototype.update = function(dt) { // update if(Number(globalPc.scene) > 0) { // いずれかのsceneが選択されている場合 if(!container.classList.contains("is-open")){ // 追加したアセットのhtmlにis-openのclassが追加されていない場合(どのhtmlも表示されていない) container.classList.add("is-open"); // is-openのclass名をcontainerに追加 var sectionElements = document.getElementsByClassName("section"); // sectionを取得 for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理 if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理 sectionElements[i].classList.remove("is-current"); // is-currentを削除 } } sectionElements[globalPc.scene-1].classList.add("is-current"); // 選択されたsceneにis-currentのclass名を追加 document.getElementsByClassName("section_close")[globalPc.scene-1].addEventListener("click", btnClose, false); // 選択されたsceneの閉じるボタンにイベントをセット } } }; function btnClose(e){ // 閉じるボタンが押されたら発火 e.preventDefault(); globalPc.scene = 0; // 閉じるボタンなので選択されたsceneはnullにするので、0を代入 container.classList.remove("is-open"); // containerのis-openのclass名を削除 var sectionElements = document.getElementsByClassName("section"); // sectionを取得 for(var i = 0; i < sectionElements.length; i++){ // sectionをループ処理 if(sectionElements[i].classList.contains("is-current")){ // sectionにis-currentのclass名を持つかif処理 sectionElements[i].classList.remove("is-current"); // is-currentを削除 } } }次はここまで作成したSceneをPublishして公開します。
- 投稿日:2019-11-26T18:56:57+09:00
チュートリアル - 3D空間を使った簡単webサイト 3/5
3Dオブジェクトでマウスのイベントを起こす
3Dオブジェクトとマウスカーソルとの関係性を作ります。
ヒエラルキーからchara->Modelを選択。
ADD COMPONENTからScriptを選択し、hotspotのscriptを登録します。
このModelを一つずつ選択する動作は面倒ですが、複数選択して設定を行うこともできます。hotspotの中身をコードエディターで開きます。
配置された3Dオブジェクトとマウスカーソルとの関係を持たせるにはレイキャストと呼ばれるものを使用します。
レイキャストとは、特定の地点から特定の方向へ直線の線を引き、その線上のどこかでぶつかるものがあるか検知するものです。
ここでのレイキャストは、「マウスがクリックされた時、カメラから見て、3Dオブジェクトとそのマウスカーソルが重なっているか」を取得するような処理を作ります。ここから以下の処理を追加していきます
- 3Dオブジェクトにレイキャストの線が当たるヒットエリアを作成
- 使用するカメラのEntityとヒットエリアの大きさをAttributesでEditorから設定
- ヒットエリアにマウスホバーしてイベントが取れる
以下のコードをhotspot.jsに書き換えます。
var Hotspot = pc.createScript('hotspot'); Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのEntityを取得 Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // Entityのヒットエリアの範囲を指定 Hotspot.prototype.initialize = function() { // init this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な) this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能) this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時 }; Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時 this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換 this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換 if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("hover"); } };新しくattributesというのが言葉がありますが、これは属性設定ができ、PlayCanvasのEditorからデータを変更することができるようになります。
ここではカメラのEntityの選択と、対象の3Dオブジェクトのヒットエリアの大きさを指定できます。Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのEntityを取得 Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // Entityのヒットエリアの範囲を指定以下画像のように入力エリアが表示されていない場合は、Editアイコン横のParseで更新され表示されます。
initializeではヒットエリア、レイキャストのRayの設定、マウス移動のイベントを取得しています。
Hotspot.prototype.initialize = function() { this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な) this.ray = new pc.Ray(); // 直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能) this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時 };マウス移動のイベントで3Dオブジェクトとカメラから見てホバーしているかonMouseHoverで取得しています。
Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時 this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換 this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換 if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("hover"); } };マウス移動のイベントで3Dオブジェクトとカメラから見てホバーしているかonMouseHoverで取得しています。
これでマウスオーバー(ホバー)のイベントを取得できました。
次はクリックのイベントです。initializeに以下を追加
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時クリックイベントの左クリックを取得
Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時 if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時 this.doRayCast(event); // レイキャストを呼ぶ } };クリックしたらレイキャストを走らせる
Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理) if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("click!!"); } };Launch画面をリロードしてコンソールから確認ができます。
ここまでのコードは以下になります。(hotspot.js)
var Hotspot = pc.createScript('hotspot'); // canvasのclickやhoverなどの処理を行う Hotspot.attributes.add("cameraEntity", {type: "entity", title: "Camera Entity"}); // カメラのentityを取得 Hotspot.attributes.add("radius", {type: "number", title: "Radius"}); // entityのヒットエリアの範囲を指定 Hotspot.prototype.initialize = function() { // init this.hitArea = new pc.BoundingSphere(this.entity.getPosition(), this.radius); // ヒットエリアを作成。BoundingSphereがentityの境界エリアを作成(Photoshopでいうバウンディングボックス的な) this.ray = new pc.Ray(); // cameraからentityへ直進する線のデータを作成。(Rayは光線の意でstart pointからentityまでの距離を測ったりすることが可能) this.directionToCamera = new pc.Vec3(); // Vector座標の型を取得 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseHover, this); // マウスカーソルがホバーした時 this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時 }; Hotspot.prototype.doRayCast = function (screenPosition) { // レイキャスト処理(ある地点から特定方向に直線で線を引いて、その線上で物体があるか検知する処理) if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("click!!"); } }; Hotspot.prototype.onMouseHover = function(screenPosition) { // マウスホバー時 this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); // ポジションを2Dスクリーンから3D空間へ変換 this.ray.origin.copy(this.cameraEntity.getPosition()); // レイのオリジナルのポジションにカメラのポジションをコピー this.ray.direction.sub(this.ray.origin).normalize(); // 3次元ベクトルを他の場所から減算し、単位ベクトルに変換 if (this.hitArea.intersectsRay(this.ray)) { // ヒットエリアとレイが交差した場合 console.log("hover"); } }; Hotspot.prototype.onMouseDown = function(event) { // クリックが押されている時 if (event.button == pc.MOUSEBUTTON_LEFT) { // 左クリックが押された時 this.doRayCast(event); // レイキャストを呼ぶ } };次は3Dオブジェクトに応じてHTMLのページを表示させるようにします。
- 投稿日:2019-11-26T18:56:40+09:00
チュートリアル - 3D空間を使った簡単webサイト 2/5
カメラを移動させる
まずはドラッグして3D空間を移動させます。
事前に用意している scripts ディレクトリ内の cameraMove.js を開きます。
ファイル内のコードはファイル作成時にデフォルトで書かれているもので、上から以下になります。
- initialize : 初回読み込み
- update : 毎フレームごと
- swap : scriptファイル更新時(hot reloading)
マウスイベントを取得
PlayCanvasにはエンジンのRootのネームスペースがあります。
https://developer.playcanvas.com/en/api/pc.htmlpc.MOUSEBUTTON_LEFTなどからイベントを取得し判別することができます。
以下のコードを追加します。CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら if (event.button === pc.MOUSEBUTTON_LEFT) {} // 左クリックのとき if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック }; CameraMove.prototype.onMouseUp = function (event) { // クリックアップしたら if (event.button === pc.MOUSEBUTTON_LEFT) {} // 左クリック if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック }; CameraMove.prototype.onMouseMove = function (event) { // マウスカーソルが動いたら console.log(event); };上記で追加した関数を使いたいので、initializeでイベントを取得できるようにします。
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時 this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this); // クリックを押している状態から離れる時 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスカーソルが動いている時ここのon()はjQueryのon()と同じようなもので、イベント処理を行い、関数を呼び出します。
このままではこのscriptは呼び出されません。対象となるEntityに登録する必要があります。
ヒエラルキーからCameraを選択し、インスペクターのADD COMPONENTからScriptを選択。SCRIPTSの設定のADD SCRIPTからcameraMoveを選択します。
cameraMoveが設定されたらOKです。
ちなみに、名前の横のEditをクリックするとコードエディターを開きます。
そのさらに横のParseをクリックすると設定した scriptを再読み込みします。Lauch画面をリロードしConsoleを確認するとonMouseMoveのconsole.logがマウスが動くたびに呼び出されています。
これをクリックしている間だけに変更し、ドラッグしたら呼び出すようにします。
initializeにクリックしているか判定するフラッグを用意します。this.f_click = false;これに合わせて、onMouseDownとonMouseUpに適当なものを追加します。
追加すると以下のようなコードになると思います。var CameraMove = pc.createScript('cameraMove'); // カメラ移動などの処理 CameraMove.prototype.initialize = function() { this.f_click = false; this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時 this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this); // クリックを押している状態から離れる時 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスカーソルが動いている時 }; CameraMove.prototype.update = function(dt) {}; CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = true; } // 左クリックのとき if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック }; CameraMove.prototype.onMouseUp = function (event) { // クリックアップしたら if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = false; } // 左クリック if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック }; CameraMove.prototype.onMouseMove = function (event) { // マウスカーソルが動いたら if(this.f_click){ console.log(event); } };これでクリックしドラッグした時だけconsole.logが呼ばれるようになりました。
ここで呼ばれているデータからカメラも移動するようにします。initializeで以下を設定。
カメラのEntityの情報を取得しています。ここではカメラのPositionを取得します。
https://developer.playcanvas.com/en/api/pc.Entity.htmlthis.cameraEntity = this.entity; // このjsを受けているEntityを取得(カメラであることを前提に) this.pos = this.cameraEntity.getPosition(); // カメラの座標を取得onMouseMoveの中身を以下に差し替えます。
クリックされていない場合はreturnするように変更。
eventからベクトル量を取得します。取得したベクトル量からカメラのポジションを再設定します。if(!this.f_click) return; // クリックが押されている時 var posDX = event.dx/500; // x座標の変化量 var posDY = event.dy/500; // y座標の変化量 var posX = this.pos.x - posDX; // ドラッグ量からカメラの座標を計算 var posZ = this.pos.z - posDY; // ドラッグ量からカメラの座標を計算 this.cameraEntity.setPosition(posX,this.pos.y,posZ); // カメラの座標をセットこれでカメラの移動ができるようになりました。
最終的にコードは以下のようになります。
var CameraMove = pc.createScript('cameraMove'); // カメラ移動などの処理 CameraMove.prototype.initialize = function() { this.f_click = false; this.cameraEntity = this.entity; // このjsを受けているEntityを取得(カメラであることを前提に) this.pos = this.cameraEntity.getPosition(); // カメラの座標を取得 this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this); // クリックが押された時 this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this); // クリックを押している状態から離れる時 this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this); // マウスカーソルが動いている時 }; CameraMove.prototype.update = function(dt) {}; CameraMove.prototype.onMouseDown = function (event) { // クリックダウンしたら if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = true; } // 左クリックのとき if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック }; CameraMove.prototype.onMouseUp = function (event) { // クリックアップしたら if (event.button === pc.MOUSEBUTTON_LEFT) { this.f_click = false; } // 左クリック if (event.button === pc.MOUSEBUTTON_MIDDLE) {} // 中クリック if (event.button === pc.MOUSEBUTTON_RIGHT) {} // 右クリック }; CameraMove.prototype.onMouseMove = function (event) { // マウスカーソルが動いたら if(!this.f_click) return; // クリックが押されている時 var posDX = event.dx/500; // deltaX var posDY = event.dy/500; // deltaY var posX = this.pos.x - posDX; // ドラッグ量からカメラの座標を計算 var posZ = this.pos.z - posDY; // ドラッグ量からカメラの座標を計算 this.cameraEntity.setPosition(posX,this.pos.y,posZ); // カメラの座標をセット };次は3Dオブジェクトをクリックしてイベントを取得する処理です。
- 投稿日:2019-11-26T18:55:55+09:00
チュートリアル - 3D空間を使った簡単webサイト 1/5
※このチュートリアルはPlayCanvas運営事務局で使用しているハンズオンの資料です。
PlayCanvasでWebサイトを作る
ゲームエンジンでwebサイトを作る?
PlayCanvasはWebGL/HTML5ゲームエンジンであり、ゲームだけでなくwebへの展開もできます。
そこでPlayCanvasを使ってどんなwebサイトが作れるのか、実際に作っていきたいと思います。このチュートリアルでは、PlayCanvasを使って3D空間のコンテンツを作成し、その上にhtmlの要素を配置して、LPページ(ランディングページ)やキャンペーンサイトのようなwebサイトを作成していきたいと思います。
できるページは以下のようなページができます。
https://playcanv.as/p/uQN2hNcm/3Dオブジェクトをクリックするとそれに応じたhtmlが表示されるサイト。
ドラッグするとカメラの位置も変わり、3Dオブジェクトを好きな位置に配置して探す、といったこともできます。今回は上記のものを作成するべく、チュートリアルを始めましょう。
Projectをforkする
チュートリアル用に準備したProjectがあるので、こちらをforkしてチュートリアルを始めます。
以下のProjectからforkします。
https://playcanvas.com/project/623766/forkしたProjectのページに飛ぶので、forkしたプロジェクトのEDITORボタンからエディット画面に飛びます。
エディット画面のGUIの説明
まずSCENESの画面からeditするsceneを選びます。
Untitledしかないのでこちらを選択。(新規にプロジェクトを作る際もUntitledのsceneがあります)Untitledをクリックすると以下の画面になります。
上の画像を基に、GUIを簡単に説明していきます。
- メニュー(MENU)
Photoshopなどでいうツールバーとメニューバーが合わさっている感じ。
プロジェクトの設定やシーンの設定などもここで操作できます。
- ヒエラルキー(HIERARCHY)
シーン内に存在するオブジェクトの一覧が表示されます。
このシーンではどんなentityが使われているのかなどここで管理。
まとめてグループ化や名前の変更などもできる。
- シーン(SCENE)
3D空間が表示され、自由な位置・角度から眺めることができます。
3DコンテンツやカメラのPositionやRotateなどを弄ることができます。
- アセット(ASSETS)
プロジェクトhtmlやcss、imgといったファイルや3Dモデルのデータなどもここで表示され、ここに入れて使われるデータたちは合わせてアセットと呼ばれています。
ディレクトリで分けたり名前を変更することもできます。各ファイルはアップロード時にIDが付与され管理されるため、同じ名前のファイル名をアップロードしても上書きアップロードにはならないことがあるので注意。
- インスペクター(INSPECTOR)
ヒエラルキーやシーンで選択中のオブジェクトの設定を編集できます。
3Dモデルのメッシュや衝突判定、物理制御に関するパラメータもここで定義することが可能。
コードエディター
基本的なGUIはこの5つで、他にコードエディターがあります。
コードエディターはメニューの下の方にあります。コードエディターは普段使用されているであろうエディターと基本同じです。
JavaScriptのsnippetやコードのhighlightなども基本付いています。基本ここでコードを書いて反映していきます。
Lauch実行
右上の再生ボタン的な矢印のボタンをクリックすると、Launch実行されます。
Sceneの現状をLaunch実行してくれるため、開発しながら確認ができます。
Launch画面を開いたままにしておくと、Editor画面とライブリンクが持続した状態になり、Editor画面でPositionmの数値などを変更するとリアルタイムでLaunch画面でもプレビューされます。プロジェクトを確認できたところで、次はカメラ移動の処理をつけていきます。