Skip to content

Creating Custom Components

Components define the different types of Nodes you can have in your project. Each component provides a set of functionalities and properties that you can add as nodes in your project’s Hierarchy, as many times as you like.

While there are many components included with Mattercraft (such as Box and Group) and many more can be added through libraries, it’s possible to write your own in TypeScript.

Adding a new Custom Component to your Project.

Most components in Mattercraft wrap and extend the functionality provided by lower-level elements, be they HTML elements or the objects and concepts from Mattercraft’s primary rendering engine of choice, three. js.

To get the best out of your custom components, we recommend getting up to speed with three.js - it’s a fantastic open-source project with a vibrant community.

There are many reasons you might wish to build your components, for example:

  • To build your own custom three. js-based objects.
  • To integrate three.js code from other codebases or resources.
  • To build your 3D shaders.
  • To build components that interact with third-party APIs.

Components in Mattercraft are similar in concept to components in web frameworks like React - they wrap underlying elements (typically HTML/DOM in React, and three.js in Mattercraft) and provide structure around properties and updates to those elements.

The easiest way to create a custom component is to start with a template. From the Project Panel you can add a new CustomThreeJSComponent which has the outline of a basic 3D component.

To add a new custom component to your Mattercraft project:

  1. Click the + (plus) icon button on the Project Panel
  2. Click CustomThreeJSComponent
  3. The Project Panel will now have this component script file with the skeleton of a brand-new component:

import { zComponent, zLoad } from '@zcomponent/core';
import { Group } from '@zcomponent/three/lib/components/Group';
import * as THREE from 'three';
@zComponent({ icon: 'favorite' })
export class CustomThreeJSComponent extends Group {
@zLoad()
private async _load() {
const myObject = new THREE.Mesh(
new THREE.SphereGeometry(),
new THREE.MeshBasicMaterial(),
);
this.element.add(myObject);
}
public dispose() {
// Clean up any resources that have been created here
return super.dispose();
}
}

Components are TypeScript or JavaScript classes that ultimately extend the Component class provided by Mattercraft.

In the example above, the component extends Mattercraft’s Group component. It wraps a three.js Group object that you can use to hold custom 3D content. This means your component automatically has all the properties you would expect from a 3D object, such as position, scale, and rotation.

Inside the _load function (marked with @zLoad()), we construct a custom three.js Mesh object, passing in our desired geometry and material, then we add it to the THREE.Group object provided to us as this.element.

The @zLoad() decorator registers the method as a loadable process - the loading screen will wait for it to complete before hiding. By default, the method is automatically invoked during construction. If you need to control when loading starts, pass false to disable auto-invocation:

@zLoad(false) // Won't auto-invoke - call manually when ready
private async _load() { ... }

The @zComponent() decorator tells the Mattercraft editor that we’d like this component to appear in the Hierarchy Panel + (plus) menu and right-click menus; and thus make it easy to add to our 3D scene.

See Mattercraft’s full API documentation here.

Once your component is constructed, you can add it as a child of the root Group node in your scene. Once added and selected in the Hierarchy, you can see your component’s properties in the editor.

Adding a Custom Component as a child of the Group node.

Just as with custom components, you can add properties to your behaviors that can be controlled from the 3D editor.

For more information, see the Properties article.

When 3D experiences run in an end user’s browser, the 3D engine will draw, or ’render’, the 3D scene up to 60 times every second. You may wish to run a script before each render frame.

Use the @zOnBeforeRender() decorator to run code every frame:

import { ContextManager, zComponent, zOnBeforeRender } from '@zcomponent/core';
@zComponent({ icon: 'favorite' })
export class RotatingComponent extends Group {
@zOnBeforeRender()
private _spin(deltaTime: number) {
// deltaTime is the number of milliseconds since the last frame
this.element.rotation.y += 0.001 * deltaTime;
}
}

For a full example, see our dedicated The Frame Loop article.

Use the icon option in @zComponent() to set an icon for your component in the Hierarchy Panel. Any icon from Google’s Material Icon Set can be used - just lowercase the name and replace spaces with underscores.

@zComponent({ icon: 'offline_bolt' })
export class CustomThreeJSComponent extends Group {
// ...
}

For example, the Google Material icon Check Box becomes check_box in Mattercraft.

Use the group option in @zComponent() to specify which category your component appears under in the Hierarchy Panel’s add menu. This could be an existing group, or you can create a new one.

@zComponent({ icon: 'star', group: 'My Custom Components' })
export class CustomComponent extends Group {
// ...
}

The following options are primarily useful when building component libraries or specialized component systems.

Use the parents option to limit where your component can be added in the hierarchy. This accepts glob patterns matching component tags.

@zComponent({
icon: 'html',
group: 'Heading',
tag: 'html/element/h1',
parents: 'html/element/**'
})
export class H1 extends ZHTMLElement<HTMLHeadingElement> {
constructor(contextManager: ContextManager, constructorProps: {}) {
super(contextManager, constructorProps, document.createElement('h1'));
}
}

In this example, the H1 component can only be added as a child of other HTML element components.

Use the tag option to assign a unique identifier to your component. Tags are used with the parents option to create component hierarchies.

@zComponent({
icon: 'html',
tag: 'html/element/div'
})
export class Div extends ZHTMLElement<HTMLDivElement> {
// ...
}

Use the stream option to indicate that your component provides a media stream (such as video or audio).

@zComponent({
icon: 'movie',
group: 'Media',
stream: true
})
export class VideoPlayer extends Object3D implements Stream {
// ...
}