A Happy Holiday VR Card Experience!
By: Michael Mcanally
Tags: VR, Virtual Reality, A Frame, Webxr, Web Development

Happy Holidays All!

I wanted to give you an A-Frame Seasonal VR card experience.

I have snow again; tis the season. Will be updating some of my older stuff to include the Oculus Quest 2.

Try it out here:

Three source files.

HappyHolidays.html source:

<!DOCTYPE>

<html>

<head>

<meta charset="utf-8">

<title>Happy Holiday VR Card Experience!</title>

<meta name="description" content="A Season VR Card Experience. Created By Michael McAnally, December 5, 2018 using A-Frame. Updated December 6, 2020 for Medium article, Happy Holiday VR Card Experience!"></meta>

<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">

<meta name="mobile-web-app-capable" content="yes">

<meta name="apple-mobile-web-app-capable" content="yes" />

<meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />

<script src="

<script src="

-->

<!-- *** CHANGE THESE TO COMPONENTS ON YOUR SERVER *** -->

<script src="

<script src="aframe-environment-component-master/dist/aframe-environment-component.min.js"></script>

<script src="aframe-particle-system-component-master/dist/aframe-particle-system-component.min.js"></script>

<script src="aframe-extras-master/dist/aframe-extras.min.js"></script>

<script src="aframe-teleport-controls-master/dist/aframe-teleport-controls.js"></script>

<script src="superframe-master/components/thumb-controls/dist/aframe-thumb-controls-component.min.js"></script>

<script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>

<script src="js/info-message.js"></script> https://rocketvirtual.com/A-Frame_WebXR/aframe-master/dist/aframe-v1.0.4.min.js</a> "> <script type="text/javascript"> // Solves Google mute of audio problem AFRAME.registerComponent('audiohandler', {

init:function() {

let playing = false;

let audio = document.querySelector("#playAudio");

this.el.addEventListener('click', () => { if(!playing) {

audio.play();

} else {

audio.pause();

audio.currentTime = 0;

}

playing = !playing;

});

}

}) function playSound() { //alert("Sound playing functional!!!");

} </script> </head>

<body>

<button id="playButton" type="button">Play Music</button>

<!-- Jingle Bells Kevin MacLeod (incompetech.com)

Licensed under Creative Commons: By Attribution 3.0 License

-->

<source src="assets/mp3/JingleBells2.mp3" type="audio/mpeg"> </audio> <a-scene info-message="htmlSrc: #messageText"> <a-assets>

<a-asset-item id="xmas" src="assets/gltf/xmas.glb"></a-asset-item> <img crossorigin="anonymous" id="music-image_on" src="assets/img/music.png"> <!-- Setup color and fonts -->

<img crossorigin="anonymous" id="pink" src="assets/img/fontcolor.png" /> <!-- Our font -->

<a-asset-item id="optimerBoldFont" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item> <a-asset-item id="messageText" src="blogmessage.html" response-type="text"></a-asset-item> </a-assets> <a-entity environment="preset: starry;

ground: canyon;

groundYScale: 5;

groundColor: #FFFFFF;

shadow: true;

fog: 0;"></a-entity> <!-- Basic movement and teleportation -->

<a-entity id="cameraRig" movement-controls="constrainToNavMesh: false;" navigator="cameraRig: #cameraRig; cameraHead: #head; collisionEntities: .collision; ignoreEntities: .clickable" position="0 0 0" rotation="0 0 0" animation="property: position;

dur: 15000;

from: 0 32 55;

to: 0 1.6 0;

easing: easeInOutSine;

loop: false">

<!-- camera -->

<a-entity id="head" camera="active: true" position="0 1.6 0" look-controls="pointerLockEnabled: true; reverseMouseDrag: true" ></a-entity>

<!-- Left Controller -->

<a-entity id="leftHand" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; button: trigger; curveShootingSpeed: 18; landingMaxAngle: 60" visible="true"></a-entity>

<!-- Right Controller -->

<a-entity id="rightHand" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" laser-controls raycaster="showLine: true; far: 10; interval: 0; objects: .clickable, a-link;" line="color: #7cfc00; opacity: 0.5" visible="true"></a-entity>

</a-entity> <!-- It's Snowing!!! -->

<a-entity position="0 2.25 -15" particle-system="preset: snow; particleCount: 550"></a-entity> <!-- Snowman Xmas tree scene -->

