すだちキャンパス

すだちキャンパス

やってみたこと、学んだことなどのメモ。

A-FrameでUI(ポップアップウィンドウ)を作ってみた

こんにちは。WebVRでポップアップウィンドウを表示させる事があったので、方法などをメモしておきます。

基本的なポップアップウィンドウの作成

A-Frame GUI を使う

調べたところA-FrameのUI用フレームワークを作成されている方がいらっしゃったので、使用させて頂くことにしました。
github.com

こちらから動く例を見ることもできます。
基本的にはこの例を改造して作成しました。

使用する際は、A-Frameと同様にヘッダーに次のように記述して読み込みます。

<script src="https://rawgit.com/rdub80/aframe-gui/master/dist/aframe-gui.min.js"></script>

また、a-sceneでシーンを作成する時に

<a-scene gui-env>
</a-scene>

のように記述しておきます。

クリックすると文字が表示されるものを作る

作ると言ってもこちらのドロップダウンリストの改造ですが、覚書としてコードについてメモしておきます。
まずコードの全容です。

<!-- ボタンを押した時の挙動 -->
<script>
	window.closeDropdown = function() {
	    var mydropdown = document.getElementById("mydropdown");
            mydropdown.emit('closedropdown');
	    var myoptions = document.getElementById("myoptions");
            myoptions.emit('closedropdown');
	    }
	window.openDropdown = function() {
	    var mydropdown = document.getElementById("mydropdown");
            mydropdown.emit('opendropdown');
	    var myoptions = document.getElementById("myoptions");
            myoptions.emit('opendropdown');
</script>

<a-scene gui-env>
<a-assets>
      <img id="crab" src="assets/subesubemanjugani.png">
</a-assets>

<a-entity id="dropdownContainer" position="0 0 -4">

<!-- はじめに表示されているボタン -->
          <a-gui-flex-container
            flex-direction="row" justify-content="center" align-items="normal" component-padding="0.1"
            opacity="0" width="2.5" height="0.75"
            position="0 0.375 0" rotation="0 0 0"
          >
              <a-gui-icon-button
			id="mydropdown"
			height="0.75"
			onclick="openDropdown"
			icon="android-menu"
			value=""
			font-family="Arial"
			visible="true"
			animation__visibleIn="property: visible; to: false; delay: 500; startEvents:opendropdown"
			animation__visibleOut="property: visible; to: true; delay: 500; startEvents:closedropdown"
		>
		</a-gui-icon-button>
          </a-gui-flex-container>

<!-- ボタンが押されたら表示される部分-->
          <a-gui-flex-container
  		id="myoptions"
  		flex-direction="column" justify-content="center" align-items="normal" component-padding="0.1"
  		opacity="0" width="2.5" height="0.75"
  		position="0 0 -0.15" rotation="0 0 0" scale="1 0.001 0.001"
  		visible="false"
                animation__positionIn="property: position; to: 0 0.375 0.15; dur: 1000; startEvents:opendropdown"
		animation__scaleIn="property: scale; to: 1 1 1; dur: 1000; startEvents:opendropdown"
		animation__visibleIn="property: visible; to: true; dur: 100; startEvents:opendropdown"
		animation__positionOut="property: position; to: 0 0 -0.15; dur: 1000; startEvents:closedropdown"
		animation__scaleOut="property: scale; to: 1 0.001 0.001; dur: 1000; startEvents:closedropdown"
		animation__visibleOut="property: visible; to: false; dur: 100; delay: 500; startEvents:closedropdown"
	  >
          <a-gui-label
		width="4" height="0.75"
		value="スベスベマンジュウガニ"
		margin="0 0 0.05 0"
	   >
	   </a-gui-label>
  	   <a-gui-icon-button
  		width="0.5" height="0.5"
  		onclick="closeDropdown"
  		icon="android-close"
  	   >
  	   </a-gui-icon-button>
 	</a-gui-flex-container>

</a-entity>

<!-- カメラとカーソル -->
<a-entity id="cameraRig" position="0 1.6 0">
	<a-camera look-controls wasd-controls position="0 0 0">
		<a-gui-cursor id="cursor"
				raycaster="objects: [gui-interactable]"
				fuse="true" fuse-timeout="1000"
				design="ring"
		>
		</a-gui-cursor>
	</a-camera>
</a-entity>

<!-- 背景色の指定 -->
<a-sky color="#00bfff"></a-sky>
</a-scene>

仕組みとしては、まずonclick="hogehoge"と記述してあるボタンをクリックすることによってJavaScriptのwindow.hogehogeが動作します。次にidが振られているコンポーネント(ラベルやボタン)が取得され、mylabel.emit('hoge');によりhogeという値がそのコンポーネントに送られます。そしてアニメーションのstartEvents:hogeによってアニメーションが行われるようになっています。アトリビュートのvisibleをtrue/falseにすることで、ボタンを押した時に表示されたり消えたりするようになっているのですね。

また、a-cursorについてですが、fuseをtrueにすることで一定時間注視して実行されるようなクリックになっています。fuse-timeoutの値を変更すると注視に必要な時間が変わります。

画像も一緒に表示する

画像も一緒に表示させることが可能です。ただ、a-imageにはvisibleのアトリビュートがないので、opacityをアニメーションさせることになります。もしくは、a-imageをa-entityで囲ったり、planeを用意してそこに貼り付けても良いです。

<!-- 板に貼り付けた画像 -->
<a-entity
          id="myImage"
          geometry="primitive: plane; height: 5; width: 4.5"
          material="src: #crab"
          position="0 2 -4"
          rotation="0 0 0"
          visible="false"
          animation__visibleIn="property: visible; to: true; delay: 500; startEvents:opendropdown"
          animation__visibleOut="property: visible; to: false; delay: 500; startEvents:closedropdown"
></a-entity>

<!-- entityで挟んでvisibleをアニメーションさせたもの -->
<a-entity
          id="myImage2"
          visible="false"
          animation__visibleIn="property: visible; to: true; delay: 500; startEvents:opendropdown"
          animation__visibleOut="property: visible; to: false; delay: 500; startEvents:closedropdown"
>
          <a-image src="#crab" height="5" width="4.5" position="-4.5 2 -4"
          rotation="0 0 0">
          </a-image>
</a-entity>

<!-- opacityを変化させたもの -->
<a-image
          id="myImage3"
          height="5"
          width="4.5"
          src="#crab"
          position="4.5 2 -4"
          rotation="0 0 0"
          opacity="0"
          animation__visibleIn="property: material.opacity; to: 1; delay: 500; startEvents:opendropdown"
          animation__visibleOut="property: material.opacity; to: 0; delay: 500; startEvents:closedropdown"
></a-image>

さらに、ボタンが押された時にアニメーションさせるためにスクリプトを追加します。

<script>
	window.closeDropdown = function() {
	    var mydropdown = document.getElementById("mydropdown");
            mydropdown.emit('closedropdown');
	    var myoptions = document.getElementById("myoptions");
            myoptions.emit('closedropdown');
            var myImage = document.getElementById("myImage");
            myImage.emit('closedropdown');
            var myImage2 = document.getElementById("myImage2");
            myImage2.emit('closedropdown');
            var myImage3 = document.getElementById("myImage3");
            myImage3.emit('closedropdown');
	 }
	window.openDropdown = function() {
	    var mydropdown = document.getElementById("mydropdown");
            mydropdown.emit('opendropdown');
	    var myoptions = document.getElementById("myoptions");
            myoptions.emit('opendropdown');
            var myImage = document.getElementById("myImage");
            myImage.emit('opendropdown');
            var myImage2 = document.getElementById("myImage2");
            myImage2.emit('opendropdown');
            var myImage3 = document.getElementById("myImage3");
            myImage3.emit('opendropdown');
         }
</script>

リンクを貼る

リンクのバグ(2020/11/01時点)

本来はa-linkというコンポーネントがあり、特にページ間の移動などがとても楽に作れます。
しかし、A-Frameの最新版(1.0.0以降?)でa-linkを使うと無限に遷移先と移動元のページをループするというバグがあり、実質使用できなくなっていました。
↓Issueが立てられています
github.com

なので今回は簡単なJavaScriptを記述しました。一応0.9以前を使えば動くのですが、そうすると今度はiPhoneで動かなくなるので・・・

ちなみに、a-entityにリンクを貼ることでも作れるのですが、それ自身のvisibleをfalseにしてもなぜかリンクを押せてしまうというバグがあります。恐らくa-linkのバグと同じ原因です。その為今回はこのような少し周りくどい方法をとっています。

リンクを貼る方法

さて、今回はボタンを作ってそれを押すことでリンク先へ飛ぶようにしています。ボタンは次のような感じです。

<a-gui-button
                id="button"
                link="URLを記述"
	        width="3" height="0.75"
                onclick="button"
  		value="Wikipediaへのリンク"
  		margin="0 0 0.05 0"
 ></a-gui-button>

そしてスクリプトを追加します。
今回はlinkというアトリビュートを作成し、そこにURLを格納することで任意のリンクにアクセスできるようになっています。

<script>
      window.button = function () {
      var button = document.getElementById("button");
      var url = button.getAttribute('link');
      window.location.href = url;
      }
</script>

ちなみに、次のようにすると別タブで開くことが可能なのですが、Safariだと無言でブロックされてしまい表示されません。Chromeでは警告が出た後に開くことができます。

window.open(url, '_blank')

出来たもの

こちら↓になります。
https://vibrant-goldberg-a095b0.netlify.app/

真ん中の画像はplaneに貼り付けたものなので背景が白くなっています。また、右側はopacityをアニメーションさせているもので、ゆっくり現れたり消えたりしています。