Skip to content

Commit

Permalink
allow to animate object properties
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenmeier committed May 12, 2024
1 parent d8c33d6 commit 0fea979
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/property/Property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default interface Property<V, I = V> {
}

export function newProperty(prop: string, targetType = ''): Property<any> {
if (targetType === 'object') return new StyleProperty(prop, false);

if (targetType !== 'dom')
throw new Error('only dom is supported as target');

Expand Down
4 changes: 2 additions & 2 deletions src/property/StyleProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ export default class StyleProperty

private _defaultValue: StyleValue | null;

constructor(name: string) {
constructor(name: string, useDefaults: boolean = true) {
this.name = name;

this.from = null;
this.to = null;

const def = STYLE_PROPS[name];
const def = useDefaults ? STYLE_PROPS[name] : undefined;
if (Array.isArray(def)) {
this._defaultValue = new StyleValue([new Value(def[0], def[1])]);
this.unit = def[1];
Expand Down
124 changes: 124 additions & 0 deletions src/target/ObjectTarget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import StyleValue from '../values/StyleValue.js';
import Value from '../values/Value.js';
import { Target } from './Target.js';

export default class ObjectTarget implements Target {
private target: Record<string, any>;

// private _extFns: ExternalFunctions;

// private transformValues: Map<string, (Value | null)[]>;
private styleValues: Map<string, StyleValue | undefined>;
// private values: Map<string, Value>;

/**
* target a dom node or svg
*
* extFns: {getComputedStyle, getRootElement}
*/
constructor(target: Record<string, any>) {
this.target = target;

this.styleValues = new Map();
// this.values = new Map();
}

inner() {
return this.target;
}

type() {
return 'object';
}

getValue(name: string): Value | null {
// return this.values.get(name) ?? null;
throw new Error('not supported');
}

setValue(name: string, value: Value) {
// this.values.set(name, value);
throw new Error('not supported');
}

removeValue(name: string) {
// this.values.delete(name);
throw new Error('not supported');
}

getTransformValue(name: string): Value | null {
throw new Error('not supported');
}

setTransformValue(name: string, value: Value) {
throw new Error('not supported');
}

removeTransformValue(name: string) {
throw new Error('not supported');
}

getStyleValue(name: string): StyleValue | null {
const v = this.styleValues.get(name) ?? null;

if (v !== null) return v;

const styleV = this.target[name];
if (typeof styleV === 'undefined' || styleV === null) {
return styleV;
}

try {
const value = StyleValue.parse(styleV);
this.styleValues.set(name, value);

return value;
} catch (e) {
console.log('could not parse value', styleV);
return v;
}
}

setStyleValue(name: string, value: StyleValue): void {
this.styleValues.set(name, value);
}

removeStyleValue(name: string): void {
this.styleValues.set(name, undefined);
}

hasClass(name: string): boolean {
throw new Error('not supported');
}

addClass(name: string): void {
throw new Error('not supported');
}

removeClass(name: string): void {
throw new Error('not supported');
}

unifyValues(name: string, a: Value, b: Value): [Value, Value] {
if (a.unit === b.unit) return [a, b];

if (!a.unit && b.unit) a.unit = b.unit;

if (!b.unit && a.unit) b.unit = a.unit;

return [a, b];
}

apply() {
// style
for (const [k, v] of this.styleValues.entries()) {
if (v === undefined) {
console.log('delete', k);
delete this.target[k];
this.styleValues.delete(k);
} else {
this.target[k] = v.toMixed();
}
}
}
}
13 changes: 13 additions & 0 deletions src/target/Target.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import Value from '../values/Value.js';
import DomTarget from './DomTarget.js';
import StyleValue from '../values/StyleValue.js';
import type TestDomNode from './TestDomNode.js';
import ObjectTarget from './ObjectTarget.js';

export type Targets = AnimationTarget | AnimationTarget[];
export type AnimationTarget =
| HTMLElement
| SVGElement
| TestDomNode
| Record<string, any>;

export function newTarget(target: any): Target {
if (typeof window === 'undefined') {
Expand All @@ -19,6 +28,10 @@ export function newTarget(target: any): Target {
}
}

if (typeof target === 'object' && target !== null) {
return new ObjectTarget(target);
}

throw new Error('unknown target');
}

Expand Down
52 changes: 51 additions & 1 deletion src/tests/properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
import TestTicker from '../timing/TestTicker.js';
import TestResponsiveEvent from '../responsive/TestEvent.js';
import { animate, timeline } from '../chnobli.js';
import { responsive, curve } from '../utils/utils.js';
import { responsive, curve, fromTo } from '../utils/utils.js';
import { el } from '../target/TestDomNode.js';
import { timeout } from 'fire/utils';

Expand Down Expand Up @@ -180,4 +180,54 @@ describe('properties', () => {
'translate3d(50.000px,20.000px,0.000px)',
);
});

it('object-target', () => {
const ticker = new TestTicker();

const obj: { x: number; width?: number } = {
x: 0,
};
const tl = timeline()
.add(obj, {
x: 100,
width: fromTo(0, 100),
duration: 10,
})
.play();

ticker.run(5);
expect(obj.x).toBe(50);
expect(obj.width).toBe(50);

ticker.run();
expect(obj.x).toBe(100);
expect(obj.width).toBe(100);

tl.reverse();
tl.play();
ticker.run();
expect(obj.x).toBe(undefined);
expect(obj.width).toBe(undefined);
});

it('reset values', () => {
const ticker = new TestTicker();

const div = el();
div.computedStyle.width = '0px';
const tl = timeline()
.add(div, {
width: 100,
duration: 10,
})
.play();

ticker.run();
expect(div.style.width).toBe('100.000px');

tl.reverse();
tl.play();
ticker.run();
expect(div.style.width).toBe(undefined);
});
});
12 changes: 12 additions & 0 deletions src/values/StyleValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,16 @@ export default class StyleValue {
throw new Error('unknown kind ' + this.kind);
}
}

// returns the value as a number if possible otherwise as a string
toMixed(): string | number {
if (this.inner.kind !== 'values' || this.inner.values.length !== 1)
return this.toString();

// length already checks that kind is values
const val = (this.inner.values as Value[])[0];

if (val.unit) return this.toString();
return val.num;
}
}

0 comments on commit 0fea979

Please sign in to comment.