Skip to content

Instantly share code, notes, and snippets.

@ryanbetts
Created March 8, 2017 08:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanbetts/0cad8b658417a118acf984d809d8d456 to your computer and use it in GitHub Desktop.
Save ryanbetts/0cad8b658417a118acf984d809d8d456 to your computer and use it in GitHub Desktop.
Storyboard Template Storyboard Template // source http://jsbin.com/cakunu
<html>
<head>
<title>Storyboard Template</title>
<meta name="description" content="Storyboard Template">
<script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script>
<script src="https://rawgit.com/ngokevin/aframe-layout-component/master/dist/aframe-layout-component.min.js"></script>
</head>
<body>
<a-scene storyboard>
<!-- place your storyboard assets here -->
<a-assets>
<img id="frontSkyTexture" src="https://rawgit.com/ryanbetts/sketchy-vr/multi-frame/assets/front.jpg"/>
<img id="backSkyTexture" src="https://rawgit.com/ryanbetts/sketchy-vr/multi-frame/assets/back.jpg"/>
<img id="cheerioWaveTexture" src="https://rawgit.com/ryanbetts/sketchy-vr/multi-frame/assets/cheerio-wave.JPG"/>
</a-assets>
<!-- Frame 1 -->
<a-entity storyboard-frame="frame-1">
<a-sky height="2048" width="2048" radius="300" src="#cheerioWaveTexture" phi-length="180" theta-length="180"></a-sky>
<a-sky height="2048" width="2048" radius="300" src="#frontSkyTexture" phi-length="180" theta-length="180" rotation="0 180 0"></a-sky>
<!--
If you want an element to link to a specific frame when clicked,
give it a `storyboard-target-id` attribute
-->
<a-circle link-to-frame="frame-2" radius="1" color="tomato" opacity="0.4" position="-5.20 1.6 -3.80" rotation="0 65.89 0"></a-circle>
</a-entity>
<!-- Flipped version of frame 2 -->
<a-entity storyboard-frame="frame-2">
<a-sky height="2048" width="2048" radius="300" src="#frontSkyTexture" phi-length="180" theta-length="180"></a-sky>
<a-sky height="2048" width="2048" radius="300" src="#cheerioWaveTexture" phi-length="180" theta-length="180" rotation="0 180 0"></a-sky>
</a-entity>
<!-- Frame 3 -->
<a-entity storyboard-frame="frame-3">
<!--
If you want an element to link to a specific frame when clicked,
give it a `storyboard-target-id` attribute
-->
<a-box link-to-frame="frame-4" color="orange" position="0 0 -4"></a-box>
</a-entity>
<!-- Frame 4 -->
<a-entity storyboard-frame="frame-4">
<a-box position="0 0 -4" color="yellow"></a-box>
</a-entity>
<!-- Frame 5 -->
<a-entity storyboard-frame="frame-5">
<a-box position="0 0 -4" color="red"></a-box>
</a-entity>
<a-box position="0 2 -6" color="pink" id="persistent nav" link-to-frame="frame-1"></a-box>
</a-scene>
<script id="jsbin-javascript">
/* global AFRAME, THREE */
if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}
AFRAME.registerComponent('button', {
dependencies: ['position'],
schema: {
clickOffset: {
default: 0.05
},
ringColor: {
default: '#00B9FF'
}
},
init: function () {
this.el.getAttribute('position') ? '' : this.el.setAttribute('position', {x: 0, y:0, z:0})
this.el.addEventListener('click', this.onClick.bind(this));
this.el.addEventListener('mouseenter', this.onMouseEnter.bind(this))
this.el.addEventListener('mouseleave', this.onMouseLeave.bind(this))
this.enabled = true;
this.el.addEventListener('stateadded',this.onStateAdded.bind(this))
this.el.addState('button:enabled');
},
// Create or update the line geometry.
update: function () { },
// Remove the line geometry.
remove: function () {
this.el.removeEventListener('click', this.onClick);
this.el.removeEventListener('mouseenter', this.onMouseEnter);
this.el.removeEventListener('mouseleave', this.onMouseLeave);
},
produceClickRing: function (evt) {
var camera = document.querySelector('a-camera');
var rayDirection = evt.detail.cursorEl.components.raycaster.direction;
var clickDistance = evt.detail.intersection.distance;
var intersectionPoint = evt.detail.intersection.point;
var clickPosition = intersectionPoint.sub(rayDirection);
var clickRotation = camera.components.rotation.data;
var scene = document.querySelector('a-scene');
var clickRing = document.createElement('a-entity');
clickRing.id = 'clickring-'+new Date().getTime();
clickRing.setAttribute('material','color:'+this.data.ringColor);
clickRing.setAttribute('geometry','primitive: ring; radius-inner: 0.2; radius-outer:0.22');
clickRing.setAttribute('position',clickPosition);
clickRing.setAttribute('rotation',clickRotation);
new AFRAME.TWEEN.Tween({ scale: 0.8 })
.to({ scale: 1.3 },500)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
clickRing.setAttribute('scale',this.scale+' '+this.scale+' '+this.scale);
})
.start()
new AFRAME.TWEEN.Tween({ opacity: 1 })
.to({ opacity: 0.0 },500)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
clickRing.setAttribute('material','opacity',this.opacity);
})
.onComplete(function () {
scene.removeChild(scene.querySelector('#'+clickRing.id))
}.bind(this)).start()
scene.appendChild(clickRing);
},
animateButton: function (evt) {
if (this.animating) { return };
var rayDirection = evt.detail.cursorEl.components.raycaster.direction;
var from = Object.assign({},this.el.components.position.data);
var to = rayDirection.normalize()
.multiplyScalar(this.data.clickOffset)
.add(from);
var self = this;
new AFRAME.TWEEN.Tween(this.el.getAttribute('position'))
.to({ z: to.z, x: to.x, y: to.y },150)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
evt.target.setAttribute('position',this);
})
.onStart(function () {
self.animating = true;
})
.onComplete(function () {
new AFRAME.TWEEN.Tween(this.el.getAttribute('position'))
.to({ z: from.z, x: from.x, y: from.y },150)
.easing(AFRAME.TWEEN.Easing.Back.Out)
.onUpdate(function () {
evt.target.setAttribute('position',this);
})
.onComplete(function () {
self.animating = false;
}).start()
}.bind(this)).start()
},
onStateAdded: function (evt) {
if (evt.detail.state === 'button:enabled') {
this.enabled = true;
} else if (evt.detail.state === 'button:disabled') {
this.enabled = false;
}
},
onClick: function (evt) {
if (!this.enabled) return;
this.produceClickRing(evt);
this.animateButton(evt);
},
onMouseEnter: function (evt) {
if (!this.enabled) return;
this.el.emit('focus');
},
onMouseLeave: function (evt) {
this.el.emit('blur');
}
});
AFRAME.registerComponent('storyboard', {
schema: {
clickOffset: {
default: 0.05
},
ringColor: {
default: '#00B9FF'
}
},
init: function () {
console.log('storyboard-controls: init');
// init flags
this.uiVisible=false;
this.hideUITimeout=null;
this.storyboardFrames = [];
this.currStoryboardFrameIndex = null;
this.dotSize = 0.08;
this.dotMargin = 0.25;
this.arrowSize = 0.5;
this.dotsContainerWidth;
// construct UI
this.buildUI();
this.buildCamera();
},
tick: function () {
if (this.$camera && !this.uiVisible) {
var cameraY = this.$camera.getAttribute('rotation').y;
this.$uiContainer.setAttribute('rotation','0 '+cameraY+' 0');
}
},
buildUI: function () {
console.log('buildUI');
this.$uiContainer = document.createElement('a-entity');
this.el.appendChild(this.$uiContainer);
// create ui trigger
this.$uiTrigger = document.createElement('a-plane');
this.$uiTrigger.setAttribute('rotation','-36 0 0');
this.$uiTrigger.setAttribute('position','0 -0.5 -1.9');
this.$uiTrigger.setAttribute('width','4');
this.$uiTrigger.setAttribute('height','1.5');
this.$uiTrigger.setAttribute('material','color: yellow; visible: false');
this.$uiTrigger.setAttribute('class','highlightable');
this.$uiContainer.appendChild(this.$uiTrigger);
this.$uiTrigger.addEventListener('mouseenter', this.showUI.bind(this));
this.$uiTrigger.addEventListener('mouseleave', this.hideUI.bind(this));
// create nav container
this.$navigation = document.createElement('a-entity');
this.$navigation.setAttribute('rotation','-36 0 0');
this.$navigation.setAttribute('position','0 -0.5 -2');
this.$uiContainer.appendChild(this.$navigation);
// create right arrow
this.$rightArrow = document.createElement('a-circle');
this.$rightArrow.setAttribute('color','#00B9FF');
this.$rightArrow.setAttribute('rotation','0 -25 0');
this.$rightArrow.setAttribute('radius','0.25');
this.$rightArrow.setAttribute('class','clickable');
this.$rightArrow.setAttribute('button','');
this.$rightArrow.setAttribute('transparent','true');
this.$rightArrow.innerHTML='<a-text scale="0.8 0.8 0.8" value="next" align="center"></a-text>';
this.$navigation.appendChild(this.$rightArrow);
this.$rightArrow.addEventListener('click', this.handleRightArrowClick.bind(this));
// create left arrow
this.$leftArrow = document.createElement('a-circle');
this.$leftArrow.setAttribute('color','#00B9FF');
this.$leftArrow.setAttribute('rotation','0 25 0');
this.$leftArrow.setAttribute('radius','0.25');
this.$leftArrow.setAttribute('class','clickable');
this.$leftArrow.setAttribute('button','');
this.$leftArrow.setAttribute('transparent','true');
this.$leftArrow.innerHTML='<a-text scale="0.8 0.8 0.8" value="prev" align="center"></a-text>';
this.$navigation.appendChild(this.$leftArrow);
this.$leftArrow.addEventListener('click', this.handleLeftArrowClick.bind(this));
// create pagination container
this.$paginationContainer = document.createElement('a-entity');
this.$navigation.appendChild(this.$paginationContainer);
// create pagination bg
this.$paginationBG = document.createElement('a-plane');
this.$paginationBG.setAttribute('color','#333');
this.$paginationBG.setAttribute('width','2.5');
this.$paginationBG.setAttribute('height','0.5');
this.$paginationContainer.appendChild(this.$paginationBG);
// create pagination highlight
this.$paginationHighlight = document.createElement('a-ring');
this.$paginationHighlight.setAttribute('color','#00B9FF');
this.$paginationHighlight.setAttribute('radius-inner','0.08');
this.$paginationHighlight.setAttribute('radius-outer','0.12');
this.$paginationContainer.appendChild(this.$paginationHighlight);
// create pagination dots
this.$paginationDots = document.createElement('a-entity');
this.$paginationContainer.appendChild(this.$paginationDots);
// resize the navigation and initialize it to the first frame
this.armStoryboardButtons();
this.updateNavigation();
this.navigateToIndex(0);
},
buildCamera: function () {
console.log('build camera');
// create camera
this.$camera=document.createElement('a-camera');
this.el.appendChild(this.$camera);
// create clickable cursor
this.$cursor=document.createElement('a-entity');
this.$cursor.setAttribute('cursor','fuse: false');
this.$cursor.setAttribute('geometry','primitive: ring');
this.$cursor.setAttribute('material','color: #FF00E6; shader: flat; opacity: 0.5');
this.$cursor.setAttribute('raycaster','objects: [link-to-frame], .clickable');
this.$cursor.setAttribute('position','0 0 -2');
this.$cursor.setAttribute('scale','0.025 0.025 0.025');
this.$cursor.innerHTML='<a-animation begin="mouseenter" attribute="material.opacity" dur="150" from="0.5" to="1.0"></a-animation>'+
'<a-animation begin="mouseenter" easing="ease-in-out" attribute="scale" from="0.025 0.025 0.025" to="0.05 0.05 0.05" dur="150"></a-animation>'+
'<a-animation begin="mouseleave" easing="ease-out" attribute="material.opacity" from="1.0" to="0.1" dur="75"></a-animation>'+
'<a-animation begin="mouseleave" easing="ease-in-out" attribute="scale" from="0.05 0.05 0.05" to="0.025 0.025 0.025" dur="150"></a-animation>';
this.$camera.appendChild(this.$cursor);
this.$cursorHighlightable=document.createElement('a-entity');
this.$cursorHighlightable.setAttribute('cursor','fuse: false');
this.$cursorHighlightable.setAttribute('material','visible: false;');
this.$cursorHighlightable.setAttribute('raycaster','objects: .highlightable');
this.$camera.appendChild(this.$cursorHighlightable);
},
showUI: function() {
console.log('show UI');
this.uiVisible = true;
clearTimeout(this.hideUITimeout);
this.$navigation.setAttribute('scale','1 1 1');
},
hideUI: function () {
console.log('hide UI');
var that = this;
this.hideUITimeout = setTimeout(function() {
that.$navigation.setAttribute('scale','0 0 0');
that.uiVisible = false;
}, 3000);
},
handleRightArrowClick: function () {
if (this.currStoryboardFrameIndex<(this.storyboardFrames.length-1)) {
this.navigateToIndex(this.currStoryboardFrameIndex+1);
} else {
this.navigateToIndex(0);
}
},
handleLeftArrowClick: function () {
if (this.currStoryboardFrameIndex>0) {
this.navigateToIndex(this.currStoryboardFrameIndex-1);
} else {
this.navigateToIndex(this.storyboardFrames.length-1);
}
},
armStoryboardButtons: function () {
var storyboardButtons = document.querySelectorAll('[link-to-frame]');
for (var i=0; i<storyboardButtons.length; i++) {
var button = storyboardButtons[i];
// button.removeEventListener('click');
button.addEventListener('click', function () {
this.navigateToId(button.getAttribute('link-to-frame'));
}.bind(this));
}
},
navigateToIndex: function (targetIndex) {
if (this.currStoryboardFrameIndex != null) {
this.el.removeChild(this.storyboardFrames[this.currStoryboardFrameIndex]);
}
this.el.appendChild(this.storyboardFrames[targetIndex]);
// this.armStoryboardButtons();
this.currStoryboardFrameIndex = targetIndex;
// move the pagination highlight
var highlightX = -this.dotsContainerWidth/2 + this.currStoryboardFrameIndex*this.dotMargin;
this.$paginationHighlight.setAttribute('position', highlightX +' 0 0')
},
navigateToId: function (id) {
var index;
for (var i=0; i<this.storyboardFrames.length; i++ ) {
if (id == this.storyboardFrames[i].getAttribute('storyboard-frame')) {
index = i;
}
}
this.navigateToIndex(index);
},
updateNavigation: function () {
this.storyboardFrames = document.querySelectorAll('[storyboard-frame]');
// initialize the frames
for (var i = 0; i<this.storyboardFrames.length; i++) {
var frame = this.storyboardFrames[i];
this.el.removeChild(frame);
var dot = document.createElement('a-circle');
dot.setAttribute('color','#FFFFFF');
dot.setAttribute('scale',this.dotSize+' '+this.dotSize+' '+this.dotSize);
dot.setAttribute('link-to-frame',frame.getAttribute('storyboard-frame'));
var that = this;
dot.addEventListener('click', function (evt) {
that.navigateToId(evt.target.getAttribute('link-to-frame'));
});
this.$paginationDots.appendChild(dot);
}
this.dotsContainerWidth = this.dotMargin*(this.storyboardFrames.length-1);
this.$paginationDots.setAttribute('position', -.5*this.dotsContainerWidth+' 0 0');
this.$paginationDots.setAttribute('layout','type: line; margin: '+this.dotMargin);
this.$paginationBG.setAttribute('width',this.dotsContainerWidth+this.dotSize*4);
this.$paginationBG.setAttribute('position','0 0 -.1');
this.$leftArrow.setAttribute('position',(-this.dotsContainerWidth/2-this.arrowSize)+' 0 0');
this.$rightArrow.setAttribute('position',(this.dotsContainerWidth/2+this.arrowSize)+' 0 0');
}
});
</script>
<script id="jsbin-source-html" type="text/html">
<html>
<head>
<title>Storyboard Template</title>
<meta name="description" content="Storyboard Template">
<script src="https://aframe.io/releases/0.5.0/aframe.min.js"><\/script>
<script src="https://rawgit.com/ngokevin/aframe-layout-component/master/dist/aframe-layout-component.min.js"><\/script>
</head>
<body>
<a-scene storyboard>
<\!-- place your storyboard assets here -->
<a-assets>
<img id="frontSkyTexture" src="https://rawgit.com/ryanbetts/sketchy-vr/multi-frame/assets/front.jpg"/>
<img id="backSkyTexture" src="https://rawgit.com/ryanbetts/sketchy-vr/multi-frame/assets/back.jpg"/>
<img id="cheerioWaveTexture" src="https://rawgit.com/ryanbetts/sketchy-vr/multi-frame/assets/cheerio-wave.JPG"/>
</a-assets>
<\!-- Frame 1 -->
<a-entity storyboard-frame="frame-1">
<a-sky height="2048" width="2048" radius="300" src="#cheerioWaveTexture" phi-length="180" theta-length="180"></a-sky>
<a-sky height="2048" width="2048" radius="300" src="#frontSkyTexture" phi-length="180" theta-length="180" rotation="0 180 0"></a-sky>
<\!--
If you want an element to link to a specific frame when clicked,
give it a `storyboard-target-id` attribute
-->
<a-circle link-to-frame="frame-2" radius="1" color="tomato" opacity="0.4" position="-5.20 1.6 -3.80" rotation="0 65.89 0"></a-circle>
</a-entity>
<\!-- Flipped version of frame 2 -->
<a-entity storyboard-frame="frame-2">
<a-sky height="2048" width="2048" radius="300" src="#frontSkyTexture" phi-length="180" theta-length="180"></a-sky>
<a-sky height="2048" width="2048" radius="300" src="#cheerioWaveTexture" phi-length="180" theta-length="180" rotation="0 180 0"></a-sky>
</a-entity>
<\!-- Frame 3 -->
<a-entity storyboard-frame="frame-3">
<\!--
If you want an element to link to a specific frame when clicked,
give it a `storyboard-target-id` attribute
-->
<a-box link-to-frame="frame-4" color="orange" position="0 0 -4"></a-box>
</a-entity>
<\!-- Frame 4 -->
<a-entity storyboard-frame="frame-4">
<a-box position="0 0 -4" color="yellow"></a-box>
</a-entity>
<\!-- Frame 5 -->
<a-entity storyboard-frame="frame-5">
<a-box position="0 0 -4" color="red"></a-box>
</a-entity>
<a-box position="0 2 -6" color="pink" id="persistent nav" link-to-frame="frame-1"></a-box>
</a-scene>
</body>
</html>
</script>
<script id="jsbin-source-javascript" type="text/javascript">/* global AFRAME, THREE */
if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}
AFRAME.registerComponent('button', {
dependencies: ['position'],
schema: {
clickOffset: {
default: 0.05
},
ringColor: {
default: '#00B9FF'
}
},
init: function () {
this.el.getAttribute('position') ? '' : this.el.setAttribute('position', {x: 0, y:0, z:0})
this.el.addEventListener('click', this.onClick.bind(this));
this.el.addEventListener('mouseenter', this.onMouseEnter.bind(this))
this.el.addEventListener('mouseleave', this.onMouseLeave.bind(this))
this.enabled = true;
this.el.addEventListener('stateadded',this.onStateAdded.bind(this))
this.el.addState('button:enabled');
},
// Create or update the line geometry.
update: function () { },
// Remove the line geometry.
remove: function () {
this.el.removeEventListener('click', this.onClick);
this.el.removeEventListener('mouseenter', this.onMouseEnter);
this.el.removeEventListener('mouseleave', this.onMouseLeave);
},
produceClickRing: function (evt) {
var camera = document.querySelector('a-camera');
var rayDirection = evt.detail.cursorEl.components.raycaster.direction;
var clickDistance = evt.detail.intersection.distance;
var intersectionPoint = evt.detail.intersection.point;
var clickPosition = intersectionPoint.sub(rayDirection);
var clickRotation = camera.components.rotation.data;
var scene = document.querySelector('a-scene');
var clickRing = document.createElement('a-entity');
clickRing.id = 'clickring-'+new Date().getTime();
clickRing.setAttribute('material','color:'+this.data.ringColor);
clickRing.setAttribute('geometry','primitive: ring; radius-inner: 0.2; radius-outer:0.22');
clickRing.setAttribute('position',clickPosition);
clickRing.setAttribute('rotation',clickRotation);
new AFRAME.TWEEN.Tween({ scale: 0.8 })
.to({ scale: 1.3 },500)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
clickRing.setAttribute('scale',this.scale+' '+this.scale+' '+this.scale);
})
.start()
new AFRAME.TWEEN.Tween({ opacity: 1 })
.to({ opacity: 0.0 },500)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
clickRing.setAttribute('material','opacity',this.opacity);
})
.onComplete(function () {
scene.removeChild(scene.querySelector('#'+clickRing.id))
}.bind(this)).start()
scene.appendChild(clickRing);
},
animateButton: function (evt) {
if (this.animating) { return };
var rayDirection = evt.detail.cursorEl.components.raycaster.direction;
var from = Object.assign({},this.el.components.position.data);
var to = rayDirection.normalize()
.multiplyScalar(this.data.clickOffset)
.add(from);
var self = this;
new AFRAME.TWEEN.Tween(this.el.getAttribute('position'))
.to({ z: to.z, x: to.x, y: to.y },150)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
evt.target.setAttribute('position',this);
})
.onStart(function () {
self.animating = true;
})
.onComplete(function () {
new AFRAME.TWEEN.Tween(this.el.getAttribute('position'))
.to({ z: from.z, x: from.x, y: from.y },150)
.easing(AFRAME.TWEEN.Easing.Back.Out)
.onUpdate(function () {
evt.target.setAttribute('position',this);
})
.onComplete(function () {
self.animating = false;
}).start()
}.bind(this)).start()
},
onStateAdded: function (evt) {
if (evt.detail.state === 'button:enabled') {
this.enabled = true;
} else if (evt.detail.state === 'button:disabled') {
this.enabled = false;
}
},
onClick: function (evt) {
if (!this.enabled) return;
this.produceClickRing(evt);
this.animateButton(evt);
},
onMouseEnter: function (evt) {
if (!this.enabled) return;
this.el.emit('focus');
},
onMouseLeave: function (evt) {
this.el.emit('blur');
}
});
AFRAME.registerComponent('storyboard', {
schema: {
clickOffset: {
default: 0.05
},
ringColor: {
default: '#00B9FF'
}
},
init: function () {
console.log('storyboard-controls: init');
// init flags
this.uiVisible=false;
this.hideUITimeout=null;
this.storyboardFrames = [];
this.currStoryboardFrameIndex = null;
this.dotSize = 0.08;
this.dotMargin = 0.25;
this.arrowSize = 0.5;
this.dotsContainerWidth;
// construct UI
this.buildUI();
this.buildCamera();
},
tick: function () {
if (this.$camera && !this.uiVisible) {
var cameraY = this.$camera.getAttribute('rotation').y;
this.$uiContainer.setAttribute('rotation','0 '+cameraY+' 0');
}
},
buildUI: function () {
console.log('buildUI');
this.$uiContainer = document.createElement('a-entity');
this.el.appendChild(this.$uiContainer);
// create ui trigger
this.$uiTrigger = document.createElement('a-plane');
this.$uiTrigger.setAttribute('rotation','-36 0 0');
this.$uiTrigger.setAttribute('position','0 -0.5 -1.9');
this.$uiTrigger.setAttribute('width','4');
this.$uiTrigger.setAttribute('height','1.5');
this.$uiTrigger.setAttribute('material','color: yellow; visible: false');
this.$uiTrigger.setAttribute('class','highlightable');
this.$uiContainer.appendChild(this.$uiTrigger);
this.$uiTrigger.addEventListener('mouseenter', this.showUI.bind(this));
this.$uiTrigger.addEventListener('mouseleave', this.hideUI.bind(this));
// create nav container
this.$navigation = document.createElement('a-entity');
this.$navigation.setAttribute('rotation','-36 0 0');
this.$navigation.setAttribute('position','0 -0.5 -2');
this.$uiContainer.appendChild(this.$navigation);
// create right arrow
this.$rightArrow = document.createElement('a-circle');
this.$rightArrow.setAttribute('color','#00B9FF');
this.$rightArrow.setAttribute('rotation','0 -25 0');
this.$rightArrow.setAttribute('radius','0.25');
this.$rightArrow.setAttribute('class','clickable');
this.$rightArrow.setAttribute('button','');
this.$rightArrow.setAttribute('transparent','true');
this.$rightArrow.innerHTML='<a-text scale="0.8 0.8 0.8" value="next" align="center"></a-text>';
this.$navigation.appendChild(this.$rightArrow);
this.$rightArrow.addEventListener('click', this.handleRightArrowClick.bind(this));
// create left arrow
this.$leftArrow = document.createElement('a-circle');
this.$leftArrow.setAttribute('color','#00B9FF');
this.$leftArrow.setAttribute('rotation','0 25 0');
this.$leftArrow.setAttribute('radius','0.25');
this.$leftArrow.setAttribute('class','clickable');
this.$leftArrow.setAttribute('button','');
this.$leftArrow.setAttribute('transparent','true');
this.$leftArrow.innerHTML='<a-text scale="0.8 0.8 0.8" value="prev" align="center"></a-text>';
this.$navigation.appendChild(this.$leftArrow);
this.$leftArrow.addEventListener('click', this.handleLeftArrowClick.bind(this));
// create pagination container
this.$paginationContainer = document.createElement('a-entity');
this.$navigation.appendChild(this.$paginationContainer);
// create pagination bg
this.$paginationBG = document.createElement('a-plane');
this.$paginationBG.setAttribute('color','#333');
this.$paginationBG.setAttribute('width','2.5');
this.$paginationBG.setAttribute('height','0.5');
this.$paginationContainer.appendChild(this.$paginationBG);
// create pagination highlight
this.$paginationHighlight = document.createElement('a-ring');
this.$paginationHighlight.setAttribute('color','#00B9FF');
this.$paginationHighlight.setAttribute('radius-inner','0.08');
this.$paginationHighlight.setAttribute('radius-outer','0.12');
this.$paginationContainer.appendChild(this.$paginationHighlight);
// create pagination dots
this.$paginationDots = document.createElement('a-entity');
this.$paginationContainer.appendChild(this.$paginationDots);
// resize the navigation and initialize it to the first frame
this.armStoryboardButtons();
this.updateNavigation();
this.navigateToIndex(0);
},
buildCamera: function () {
console.log('build camera');
// create camera
this.$camera=document.createElement('a-camera');
this.el.appendChild(this.$camera);
// create clickable cursor
this.$cursor=document.createElement('a-entity');
this.$cursor.setAttribute('cursor','fuse: false');
this.$cursor.setAttribute('geometry','primitive: ring');
this.$cursor.setAttribute('material','color: #FF00E6; shader: flat; opacity: 0.5');
this.$cursor.setAttribute('raycaster','objects: [link-to-frame], .clickable');
this.$cursor.setAttribute('position','0 0 -2');
this.$cursor.setAttribute('scale','0.025 0.025 0.025');
this.$cursor.innerHTML='<a-animation begin="mouseenter" attribute="material.opacity" dur="150" from="0.5" to="1.0"></a-animation>'+
'<a-animation begin="mouseenter" easing="ease-in-out" attribute="scale" from="0.025 0.025 0.025" to="0.05 0.05 0.05" dur="150"></a-animation>'+
'<a-animation begin="mouseleave" easing="ease-out" attribute="material.opacity" from="1.0" to="0.1" dur="75"></a-animation>'+
'<a-animation begin="mouseleave" easing="ease-in-out" attribute="scale" from="0.05 0.05 0.05" to="0.025 0.025 0.025" dur="150"></a-animation>';
this.$camera.appendChild(this.$cursor);
this.$cursorHighlightable=document.createElement('a-entity');
this.$cursorHighlightable.setAttribute('cursor','fuse: false');
this.$cursorHighlightable.setAttribute('material','visible: false;');
this.$cursorHighlightable.setAttribute('raycaster','objects: .highlightable');
this.$camera.appendChild(this.$cursorHighlightable);
},
showUI: function() {
console.log('show UI');
this.uiVisible = true;
clearTimeout(this.hideUITimeout);
this.$navigation.setAttribute('scale','1 1 1');
},
hideUI: function () {
console.log('hide UI');
var that = this;
this.hideUITimeout = setTimeout(function() {
that.$navigation.setAttribute('scale','0 0 0');
that.uiVisible = false;
}, 3000);
},
handleRightArrowClick: function () {
if (this.currStoryboardFrameIndex<(this.storyboardFrames.length-1)) {
this.navigateToIndex(this.currStoryboardFrameIndex+1);
} else {
this.navigateToIndex(0);
}
},
handleLeftArrowClick: function () {
if (this.currStoryboardFrameIndex>0) {
this.navigateToIndex(this.currStoryboardFrameIndex-1);
} else {
this.navigateToIndex(this.storyboardFrames.length-1);
}
},
armStoryboardButtons: function () {
var storyboardButtons = document.querySelectorAll('[link-to-frame]');
for (var i=0; i<storyboardButtons.length; i++) {
var button = storyboardButtons[i];
// button.removeEventListener('click');
button.addEventListener('click', function () {
this.navigateToId(button.getAttribute('link-to-frame'));
}.bind(this));
}
},
navigateToIndex: function (targetIndex) {
if (this.currStoryboardFrameIndex != null) {
this.el.removeChild(this.storyboardFrames[this.currStoryboardFrameIndex]);
}
this.el.appendChild(this.storyboardFrames[targetIndex]);
// this.armStoryboardButtons();
this.currStoryboardFrameIndex = targetIndex;
// move the pagination highlight
var highlightX = -this.dotsContainerWidth/2 + this.currStoryboardFrameIndex*this.dotMargin;
this.$paginationHighlight.setAttribute('position', highlightX +' 0 0')
},
navigateToId: function (id) {
var index;
for (var i=0; i<this.storyboardFrames.length; i++ ) {
if (id == this.storyboardFrames[i].getAttribute('storyboard-frame')) {
index = i;
}
}
this.navigateToIndex(index);
},
updateNavigation: function () {
this.storyboardFrames = document.querySelectorAll('[storyboard-frame]');
// initialize the frames
for (var i = 0; i<this.storyboardFrames.length; i++) {
var frame = this.storyboardFrames[i];
this.el.removeChild(frame);
var dot = document.createElement('a-circle');
dot.setAttribute('color','#FFFFFF');
dot.setAttribute('scale',this.dotSize+' '+this.dotSize+' '+this.dotSize);
dot.setAttribute('link-to-frame',frame.getAttribute('storyboard-frame'));
var that = this;
dot.addEventListener('click', function (evt) {
that.navigateToId(evt.target.getAttribute('link-to-frame'));
});
this.$paginationDots.appendChild(dot);
}
this.dotsContainerWidth = this.dotMargin*(this.storyboardFrames.length-1);
this.$paginationDots.setAttribute('position', -.5*this.dotsContainerWidth+' 0 0');
this.$paginationDots.setAttribute('layout','type: line; margin: '+this.dotMargin);
this.$paginationBG.setAttribute('width',this.dotsContainerWidth+this.dotSize*4);
this.$paginationBG.setAttribute('position','0 0 -.1');
this.$leftArrow.setAttribute('position',(-this.dotsContainerWidth/2-this.arrowSize)+' 0 0');
this.$rightArrow.setAttribute('position',(this.dotsContainerWidth/2+this.arrowSize)+' 0 0');
}
});</script></body>
</html>
/* global AFRAME, THREE */
if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}
AFRAME.registerComponent('button', {
dependencies: ['position'],
schema: {
clickOffset: {
default: 0.05
},
ringColor: {
default: '#00B9FF'
}
},
init: function () {
this.el.getAttribute('position') ? '' : this.el.setAttribute('position', {x: 0, y:0, z:0})
this.el.addEventListener('click', this.onClick.bind(this));
this.el.addEventListener('mouseenter', this.onMouseEnter.bind(this))
this.el.addEventListener('mouseleave', this.onMouseLeave.bind(this))
this.enabled = true;
this.el.addEventListener('stateadded',this.onStateAdded.bind(this))
this.el.addState('button:enabled');
},
// Create or update the line geometry.
update: function () { },
// Remove the line geometry.
remove: function () {
this.el.removeEventListener('click', this.onClick);
this.el.removeEventListener('mouseenter', this.onMouseEnter);
this.el.removeEventListener('mouseleave', this.onMouseLeave);
},
produceClickRing: function (evt) {
var camera = document.querySelector('a-camera');
var rayDirection = evt.detail.cursorEl.components.raycaster.direction;
var clickDistance = evt.detail.intersection.distance;
var intersectionPoint = evt.detail.intersection.point;
var clickPosition = intersectionPoint.sub(rayDirection);
var clickRotation = camera.components.rotation.data;
var scene = document.querySelector('a-scene');
var clickRing = document.createElement('a-entity');
clickRing.id = 'clickring-'+new Date().getTime();
clickRing.setAttribute('material','color:'+this.data.ringColor);
clickRing.setAttribute('geometry','primitive: ring; radius-inner: 0.2; radius-outer:0.22');
clickRing.setAttribute('position',clickPosition);
clickRing.setAttribute('rotation',clickRotation);
new AFRAME.TWEEN.Tween({ scale: 0.8 })
.to({ scale: 1.3 },500)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
clickRing.setAttribute('scale',this.scale+' '+this.scale+' '+this.scale);
})
.start()
new AFRAME.TWEEN.Tween({ opacity: 1 })
.to({ opacity: 0.0 },500)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
clickRing.setAttribute('material','opacity',this.opacity);
})
.onComplete(function () {
scene.removeChild(scene.querySelector('#'+clickRing.id))
}.bind(this)).start()
scene.appendChild(clickRing);
},
animateButton: function (evt) {
if (this.animating) { return };
var rayDirection = evt.detail.cursorEl.components.raycaster.direction;
var from = Object.assign({},this.el.components.position.data);
var to = rayDirection.normalize()
.multiplyScalar(this.data.clickOffset)
.add(from);
var self = this;
new AFRAME.TWEEN.Tween(this.el.getAttribute('position'))
.to({ z: to.z, x: to.x, y: to.y },150)
.easing(AFRAME.TWEEN.Easing.Quadratic.Out)
.onUpdate(function () {
evt.target.setAttribute('position',this);
})
.onStart(function () {
self.animating = true;
})
.onComplete(function () {
new AFRAME.TWEEN.Tween(this.el.getAttribute('position'))
.to({ z: from.z, x: from.x, y: from.y },150)
.easing(AFRAME.TWEEN.Easing.Back.Out)
.onUpdate(function () {
evt.target.setAttribute('position',this);
})
.onComplete(function () {
self.animating = false;
}).start()
}.bind(this)).start()
},
onStateAdded: function (evt) {
if (evt.detail.state === 'button:enabled') {
this.enabled = true;
} else if (evt.detail.state === 'button:disabled') {
this.enabled = false;
}
},
onClick: function (evt) {
if (!this.enabled) return;
this.produceClickRing(evt);
this.animateButton(evt);
},
onMouseEnter: function (evt) {
if (!this.enabled) return;
this.el.emit('focus');
},
onMouseLeave: function (evt) {
this.el.emit('blur');
}
});
AFRAME.registerComponent('storyboard', {
schema: {
clickOffset: {
default: 0.05
},
ringColor: {
default: '#00B9FF'
}
},
init: function () {
console.log('storyboard-controls: init');
// init flags
this.uiVisible=false;
this.hideUITimeout=null;
this.storyboardFrames = [];
this.currStoryboardFrameIndex = null;
this.dotSize = 0.08;
this.dotMargin = 0.25;
this.arrowSize = 0.5;
this.dotsContainerWidth;
// construct UI
this.buildUI();
this.buildCamera();
},
tick: function () {
if (this.$camera && !this.uiVisible) {
var cameraY = this.$camera.getAttribute('rotation').y;
this.$uiContainer.setAttribute('rotation','0 '+cameraY+' 0');
}
},
buildUI: function () {
console.log('buildUI');
this.$uiContainer = document.createElement('a-entity');
this.el.appendChild(this.$uiContainer);
// create ui trigger
this.$uiTrigger = document.createElement('a-plane');
this.$uiTrigger.setAttribute('rotation','-36 0 0');
this.$uiTrigger.setAttribute('position','0 -0.5 -1.9');
this.$uiTrigger.setAttribute('width','4');
this.$uiTrigger.setAttribute('height','1.5');
this.$uiTrigger.setAttribute('material','color: yellow; visible: false');
this.$uiTrigger.setAttribute('class','highlightable');
this.$uiContainer.appendChild(this.$uiTrigger);
this.$uiTrigger.addEventListener('mouseenter', this.showUI.bind(this));
this.$uiTrigger.addEventListener('mouseleave', this.hideUI.bind(this));
// create nav container
this.$navigation = document.createElement('a-entity');
this.$navigation.setAttribute('rotation','-36 0 0');
this.$navigation.setAttribute('position','0 -0.5 -2');
this.$uiContainer.appendChild(this.$navigation);
// create right arrow
this.$rightArrow = document.createElement('a-circle');
this.$rightArrow.setAttribute('color','#00B9FF');
this.$rightArrow.setAttribute('rotation','0 -25 0');
this.$rightArrow.setAttribute('radius','0.25');
this.$rightArrow.setAttribute('class','clickable');
this.$rightArrow.setAttribute('button','');
this.$rightArrow.setAttribute('transparent','true');
this.$rightArrow.innerHTML='<a-text scale="0.8 0.8 0.8" value="next" align="center"></a-text>';
this.$navigation.appendChild(this.$rightArrow);
this.$rightArrow.addEventListener('click', this.handleRightArrowClick.bind(this));
// create left arrow
this.$leftArrow = document.createElement('a-circle');
this.$leftArrow.setAttribute('color','#00B9FF');
this.$leftArrow.setAttribute('rotation','0 25 0');
this.$leftArrow.setAttribute('radius','0.25');
this.$leftArrow.setAttribute('class','clickable');
this.$leftArrow.setAttribute('button','');
this.$leftArrow.setAttribute('transparent','true');
this.$leftArrow.innerHTML='<a-text scale="0.8 0.8 0.8" value="prev" align="center"></a-text>';
this.$navigation.appendChild(this.$leftArrow);
this.$leftArrow.addEventListener('click', this.handleLeftArrowClick.bind(this));
// create pagination container
this.$paginationContainer = document.createElement('a-entity');
this.$navigation.appendChild(this.$paginationContainer);
// create pagination bg
this.$paginationBG = document.createElement('a-plane');
this.$paginationBG.setAttribute('color','#333');
this.$paginationBG.setAttribute('width','2.5');
this.$paginationBG.setAttribute('height','0.5');
this.$paginationContainer.appendChild(this.$paginationBG);
// create pagination highlight
this.$paginationHighlight = document.createElement('a-ring');
this.$paginationHighlight.setAttribute('color','#00B9FF');
this.$paginationHighlight.setAttribute('radius-inner','0.08');
this.$paginationHighlight.setAttribute('radius-outer','0.12');
this.$paginationContainer.appendChild(this.$paginationHighlight);
// create pagination dots
this.$paginationDots = document.createElement('a-entity');
this.$paginationContainer.appendChild(this.$paginationDots);
// resize the navigation and initialize it to the first frame
this.armStoryboardButtons();
this.updateNavigation();
this.navigateToIndex(0);
},
buildCamera: function () {
console.log('build camera');
// create camera
this.$camera=document.createElement('a-camera');
this.el.appendChild(this.$camera);
// create clickable cursor
this.$cursor=document.createElement('a-entity');
this.$cursor.setAttribute('cursor','fuse: false');
this.$cursor.setAttribute('geometry','primitive: ring');
this.$cursor.setAttribute('material','color: #FF00E6; shader: flat; opacity: 0.5');
this.$cursor.setAttribute('raycaster','objects: [link-to-frame], .clickable');
this.$cursor.setAttribute('position','0 0 -2');
this.$cursor.setAttribute('scale','0.025 0.025 0.025');
this.$cursor.innerHTML='<a-animation begin="mouseenter" attribute="material.opacity" dur="150" from="0.5" to="1.0"></a-animation>'+
'<a-animation begin="mouseenter" easing="ease-in-out" attribute="scale" from="0.025 0.025 0.025" to="0.05 0.05 0.05" dur="150"></a-animation>'+
'<a-animation begin="mouseleave" easing="ease-out" attribute="material.opacity" from="1.0" to="0.1" dur="75"></a-animation>'+
'<a-animation begin="mouseleave" easing="ease-in-out" attribute="scale" from="0.05 0.05 0.05" to="0.025 0.025 0.025" dur="150"></a-animation>';
this.$camera.appendChild(this.$cursor);
this.$cursorHighlightable=document.createElement('a-entity');
this.$cursorHighlightable.setAttribute('cursor','fuse: false');
this.$cursorHighlightable.setAttribute('material','visible: false;');
this.$cursorHighlightable.setAttribute('raycaster','objects: .highlightable');
this.$camera.appendChild(this.$cursorHighlightable);
},
showUI: function() {
console.log('show UI');
this.uiVisible = true;
clearTimeout(this.hideUITimeout);
this.$navigation.setAttribute('scale','1 1 1');
},
hideUI: function () {
console.log('hide UI');
var that = this;
this.hideUITimeout = setTimeout(function() {
that.$navigation.setAttribute('scale','0 0 0');
that.uiVisible = false;
}, 3000);
},
handleRightArrowClick: function () {
if (this.currStoryboardFrameIndex<(this.storyboardFrames.length-1)) {
this.navigateToIndex(this.currStoryboardFrameIndex+1);
} else {
this.navigateToIndex(0);
}
},
handleLeftArrowClick: function () {
if (this.currStoryboardFrameIndex>0) {
this.navigateToIndex(this.currStoryboardFrameIndex-1);
} else {
this.navigateToIndex(this.storyboardFrames.length-1);
}
},
armStoryboardButtons: function () {
var storyboardButtons = document.querySelectorAll('[link-to-frame]');
for (var i=0; i<storyboardButtons.length; i++) {
var button = storyboardButtons[i];
// button.removeEventListener('click');
button.addEventListener('click', function () {
this.navigateToId(button.getAttribute('link-to-frame'));
}.bind(this));
}
},
navigateToIndex: function (targetIndex) {
if (this.currStoryboardFrameIndex != null) {
this.el.removeChild(this.storyboardFrames[this.currStoryboardFrameIndex]);
}
this.el.appendChild(this.storyboardFrames[targetIndex]);
// this.armStoryboardButtons();
this.currStoryboardFrameIndex = targetIndex;
// move the pagination highlight
var highlightX = -this.dotsContainerWidth/2 + this.currStoryboardFrameIndex*this.dotMargin;
this.$paginationHighlight.setAttribute('position', highlightX +' 0 0')
},
navigateToId: function (id) {
var index;
for (var i=0; i<this.storyboardFrames.length; i++ ) {
if (id == this.storyboardFrames[i].getAttribute('storyboard-frame')) {
index = i;
}
}
this.navigateToIndex(index);
},
updateNavigation: function () {
this.storyboardFrames = document.querySelectorAll('[storyboard-frame]');
// initialize the frames
for (var i = 0; i<this.storyboardFrames.length; i++) {
var frame = this.storyboardFrames[i];
this.el.removeChild(frame);
var dot = document.createElement('a-circle');
dot.setAttribute('color','#FFFFFF');
dot.setAttribute('scale',this.dotSize+' '+this.dotSize+' '+this.dotSize);
dot.setAttribute('link-to-frame',frame.getAttribute('storyboard-frame'));
var that = this;
dot.addEventListener('click', function (evt) {
that.navigateToId(evt.target.getAttribute('link-to-frame'));
});
this.$paginationDots.appendChild(dot);
}
this.dotsContainerWidth = this.dotMargin*(this.storyboardFrames.length-1);
this.$paginationDots.setAttribute('position', -.5*this.dotsContainerWidth+' 0 0');
this.$paginationDots.setAttribute('layout','type: line; margin: '+this.dotMargin);
this.$paginationBG.setAttribute('width',this.dotsContainerWidth+this.dotSize*4);
this.$paginationBG.setAttribute('position','0 0 -.1');
this.$leftArrow.setAttribute('position',(-this.dotsContainerWidth/2-this.arrowSize)+' 0 0');
this.$rightArrow.setAttribute('position',(this.dotsContainerWidth/2+this.arrowSize)+' 0 0');
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment