feat: surfaces

This commit is contained in:
aarthificial
2022-02-18 00:27:30 +01:00
parent 49254fc36c
commit 99f9e96a10
15 changed files with 675 additions and 141 deletions

11
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"dependencies": {
"canvas": "^2.9.0",
"konva": "^8.3.2",
"mix-color": "^1.1.2",
"ts-loader": "^9.2.6",
"typescript": "^4.5.5",
"url-loader": "^4.1.1",
@@ -2423,6 +2424,11 @@
"node": ">= 8"
}
},
"node_modules/mix-color": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/mix-color/-/mix-color-1.1.2.tgz",
"integrity": "sha512-dl0x92sQ+cMM37GVo5MpLJfPbLxFhyqTrf6paYp/k+1rRPK1aPPPI7xajEI+GtU+eWnCsAW2Bg1q0rH1gLbI7w=="
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -6007,6 +6013,11 @@
"yallist": "^4.0.0"
}
},
"mix-color": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/mix-color/-/mix-color-1.1.2.tgz",
"integrity": "sha512-dl0x92sQ+cMM37GVo5MpLJfPbLxFhyqTrf6paYp/k+1rRPK1aPPPI7xajEI+GtU+eWnCsAW2Bg1q0rH1gLbI7w=="
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",

View File