<a-entity gltf-model="#xmas" scale="2.7 2.7 2.7" position="0 -.15 -6"></a-entity> <a-entity text-geometry="value:Happy Holidays!;size:5;style:italics;bevelEnabled:true;bevelSize:0;bevelThickness:1.389;curveSegments:2.15" material="color:lavenderblush;metalness:1;roughness:0;sphericalEnvMap: #pink" position="-5.45 6.54 5.3" scale="0.25 0.25 0.25" animation__rotate="property: rotation; dur: 20000;easing: linear; loop: true; to: 0 360 0" animation__second="property: position; dir: alternate; dur: 12000; easing: easeInSine; loop: true; from: -5.45 6.54 5.3; to: -5.45 16.54 5.3" begin="20000"></a-entity> <a-box id="playButton" class="clickable" position="-1.30 0.64 -6" material="src: #music-image_on" scale="1.25 1.25 0.25" onclick="playSound();" audiohandler="" geometry=""></a-box> <a-entity light="type:spot;intensity:0.42"></a-entity> <a-entity position="-1.2 2.94 3.03" light="intensity: 1.219; castShadow: true; shadowCameraLeft: -10; shadowCameraBottom: -10; shadowCameraRight: 10; shadowCameraTop: 10"></a-entity> </a-scene>

<script>

//Google Code for un-audio mute

// Existing code unchanged.

window.onload = function() {

var context = new AudioContext();

// Setup all nodes } // One-liner to resume playback when user interacted with the page.

document.querySelector('button').addEventListener('click', function() {

context.resume().then(() => {

console.log('Playback resumed successfully');

});

}); </script> </body>

</html>

blogmessage.html source (pop up message text):

This is a browser based virtual reality card experience. It works with most VR Headsets including the Oculus Quest 2. To learn more checkout my https://rocketvirtual.com/index.html " target="_blank">VR Blog and articles on https://michael-mcanally.medium.com/ " target="_blank">Medium. Happy Holidays! No VR, Desktop: Arrow keys in combination with dragging your mouse button move around. Exit by pressing ESC. Music box plays in VR only by laser pointer trigger.

js/info-message.js source (places pop up message on screen):

/* global AFRAME */

AFRAME.registerComponent('info-message', {

schema: {

htmlSrc: {type: 'selector'}

},

init: function () {

var sceneEl = this.el.sceneEl;

var messageEl = this.messageEl = document.createElement('div');

messageEl.classList.add('a-info-message');

sceneEl.setAttribute('aframe-injected', ''); var closeButtonEl = this.closeButtonEl = document.createElement('button');

closeButtonEl.innerHTML = 'X';

closeButtonEl.classList.add('a-close-button-info');

closeButtonEl.onclick = function () { messageEl.style.display = 'none'; }; this.addStyles();

sceneEl.appendChild(messageEl);

},

update: function () {

var messageEl = this.messageEl;

messageEl.innerHTML = this.data.htmlSrc.data;

messageEl.appendChild(this.closeButtonEl);

},

addStyles: function () {

var css =

'.a-info-message{border-radius: 10px; position: absolute; width: 400px;' +

'height: 250px; background-color: white; border: 3px solid rgba(0,0,0,.75);' +

'bottom: 22px; left: 22px; color: rgb(51, 51, 51); padding: 20px 15px 0 15px;' +

'font-size: 12pt; line-height: 20pt;}' + '.a-info-message a{border-bottom: 1px solid rgba(53,196,232,.15); color: #1497b8;' +

'position: relative; text-decoration: none; transition: .05s ease;}' +

'.a-info-message {left: 20px; right: 20px; bottom: 60px; width: auto}}' + @media only screen and (max-width: 600px) {' +'.a-info-message {left: 20px; right: 20px; bottom: 60px; width: auto}}' + '.a-close-button-info{width: 25px; height: 25px; padding: 0;' +

'top: 10px; right: 10px; position: absolute; color: white;' +

'font-size: 12px; line-height: 12px; border: none; background-color: #ef2d5e;' +

'border-radius: 5px; font-weight: medium}' + '.a-close-button-info:hover{background-color: #b32146; color: white}';

var style = document.createElement('style'); if (style.styleSheet) {

style.styleSheet.cssText = css;

} else {

style.appendChild(document.createTextNode(css));

} document.getElementsByTagName('head')[0].appendChild(style);

}

});

Download Link Holiday VR Card Assets (zip file of all the assests):

https://rocketvirtual.com/aframePACKAGE/HolidayAssets.zip

Other articles I have written about using A-Frame for VR (you can find out more about how this code works):

My VR Blog (further examples and commentary):