Construct 3.
PRINCIPLES
- public methods are for using ONLY outside component
- protected methods are for using ONLY inside component
- get/set without public/protected are for using BOTH inside and outside component
- setState() component's method must be using ONLY outside
Project Structure
project/
βββ project.c3proj
βββ ...
scripts/
βββ components/
β βββ *.ts
βββ layouts/
β βββ *.layout.ts
βββ main.ts
- Install Deno (JavaScript runtime)
- Install [C3React CLI Tool]
deno install -f -A -g -n jsr:@lostinmind/c3react/cli
- Open C3 Template project in VSCode
./template/
- Install dependencies
npm install
- Open C3 project
./project/
- Run following command:
deno task dev
Template is using GSAP tool for small beautiful animations (NPM module)
main.ts
import app from 'c3react';
/** You can use NPM modules! */
import gsap from 'gsap';
import Main from '@/layouts/main.layout.ts';
app.init({
/** Enter types of inputs that your app's using */
inputs: ['pointer', 'mouse', 'keyboard'],
layouts: [
/** Import your layouts here... */
Main,
],
beforeStart: async () => {
/** Do something it's like runOnStartup() inside block */
/** Also here you already can use global 'runtime' variable */
console.log('Before start!', runtime);
},
});
/** Avoid errors with active GSAP animations when changing layout */
app.on('afteranylayoutend', () => gsap.globalTimeline.clear());
layouts/main.layout.ts
import { Layout, utils } from 'c3react';
import C3React from '@/components/c3react.ts';
import Button from '@/components/button.ts';
export class MainLayout extends Layout {
private readonly c3react = new C3React();
private readonly button = new Button('c3react');
protected override onStart = () => {
const onClicked = () => {
this.c3react.play();
this.button.setState({
color: [
utils.random(0, 255),
utils.random(0, 255),
utils.random(0, 255),
],
});
};
this.button.setState({
label: 'C3React :>',
color: [0, 225, 199],
onClicked,
});
};
}
const layout = new MainLayout('main');
export default layout;
components/c3react.ts
import { Component } from 'c3react';
import gsap from 'gsap';
export default class C3React extends Component<{}, 'c3react'> {
constructor() {
super({}, 'c3react');
}
protected override onReady(): void {
const root = this.getRoot();
root.setSize(0, 0);
gsap.to(root, {
width: 428,
height: 428,
duration: 1.25,
ease: 'back.out',
});
}
protected override onDestroyed(): void {
gsap.killTweensOf(this.getRoot());
console.log('C3React component was destroyed :3');
}
play() {
gsap.to(this.getRoot(), {
angleDegrees: 360,
duration: 1,
ease: 'power3.out',
overwrite: true,
});
}
}
components/button.ts
import { Component, useChild, useTouched, utils } from 'c3react';
import gsap from 'gsap';
export default class Button extends Component<{
onClicked?: () => void;
label: string;
color: Vec3Arr;
}, 'button'> {
private readonly useLabel = useChild(() => this.getRoot(), 'text');
private initialSize: C3React.Size = {
width: 0,
height: 0,
};
constructor(id: 'c3react') {
super(
{ color: [0, 225, 199], label: 'C3React' },
'button',
(i) => i.instVars.id === id,
);
}
protected override onReady(): void {
const { label, color } = this.getState();
const { width, height } = this.getRoot();
this.initialSize = { width, height };
this.useLabel().text = label;
this.changeColor(color);
useTouched(() => this.getRoot(), (type) => {
switch (type) {
case 'start':
{
this.animateIn();
}
break;
case 'end':
{
const { onClicked } = this.getState();
this.animateOut();
onClicked?.();
}
break;
}
});
}
protected override onStateChanged(): void {
const { label, color } = this.getState();
if (this.getPreviousState().label !== label) {
this.useLabel().typewriterText(label, 0.5);
}
this.changeColor(color);
}
private changeColor(color: Vec3Arr) {
const root = this.getRoot();
const [r, g, b] = root.colorRgb;
const rgb = utils.rgbToVec3(color);
const $ = { r, g, b };
gsap.to($, {
r: rgb[0],
g: rgb[1],
b: rgb[2],
duration: 1,
ease: 'power4.out',
overwrite: true,
onUpdate: () => {
root.colorRgb = [$.r, $.g, $.b];
},
});
}
private animateIn() {
gsap.to(this.getRoot(), {
width: this.initialSize.width / 1.2,
height: this.initialSize.height / 1.2,
duration: 0.2,
ease: 'power4.out',
overwrite: true,
});
}
private animateOut() {
gsap.to(this.getRoot(), {
width: this.initialSize.width,
height: this.initialSize.height,
duration: 0.2,
ease: 'power4.out',
overwrite: true,
});
}
}