@@ -9,11 +9,12 @@
"prepare": "npm run build",
"build": "tsc",
"test:serve": "node ./tools/serve.mjs ./test/player.ts",
"test:render": "node ./tools/serve.mjs ./test/render.ts"
"test:render": "node ./tools/render.mjs ./test/render.ts"
},
"dependencies": {
"canvas": "^2.9.0",
"konva": "^8.3.2",
"mix-color": "^1.1.2",
"ts-loader": "^9.2.6",
"typescript": "^4.5.5",
"url-loader": "^4.1.1",

View File

@@ -3,11 +3,15 @@ import {Project} from './Project';
export function Player(factory: () => Project) {
const project = factory();
project.start();
const interval = setInterval(() => {
if (project.next()) {
project.start();
project.next();
// clearInterval(interval);
const run = () => {
try {
if (project.next()) {
project.start();
project.next();
}
requestAnimationFrame(run);
} catch (e) {
}
}, 1000 / project.framesPerSeconds);
}
run();
}

View File

@@ -5,6 +5,13 @@ export const Renderer =
(factory: () => Project) => (createCanvas: any, Image: any) => {
Util.createCanvasElement = () => {
const node = createCanvas(300, 300);
const monkey = node.getContext;
node.getContext = (type: string, options: Record<any, any>) => {
return monkey.call(node, type, {
...options,
pixelFormat: 'RGB30',
});
};
if (!node['style']) {
node['style'] = {};
}

View File

@@ -1,2 +1,4 @@
export * from './move';
export * from './show';
export * from './show';
export * from './pop';
export * from './surfaceTransition';

View File

@@ -2,20 +2,37 @@ import {Node} from 'konva/lib/Node';
import {Vector2d} from 'konva/lib/types';
import {tween} from '../tweening';
export function move(node: Node, position: Vector2d): Generator;
export function move(
node: Node,
position: Vector2d,
absolute?: boolean,
): Generator;
export function move(
node: Node,
positionX: number,
positionY: number,
absolute?: boolean,
): Generator;
export function move(
node: Node,
positionX: number | Vector2d,
positionY?: number,
arg0: number | Vector2d,
arg1?: number | boolean,
arg2?: boolean,
): Generator {
let delta: Vector2d;
let absolute: boolean;
if (typeof arg0 === 'number') {
delta = {x: arg0, y: <number>arg1};
absolute = arg2;
} else {
delta = arg0;
absolute = <boolean>arg1;
}
const positionFrom = node.position();
const positionTo =
typeof positionX === 'number' ? {x: positionX, y: positionY} : positionX;
let positionTo = absolute
? delta
: {x: delta.x + positionFrom.x, y: delta.y + positionFrom.y};
const distance = Math.sqrt(
Math.pow(positionFrom.x - positionTo.x, 2) +

16
src/animations/pop.ts Normal file
View File

@@ -0,0 +1,16 @@
import {Node} from 'konva/lib/Node';
export function pop<T extends Node>(node: T): [T, () => void] {
const clone: T = node.clone();
clone.moveTo(node.getLayer());
clone.position(node.absolutePosition());
node.hide();
return [
clone,
() => {
clone.destroy();
node.show();
},
];
}

View File

@@ -0,0 +1,83 @@
import {Surface} from '../components/Surface';
import {TimeTween, tween} from '../tweening';
export function surfaceTransition(
fromSurface: Surface,
toSurface: Surface,
inverse?: boolean,
) {
const from = fromSurface.getSurfaceData();
const fromPos = fromSurface.getPosition();
const to = toSurface.getSurfaceData();
const toPos = toSurface.getPosition();
const fromDelta = fromSurface.calculateOriginDelta(toSurface.origin());
const fromNewPos = {
x: fromPos.x + fromDelta.x,
y: fromPos.y + fromDelta.y,
};
const toDelta = toSurface.calculateOriginDelta(fromSurface.origin());
const toNewPos = {
x: toPos.x + toDelta.x,
y: toPos.y + toDelta.y,
};
fromSurface.show();
toSurface.hide();
let check = true;
return tween(0.6, value => {
const distance = value.easeInOutQuint(0, Math.PI / 2);
let xValue = Math.sin(distance);
let yValue = 1 - Math.cos(distance);
if (inverse) {
[xValue, yValue] = [yValue, xValue];
}
if (value.value > 1 / 3) {
if (check) {
toSurface.setOverride(true);
toSurface.show();
fromSurface.hide();
fromSurface.setSurfaceData(from);
fromSurface.setPosition(fromPos);
fromSurface.getChild().opacity(1);
fromSurface.setOverride(false);
}
toSurface.setSurfaceData({
...from,
x: value.linear(from.x, to.x, xValue),
y: value.linear(from.y, to.y, yValue),
width: value.linear(from.width, to.width, xValue),
height: value.linear(from.height, to.height, yValue),
radius: value.easeInOutCubic(from.radius, to.radius),
color: value.color(from.color, to.color, value.easeInOutQuint()),
});
toSurface.setPosition({
x: value.linear(fromNewPos.x, toPos.x, xValue),
y: value.linear(fromNewPos.y, toPos.y, yValue),
});
toSurface
.getChild()
.opacity(Math.max(TimeTween.map(0, 1, value.linear(-1 / 2, 1)), 0));
} else {
fromSurface.setOverride(true);
fromSurface.setSurfaceData({
...from,
x: value.linear(from.x, to.x, xValue),
y: value.linear(from.y, to.y, yValue),
width: value.linear(from.width, to.width, xValue),
height: value.linear(from.height, to.height, yValue),
radius: value.easeInOutCubic(from.radius, to.radius),
color: value.color(from.color, to.color, value.easeInOutQuint()),
});
fromSurface.setPosition({
x: value.linear(fromPos.x, toNewPos.x, xValue),
y: value.linear(fromPos.y, toNewPos.y, yValue),
});
fromSurface.getChild().opacity(TimeTween.map(1, 0, value.linear(0, 3)));
}
});
}

117
src/components/Layout.ts Normal file
View File

@@ -0,0 +1,117 @@
import {Group} from 'konva/lib/Group';
import {Container, ContainerConfig} from 'konva/lib/Container';
import {Center} from 'MC/types/Origin';
import {GetSet, IRect} from 'konva/lib/types';
import {_registerNode} from 'konva/lib/Global';
import {Factory} from 'konva/lib/Factory';
import {ISurfaceChild, Surface, SURFACE_CHANGE_EVENT, SurfaceData} from 'MC/components/Surface';
import {Shape} from 'konva/lib/Shape';
import {getNumberValidator} from 'konva/lib/Validators';
export interface LayoutConfig extends ContainerConfig {
direction?: Center;
padding?: number;
}
export class Layout extends Group implements ISurfaceChild {
public direction: GetSet<Center, this>;
public padding: GetSet<number, this>;
constructor(config?: LayoutConfig) {
super(config);
this.recalculate();
}
private handleChildChange = () => this.recalculate();
getSurfaceData(): SurfaceData {
return {
...this.getClientRect(),
color: 'rgba(36, 36, 36, 1)',
radius: 20,
};
}
add(...children: (Group | Shape)[]): this {
super.add(...children);
for (const child of children) {
child.on(SURFACE_CHANGE_EVENT, this.handleChildChange);
}
this.recalculate();
return this;
}
removeChildren(): this {
for (const child of this.children) {
child.off(SURFACE_CHANGE_EVENT, this.handleChildChange);
}
return super.removeChildren();
}
private recalculate() {
if (!this.children) return;
const padding = this.attrs.padding ?? 20;
let height = 0;
let width = 0;
if (this.attrs.direction === Center.Horizontal) {
for (const child of this.children) {
const rect = child.getClientRect();
const offset = child instanceof Surface ? child.calculateOffset() : {x: 0, y: 0};
height = Math.max(height, rect.height);
width += rect.width / 2;
child.position({x: width, y: offset.y});
width += rect.width / 2 + padding;
}
this.offsetX((width - padding) / 2);
} else {
for (const child of this.children) {
const rect = child.getClientRect();
const offset = child instanceof Surface ? child.calculateOffset() : {x: 0, y: 0};
width = Math.max(width, rect.width);
height += rect.height / 2;
child.position({x: offset.x, y: height});
height += rect.height / 2 + padding;
}
this.offsetY((height - padding) / 2);
}
this.fire(SURFACE_CHANGE_EVENT, undefined, true);
}
getClientRect(config?: {
skipTransform?: boolean;
skipShadow?: boolean;
skipStroke?: boolean;
relativeTo?: Container;
}): IRect {
const padding = this.attrs.padding ?? 20;
const rect = super.getClientRect(config);
rect.x -= padding;
rect.y -= padding;
rect.width += padding * 2;
rect.height += padding * 2;
return rect;
}
}
Layout.prototype.className = 'Layout';
_registerNode(Layout);
Factory.addGetterSetter(
Layout,
'direction',
Center.Vertical,
undefined,
// @ts-ignore
Layout.prototype.recalculate,
);
Factory.addGetterSetter(
Layout,
'padding',
20,
getNumberValidator(),
// @ts-ignore
Layout.prototype.recalculate,
);

View File

@@ -1,118 +0,0 @@
import {Group} from 'konva/lib/Group';
import {Rect} from 'konva/lib/shapes/Rect';
import {Text} from 'konva/lib/shapes/Text';
import {Vector2d} from 'konva/lib/types';
import {ContainerConfig} from 'konva/lib/Container';
import {Origin, Direction} from '../types/Origin';
import {tween} from '../tweening';
export class ObjectNode extends Group {
public readonly box: Rect;
public readonly text: Text;
private _origin: Origin = Origin.Middle;
public constructor(config?: ContainerConfig) {
super(config);
this.box = new Rect({
x: 0,
y: 0,
height: 80,
cornerRadius: 8,
fill: '#242424',
});
this.text = new Text({
x: 0,
y: 0,
fontSize: 28,
align: 'center',
fontFamily: 'JetBrains Mono',
fill: 'white',
});
this.add(this.box);
this.add(this.text);
}
public setOrigin(origin: Origin): this {
if (this._origin === origin) return;
this._origin = origin;
this.recalculate();
return this;
}
public setText(text: string): this {
this.text.text(text);
this.recalculate();
return this;
}
public *animateText(text: string) {
const from = this.recalculateValues();
const previousText = this.text.text();
this.text.text(text);
const to = this.recalculateValues();
this.text.text(previousText);
yield* tween(0.3, value => {
this.box.width(value.easeInOutCubic(from.width, to.width));
this.text.width(value.easeInOutCubic(from.width, to.width));
this.box.offset(value.vector2d(from.box, to.box, value.easeInOutCubic()));
this.text.offset(
value.vector2d(from.text, to.text, value.easeInOutCubic()),
);
this.text.text(value.text(previousText, text, value.easeInOutCubic()));
});
this.recalculate();
}
private recalculate() {
const values = this.recalculateValues();
this.box.offset(values.box);
this.text.offset(values.text);
this.box.width(values.width);
this.text.width(values.width);
}
private recalculateValues() {
const previousWidth = this.text.width();
this.text.width(null);
const width = this.text.getTextWidth();
const height = this.text.height();
const boxWidth = Math.ceil((width + 80) / 20) * 20;
this.text.width(previousWidth);
const box: Vector2d = {x: 0, y: 0};
const text: Vector2d = {x: 0, y: 0};
if (this._origin & Direction.Left) {
box.x = text.x = boxWidth;
} else if (this._origin & Direction.Right) {
box.x = text.x = 0;
} else {
box.x = text.x = boxWidth / 2;
}
if (this._origin & Direction.Top) {
box.y = 80;
text.y = height / 2 + 40;
} else if (this._origin & Direction.Bottom) {
box.y = 0;
text.y = height / 2 - 40;
} else {
box.y = 40;
text.y = height / 2;
}
return {
width: boxWidth,
box,
text,
};
}
}

275
src/components/Surface.ts Normal file
View File

@@ -0,0 +1,275 @@
import {Group} from 'konva/lib/Group';
import {ContainerConfig} from 'konva/lib/Container';
import {Rect} from 'konva/lib/shapes/Rect';
import {Shape} from 'konva/lib/Shape';
import {GetSet, IRect, Vector2d} from 'konva/lib/types';
import {SceneContext} from 'konva/lib/Context';
import {Direction, Origin} from 'MC/types/Origin';
import {Factory} from 'konva/lib/Factory';
import {_registerNode} from 'konva/lib/Global';
import {tween} from 'MC/tweening';
import {parseColor} from 'mix-color';
export const SURFACE_CHANGE_EVENT = 'surfaceChange';
export type SurfaceData = IRect & {
radius: number;
color: string;
};
function roundRect(
ctx: CanvasRenderingContext2D | SceneContext,
x: number,
y: number,
width: number,
height: number,
radius: number,
) {
if (width < 2 * radius) radius = width / 2;
if (height < 2 * radius) radius = height / 2;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.closePath();
}
export interface ISurfaceChild {
getSurfaceData(): SurfaceData;
}
function isSurfaceChild(
node: Shape | Group | ISurfaceChild,
): node is (Shape | Group) & ISurfaceChild {
return 'getSurfaceData' in node;
}
export interface SurfaceConfig extends ContainerConfig {
origin?: Origin;
}
export class Surface extends Group {
private box: Rect;
private ripple: Rect;
private child: (Shape | Group) & ISurfaceChild;
private override: boolean;
private surfaceData: SurfaceData;
private maskData: SurfaceData;
public constructor(config?: SurfaceConfig) {
super(config);
this.add(
new Rect({
x: 0,
y: 0,
fill: '#242424',
name: 'ripple',
visible: false,
}),
);
this.add(
new Rect({
x: 0,
y: 0,
fill: '#242424',
name: 'box',
}),
);
}
add(...children: (Shape | Group)[]): this {
super.add(...children);
const child = children.find(isSurfaceChild);
const ripple = children.find<Rect>((child): child is Rect =>
child.hasName('ripple'),
);
const box = children.find<Rect>((child): child is Rect =>
child.hasName('box'),
);
if (child) {
if (this.child) {
this.child.off(SURFACE_CHANGE_EVENT, this.handleSurfaceChange);
}
this.child = child;
this.child.on(SURFACE_CHANGE_EVENT, this.handleSurfaceChange);
this.handleSurfaceChange();
}
if (box) {
this.box?.destroy();
this.box = box;
}
if (ripple) {
this.ripple?.destroy();
this.ripple = ripple;
}
return this;
}
public *doRipple() {
if (this.override) return;
const opaque = parseColor(this.surfaceData.color);
this.ripple.show();
this.ripple
.offsetX(this.surfaceData.width / 2)
.offsetY(this.surfaceData.height / 2)
.width(this.surfaceData.width)
.height(this.surfaceData.height)
.cornerRadius(this.surfaceData.radius)
.fill(`rgba(${opaque.r}, ${opaque.g}, ${opaque.b}, ${0.5})`);
yield* tween(1, value => {
const width = this.surfaceData.width + value.easeOutExpo(0, 100);
const height = this.surfaceData.height + value.easeOutExpo(0, 100);
const radius = this.surfaceData.radius + value.easeOutExpo(0, 50);
this.ripple
.offsetX(width / 2)
.offsetY(height / 2)
.width(width)
.height(height)
.cornerRadius(radius)
.fill(
`rgba(${opaque.r}, ${opaque.g}, ${opaque.b}, ${value.linear(
0.5,
0,
)})`,
);
});
this.ripple.hide();
}
public getChild(): (Shape | Group) & ISurfaceChild {
return this.child;
}
public setOverride(value: boolean) {
this.override = value;
this.clipFunc(value ? this.drawMask : null);
if (!value) this.handleSurfaceChange();
}
public getSurfaceData(): SurfaceData {
return this.surfaceData;
}
public setSurfaceData(data: SurfaceData) {
if (!this.override) return;
const offset = this.offsetY();
const newOffset = this.calculateOffset(data);
const scale = Math.min(1, data.width / this.surfaceData.width);
this.maskData = data;
this.maskData.x = -data.width / 2;
this.maskData.y = -data.height / 2 + offset - newOffset.y;
this.offsetX(newOffset.x);
this.child.scaleX(scale);
this.child.scaleY(scale);
this.child.position({
x: 0,
y: (-this.surfaceData.height * (1 - scale)) / 2,
});
this.box
.offsetX(data.width / 2)
.offsetY(data.height / 2 - offset + newOffset.y)
// .absolutePosition(this.surfaceData)
.width(data.width)
.height(data.height)
.cornerRadius(data.radius)
.fill(data.color);
}
private handleSurfaceChange = () => {
if (this.override) return;
this.maskData = this.surfaceData = this.child.getSurfaceData();
this.updateSurface();
};
private updateSurface() {
this.box
.offsetX(this.surfaceData.width / 2)
.offsetY(this.surfaceData.height / 2)
// .absolutePosition(this.surfaceData)
.width(this.surfaceData.width)
.height(this.surfaceData.height)
.cornerRadius(this.surfaceData.radius)
.fill(this.surfaceData.color);
this.offset(this.calculateOffset(this.surfaceData));
}
public handleOriginChange() {
if (!this.surfaceData || this.override) return;
const previousOffset = this.offset();
const nextOffset = this.calculateOffset(this.surfaceData);
this.offset(nextOffset);
this.move({
x: -previousOffset.x + nextOffset.x,
y: -previousOffset.y + nextOffset.y,
});
}
public calculateOriginDelta(newOrigin: Origin): Vector2d {
const offset = this.calculateOffset(this.surfaceData);
const nextOffset = this.calculateOffset(this.surfaceData, newOrigin);
return {
x: -offset.x + nextOffset.x,
y: -offset.y + nextOffset.y,
};
}
public calculateOffset(surfaceData?: SurfaceData, origin?: Origin): Vector2d {
surfaceData ??= this.surfaceData;
origin ??= this.attrs.origin ?? Origin.Middle;
const width = surfaceData.width / 2;
const height = surfaceData.height / 2;
const offset: Vector2d = {x: 0, y: 0};
if (origin & Direction.Left) {
offset.x = -width;
} else if (origin & Direction.Right) {
offset.x = width;
}
if (origin & Direction.Top) {
offset.y = -height;
} else if (origin & Direction.Bottom) {
offset.y = height;
}
return offset;
}
private drawMask(ctx: CanvasRenderingContext2D) {
roundRect(
ctx,
this.maskData.x,
this.maskData.y,
this.maskData.width,
this.maskData.height,
this.maskData.radius,
);
}
origin: GetSet<Origin, this>;
}
Surface.prototype.className = 'Surface';
_registerNode(Surface);
Factory.addGetterSetter(
Surface,
'origin',
Origin.Middle,
undefined,
Surface.prototype.handleOriginChange,
);

View File

@@ -0,0 +1,111 @@
import {Text, TextConfig} from 'konva/lib/shapes/Text';
import {GetSet, Vector2d} from 'konva/lib/types';
import {tween} from '../tweening';
import {ISurfaceChild, SURFACE_CHANGE_EVENT, SurfaceData} from './Surface';
import {ShapeGetClientRectConfig} from 'konva/lib/Shape';
import {_registerNode} from 'konva/lib/Global';
import {Factory} from 'konva/lib/Factory';
import {getNumberValidator} from 'konva/lib/Validators';
export interface TextContentConfig extends TextConfig {
minWidth?: number;
}
export class TextContent extends Text implements ISurfaceChild {
private contentOffset = 0;
public constructor(config?: TextContentConfig) {
super({
...config,
x: 0,
y: 0,
height: 80,
fontSize: 28,
verticalAlign: 'middle',
fontFamily: 'JetBrains Mono',
fill: 'rgba(30, 30, 28, 0.87)',
});
this.recalculate();
}
getSurfaceData(): SurfaceData {
return {
...this.getClientRect(),
radius: 40,
color: '#c0b3a3',
};
}
public setText(text: string): this {
super.setText(text);
this.recalculate();
return this;
}
public *animateText(text: string) {
const fromText = this.text();
const from = this.recalculateValues(fromText);
const to = this.recalculateValues(text);
yield* tween(0.3, value => {
this.text(value.text(fromText, text, value.easeInOutCubic()));
this.width(value.easeInOutCubic(from.width, to.width));
this.offset(
value.vector2d(from.offset, to.offset, value.easeInOutCubic()),
);
this.contentOffset = value.easeInOutCubic(from.contentOffset, to.contentOffset);
this.fire(SURFACE_CHANGE_EVENT, undefined, true);
});
this.recalculate();
}
private recalculate() {
const values = this.recalculateValues(this.text());
this.offset(values.offset);
this.width(values.width);
this.contentOffset = values.contentOffset;
this.fire(SURFACE_CHANGE_EVENT, undefined, true);
}
private recalculateValues(text: string) {
const minWidth = this.attrs.minWidth ?? 0;
const size = this.measureSize(text);
const textWidth = Math.max(minWidth, size.width);
const boxWidth = Math.ceil((textWidth + 80) / 20) * 20;
return {
width: boxWidth,
offset: <Vector2d>{x: textWidth / 2, y: 38},
contentOffset: (boxWidth - textWidth) / 2,
};
}
getClientRect(config?: ShapeGetClientRectConfig): {
width: number;
height: number;
x: number;
y: number;
} {
const rect = super.getClientRect(config);
rect.x -= this.contentOffset;
return rect;
}
public minWidth: GetSet<number, this>;
}
TextContent.prototype.className = 'TextContent';
_registerNode(TextContent);
Factory.addGetterSetter(
TextContent,
'minWidth',
0,
getNumberValidator(),
// @ts-ignore
TextContent.prototype.recalculate,
);

View File

@@ -1,3 +1,3 @@
export * from './Arrow';
export * from './Connection';
export * from './ObjectNode';
export * from './TextContent';

View File

@@ -1,4 +1,5 @@
import {Vector2d} from 'konva/lib/types';
import mixColor from "mix-color";
export class TimeTween {
public constructor(public value: number) {}
@@ -11,18 +12,20 @@ export class TimeTween {
return TimeTween.map(from, to, value);
}
public easeOutExpo(from = 0, to = 1) {
const value = this.value === 1 ? 1 : 1 - Math.pow(2, -10 * this.value);
public easeOutExpo(from = 0, to = 1, value?: number) {
value ??= this.value;
value = value === 1 ? 1 : 1 - Math.pow(2, -10 * value);
return TimeTween.map(from, to, value);
}
public easeInExpo(from = 0, to = 1) {
const value = this.value === 0 ? 0 : Math.pow(2, 10 * this.value - 10);
public easeInExpo(from = 0, to = 1, value?: number) {
value ??= this.value;
value = value === 0 ? 0 : Math.pow(2, 10 * value - 10);
return TimeTween.map(from, to, value);
}
public linear(from = 0, to = 1) {
return TimeTween.map(from, to, this.value);
public linear(from = 0, to = 1, value?: number) {
return TimeTween.map(from, to, value ?? this.value);
}
public easeInCirc(from = 0, to = 1) {
@@ -84,6 +87,10 @@ export class TimeTween {
}
}
public color(from: string, to: string, value?: number) {
return mixColor(from, to, value ?? this.value);
}
public vector2d(from: Vector2d, to: Vector2d, value?: number) {
return {
x: TimeTween.map(from.x, to.x, value ?? this.value),

View File

@@ -45,7 +45,7 @@ function build(entry) {
},
output: {
filename: `result.js`,
path: tmpDir,
path: output,
library: {
type: 'commonjs-module',
},
@@ -65,10 +65,11 @@ function build(entry) {
await build(projectFile);
const setup = await import(
/* webpackIgnore: true */
`file://${path.join(tmpDir, 'result.js')}`
`file://${path.join(output, 'result.js')}`
);
let totalSize = 0;
const startTime = Date.now();
const project = setup.default.default(createCanvas, Image);
project.start();
while (!project.next()) {
@@ -85,7 +86,7 @@ function build(entry) {
process.stdout.write(
`Frame: ${name}, Size: ${Math.round(size)} kB, Total: ${Math.round(
totalSize,
)} kB`,
)} kB, Elapsed: ${Math.round((Date.now() - startTime) / 1000)}`,
);
}
})().catch(console.error);