some notes on how to use A-Frame within a Svelte Application
A-Frame is a framework for creating "virtual reality experiences" in the same way as "ordinary" web pages: by means of HTML elements with attributes and content. Under the hood, A-Frame uses the DOM only to manage the hierarchical description of a VR/AR "scene" and its "assets" (especially, without burdening the browser's layout engine) and then simply maps these elements to corresponding objects of the underlying Three.js library (this approach drastically simplifies the use of Three.js)
Svelte is a tool for building web applications from components which look and behave like custom HTML elements. Svelte accesses the DOM directly to manage these elements and update their attributes and contents upon changes.
While the underlying execution models of A-Frame and Svelte differ completely, their use of the DOM as a common data base ensures a fairly smooth interaction.
This repository illustrates how to integrate A-Frame entities, components and systems into a Svelte application - and how to use Svelte to implement custom A-Frame entities, components and systems. As you will see, it's quite simple.
A-Frame has an entity-component-system (ECS) architecture. As a consequence, an A-Frame "component" is something completely different than a Svelte "component". The following table tries to give a (really) rough mapping between A-Frame "Entities", "Components" and "Systems" and their Svelte counterparts:
A-Frame | Svelte |
---|---|
Entity | similar to a Svelte Component |
Component | similar to Svelte "props" |
System | the business logic behind these "props" |
In an object-oriented world, ECS "components" contain the data of a mixin or (in case of multiple inheritance) a single superclass, whereas a "system" contains the mixin's or superclass's implementation. ECS "entities" then simply represent instances of (classes inheriting from) these mixins or superclasses without any own data or business logic.
This repository contains files for an example which demonstrates all mechanisms described below:
aframe-component.js
- implements an A-Frame "component"aframe-system.js
- implements an A-Frame "system" (and its associated "component")aframe-primitive.js
- implements an A-Frame "primitive"AFrameEntity.svelte
- implements a Svelte component which acts like an A-Frame "entity"App.svelte
- contains the Svelte application which uses all modules shown aboveaframe-component.js
and aframe-system.js
log invocations of their lifecycle method on the browser console - thus, make sure to open the browser's developer tools and watch the console.
While the example also exists in the Svelte REPL, the mechanisms behind this REPL currently seem to be incompatible with A-Frame - the example therefore refuses to start.
Just build the example offline instead and run it locally on your own computer.
Assuming that you are following the usual workflow for Svelte applications (which finally runs Rollup or WebPack to bundle a set of modules into an optimized JavaScript distribution), everything starts by installing A-Frame as such a module:
npm install --save aframe
Within a Svelte file, you then just import A-Frame like so:
<script context="module">
import "aframe"
</script>
Two remarks seem appropriate:
<script>
element in the <head>
section of an HTML document, it issues a warning in the browser console if this is not the case - just ignore this warning...After having imported A-Frame, its elements may be used like any other HTML element in a Svelte application:
<a-scene>
<a-sky color="#ECECEC"/>
<a-box color="#4CC3D9"
position="0 0.5 -3" rotation="0 45 0"/>
<a-plane width="4" height="4" color="#7BC8A4"
position="0 0 -4" rotation="-90 0 0"/>
</a-scene>
Even better: Svelte's reactivity mechanisms work as usual:
<script>
let BoxIsVisible = false
setTimeout(() => { BoxIsVisible = true }, 5000)
let Angle = 45
setInterval(() => { Angle++ }, 100)
</script>
<a-scene a-svelte-system>
<a-sky color="#ECECEC"/>
{#if BoxIsVisible}
<a-box color="#4CC3D9"
position="0 0.5 -3" rotation="0 {Angle} 0"/>
{/if}
<a-plane width="4" height="4" color="#7BC8A4"
position="0 0 -4" rotation="-90 0 0"/>
</a-scene>
Note: while the rotation animation shown above works (and illustrates that you don't have to synchronize your business logic with the 3D rendering loop) using A-Frame's animation
component should be preferred.
A-Frame provides a mechanism for adding new "components" - this mechanism still works within a Svelte application.
Just implement the new A-Frame component in a separate JavaScript (or TypeScript) file:
import "aframe"
AFRAME.registerComponent('a-svelte-component',{
... // add your specification and implementation here
})
and import it into your Svelte application. From then on it may be added to A-Frame elements as usual:
<script context="module">
import "aframe"
import "./aframe-component.js"
</script>
<a-scene>
<a-sky color="#ECECEC"/>
<a-box a-svelte-component="..." color="#4CC3D9"
position="0 0.5 -3" rotation="0 {Angle} 0"/>
<a-plane width="4" height="4" color="#7BC8A4"
position="0 0 -4" rotation="-90 0 0"
/>
</a-scene>
A-Frame also provides a mechanism for adding new "systems" - and, again, this mechanism also works within a Svelte application.
Just implement the new A-Frame system in a separate JavaScript (or TypeScript) file:
import "aframe"
AFRAME.registerSystem('a-svelte-system',{
... // add your specification and implementation here
})
AFRAME.registerComponent('a-svelte-system',{
... // add your specification and implementation here
})
and import it into your Svelte application. From then on it may be added to A-Frame as usual:
<script context="module">
import "aframe"
import "./aframe-system.js"
</script>
<a-scene a-svelte-system="...">
<a-sky color="#ECECEC"/>
<a-box a-svelte-system="..." color="#4CC3D9"
position="0 0.5 -3" rotation="0 {Angle} 0"/>
<a-plane width="4" height="4" color="#7BC8A4"
position="0 0 -4" rotation="-90 0 0"
/>
</a-scene>
You may even implement new A-Frame "primitives" within Svelte.
As before, implement the primitive in a separate JavaScript (or TypeScript) file:
import "aframe"
AFRAME.registerPrimitive('a-svelte-primitive',{
defaultComponents: { /* ... */ },
mappings: { /* ... */ }
})
and import it into your Svelte application where it may be used like any other A-Frame tag:
<script context="module">
import "aframe"
import "./aframe-primitive.js"
</script>
<a-scene a-svelte-system="...">
<a-sky color="#ECECEC"/>
<a-svelte-primitive position="0 0.5 -3" rotation="0 {Angle} 0"/>
<a-plane width="4" height="4" color="#7BC8A4"
position="0 0 -4" rotation="-90 0 0"
/>
</a-scene>
Svelte may also be used to implement new A-Frame entities in order to benefit from Svelte features like stores, actions, contexts and component composition.
Just implement an ordinary Svelte component in a separate .svelte
file and import A-Frame as usual:
<script context="module">
import "aframe"
</script>
<script>
export let color = 'red'
</script>
<a-entity geometry="primitive:box" material="color:{color}"
{...$$restProps} />
Within the Svelte application you may then import and use the Svelte component as usual:
<script context="module">
import "aframe"
import AFrameEntity from './AFrameEntity.svelte'
</script>
<script>
let color = 'blue'
</script>
<a-scene a-svelte-system>
<a-sky color="#ECECEC"/>
<AFrameEntity {color} position="0 0.5 -3" rotation="0 45 0"/>
<a-plane width="4" height="4" color="#7BC8A4"
position="0 0 -4" rotation="-90 0 0"
/>
</a-scene>