Skip to content

Commit

Permalink
yeah!! a basic animation works
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenmeier committed Nov 25, 2023
1 parent d68a1e8 commit b1f8a74
Show file tree
Hide file tree
Showing 11 changed files with 732 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"license": "MIT",
"exports": {
"./easing": "./src/easing/index.js",
"./easing": "./src/timing/easing.js",
".": "./src/chnobli.js"
},
"devDependencies": {
Expand Down
188 changes: 188 additions & 0 deletions src/animation/animation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { takeProp } from '../utils/internal.js';
import Value from '../utils/value.js';
// import Ease from '../easing/ease.js';
import Timing from '../timing/timing.js';
import Ticker from '../timing/ticker.js';
import { newProperty } from './property.js';


export default class Animation {
// targets;
// #timing;

constructor(targets, props = {}) {
this._timing = new Timing(props);
// todo allow to set a custom ticker
this._ticker = Ticker.global();
this.targets = new Targets(targets, this._ticker);

this._props = [];

for (const prop in props) {
if (prop in this)
throw new Error('prop: ' + prop + ' is not allowed');

this[prop] = new PropertyAnimation(
prop,
props[prop],
this
);
this._props.push(this[prop]);
}


// transform
// we need to setup our "world"
}

play() {
// let's register into the global ticker
console.log('play');

this._ticker.add((change, opts) => {
let shouldKeepListener = false;

this._timing.advance(change);
if (!this._timing.ended)
shouldKeepListener = true;

// loop through all props and check if they have the same timing
for (const prop of this._props) {
if (prop._timing === this._timing)
continue;

prop._timing.advance(change);
if (!prop._timing.ended)
shouldKeepListener = true;
}

// now render
for (const prop of this._props) {
prop.render(this.targets, opts.changes);
}

if (!shouldKeepListener)
opts.remove();
});
}

// t = 0-1
seek(t) {}

pause() {}

reset() {
this.seek(0);
}
}

class Targets {
constructor(targets, ticker) {
if (Array.from(targets).length > 0)
throw new Error('only one target supported');

// this.target = targets;

this._globalTargets = ticker._targets;

this.target = this._globalTargets.register(targets);

// somewhere global probably in the ticker
// we need to store the property changes for each target
// which will then be applied
// this allows to merge transform stuff for example
// optimize to only call the dom if necessary
}

type() {
return this.target.type();
}

// get the start value
getStartValue(prop) {
return this.target.getStartValue(prop);
}

setValue(prop, value) {
return this.target.setValue(prop, value);
}
}

class PropertyAnimation {
constructor(prop, value, animation) {
this.prop = newProperty(prop, animation.targets.type());
// we will get the from the targets

this.to = this.prop.parseValue(value);

// either the timing is custom which then only exists in this property
// or it is shared between the animation
this._timing = animation._timing;
}

render(targets, changes) {

const pos = this._timing.position;

const from = targets.getStartValue(this.prop);

if (this.to.unit !== from.unit)
throw new Error(from.unit + ' != ' + this.to.unit);

const dif = this.to.num - from.num;


targets.setValue(this.prop, from.cloneAdd(pos * dif));
}
}

/*
animate(target, {
// special
// translate
x
y
scale
scaleX
scaleY
rotation
skew
skewX
skewY
// stagger
repeatDelay
stagger: {
// wrap advanced options in an object
each: 0.1,
from: "center",
grid: "auto",
ease: "power2.inOut",
repeat: -1, // Repeats immediately, not waiting for the other staggered animations to finish
},
ease,
duration,
repeat, true|false|0-x
repeatDelay?
delay,
alternate
})
const tl new Timeline;
tl.add([
animation()
], 100)
tl.addGroup()
tl.add()
*/
108 changes: 108 additions & 0 deletions src/animation/property.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Value from '../utils/value.js';

export default class Property {
constructor(name) {
// this is the property name
this.name = name;
}

parseValue(val) {
return Value.parse(val);
}

defaultValue() {
throw new Error('default value for ' + this.name + ' not known');
}

// try to access the value from the target
getCurrentValue(target) {
return this.defaultValue();
}

// if this property is a transform function return its name
transformFunction() {
return null;
}

styleName() {
return null;
}
}

const TRANSFORM_PROPS = ['x', 'y', 'scale', 'scaleX', 'scaleY'];

export class Transform extends Property {
constructor(name) {
super(name);

if (name === 'x' || name === 'y') {
this._defUnit = 'px';
this._defVal = 0;
this._transformFunction = 'translate' + name.toUpperCase();
// } else if (name === 'rotation') {
// this.defUnit = 'deg';
// this.defVal = 0;
} else if (name.startsWith('scale')) {
this._defUnit = null;
this._defVal = 1;
this._transformFunction = name;
} else {
throw new Error('unknown prop ' + name);
}
}

parseValue(val) {
return Value.parse(val, this._defUnit);
}

defaultValue() {
return new Value(this._defVal, this._defUnit);
}

transformFunction() {
return this._transformFunction;
}
}

const STYLE_PROPS = ['width', 'height', 'top', 'left', 'right', 'bottom'];

export class StyleProp extends Property {
// constructor(name) {
// super(name);
// }

parseValue(val) {
// maybe keep it unitless as long as possible, maybe until we need to
// write it
return Value.parse(val, 'px');
}

defaultValue() {
return new Value(0, 'px');
}

getCurrentValue(target) {
if (target.type() !== 'dom')
throw new Error('error not dom');

const style = getComputedStyle(target.target);

return Value.parse(style[this.name]);
}

styleName() {
return this.name;
}
}

export function newProperty(prop, targetType = '') {
if (targetType !== 'dom')
throw new Error('only dom is supported as target');

if (TRANSFORM_PROPS.includes(prop))
return new Transform(prop);
if (STYLE_PROPS.includes(prop))
return new StyleProp(prop);

throw new Error('unknown prop ' + prop);
}
61 changes: 60 additions & 1 deletion src/chnobli.js
Original file line number Diff line number Diff line change
@@ -1 +1,60 @@
export default {};
import { takeProp } from './utils/internal.js';
import Animation from './animation/animation.js';


export function animate(target, props = {}) {
const autoplay = parseAutoplay(takeProp(props, 'autoplay', true));

const animation = new Animation(target, props);
if (autoplay)
animation.play();

return animation;
}

function parseAutoplay(autoplay) {
if (autoplay !== true && autoplay !== false)
throw new Error('autoplay needs to be true|false');

return autoplay;
}

/*
animate(target, {
// special
// translate
x
y
scale
scaleX
scaleY
rotation
skew
skewX
skewY
// stagger
repeatDelay
stagger: {
// wrap advanced options in an object
each: 0.1,
from: "center",
grid: "auto",
ease: "power2.inOut",
repeat: -1, // Repeats immediately, not waiting for the other staggered animations to finish
},
ease,
duration,
repeat, (-1?)
repeatDelay?
delay,
alternate
})
*/
16 changes: 16 additions & 0 deletions src/timing/ease.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default class Ease {
constructor(prop) {
// parse prop
if (prop !== null && typeof prop !== 'function')
throw new Error('ease needs to be a function');

this.fn = prop;
}

apply(t) {
if (this.fn === null)
return t;

return this.fn(t);
}
}
File renamed without changes.
Loading

0 comments on commit b1f8a74

Please sign in to comment.