diff options
-rw-r--r-- | dirty.html | 162 | ||||
-rw-r--r-- | src/morph.js | 112 | ||||
-rw-r--r-- | src/runner.js | 188 |
3 files changed, 139 insertions, 323 deletions
@@ -5,16 +5,17 @@ <meta charset="utf-8"> <title></title> <script type="text/javascript" src="dist/svg.js"></script> - <script type="text/javascript" src="src/morph.js"></script> <script type="text/javascript" src="src/helpers.js"></script> + <script type="text/javascript" src="src/transform.js"></script> + <script type="text/javascript" src="src/matrix.js"></script> + <script type="text/javascript" src="src/morph.js"></script> <script type="text/javascript" src="src/runner.js"></script> <script type="text/javascript" src="src/timeline.js"></script> <script type="text/javascript" src="src/controller.js"></script> - <script type="text/javascript" src="src/transform.js"></script> - <script type="text/javascript" src="src/matrix.js"></script> + </head> @@ -81,7 +82,7 @@ function getColor(t) { // .animate() // .move(300, 200) - +// // for (let i = 0 ; i < 15; i++) { // for (let j = 0 ; j < 10; j++) { // @@ -98,7 +99,7 @@ function getColor(t) { // // Animate the rect // rect.animate(3000, Math.random() * 2000) // // .during(t => rect.transform({rotate: 700 * t, origin: [cx, cy]})) -// .transform({rotate: 720, origin: [cx, cy]}) +// .transform({rotate: 720}, true) // // .during(t => rect.attr('transform', `rotate(${700 * t})`)) // .during(t => rect.attr('fill', getColor(o * 0.1 + t))) // } @@ -203,7 +204,7 @@ rectangle.animate().transform({ origin: [bbox.cx, bbox.cy] })*/ - +// let canvas = SVG('#canvas') SVG('#absolute').on('input slide', function (e) { @@ -228,7 +229,7 @@ SVG('#absolute').on('input slide', function (e) { let a = canvas.rect(200, 400).move(500, 400) .attr('opacity', 0.3) .addClass('pink') - .transform({ px: 100, py: 500, origin: 'top-left' }) + //.transform({ px: 100, py: 500, origin: 'top-left' }) var timer = 0 @@ -238,28 +239,18 @@ SVG('#absolute').on('input slide', function (e) { return timer }) - let obj = { rotate: val * 180, origin: 'center', translate: [300 * val, 0] } - let obj2 = { rotate: -val * 180, origin: 'center' } + let obj = { rotate: val * 180, origin: 'top-left' } + let obj2 = { rotate: val * 280, origin: 'center' } a.clone() // startPosition a.clone().transform(obj, true).transform(obj2, true) // endPosition }) - - -// transform({}) - - -// // Make the green rectange -// canvas.rect(200, 400).move(500, 400) -// .attr('opacity', 0.3) -// .addClass('green') - -// Make the pink rectangle +// // Make the pink rectangle let a = canvas.rect(200, 400).move(500, 400) .attr('opacity', 0.3) .addClass('pink') - // .transform({ tx: 100, ty: 500, origin: 'top-left' }) + .transform({ tx: 300, ty: 500, origin: 'top-left' }) var timer = 0 @@ -269,130 +260,37 @@ a.timeline().source(() => { return timer }) -let obj = { rotate: 180, origin: 'top-left', tx: 500} +let obj = { rotate: 100, origin: 'top-left'} let obj2 = { rotate: 280, origin: 'center' } +let obj3 = { rotate: 1000, origin: 'center', translate: [300, 200]} -var z = a.clone().attr('fill', 'blue') // startPosition -//a.clone().transform(obj, true).transform(obj2, true) // endPosition - -// that works -// a.animate(new SVG.Spring(50, 30)).transform(obj, false, true) // animation -// that breaks (why??) - -// var b = a.clone().animate(new SVG.Spring(500, 30)) -//debugger -//b.transform(obj, false, true) // animation -a.clone().animate(new SVG.Spring(1000, 15)).transform(obj).transform(obj2, true) // animation -a.clone().animate(1000).transform(obj).transform(obj2, true) // animation -a.clone().transform(obj).transform(obj2, true) // endPosition - -canvas.ellipse(20, 20).center(500, 400 + 0) +// var c = a.clone() +// var d = a.clone() // -//el.center(100, 400) -// z.transform({px: 100, py: 400}) -// z.transform({tx: 100, px: 100, py: 400}) -// z.transform({px: 100, py: 400}) -// z.transform({px: 100, py: 400}) -// z.transform({px: 100, py: 400}) - - -// SVG.on(document, 'mousemove', (e) => { -// let {x, y} = canvas.point(e.pageX, e.pageY) -// el.center(x, y) -// b.transform ({px: x, py: y, rotate: (x + y) / 3}) -// z.transform ({px: x, py: y }) //, rotate: (x + y) / 3}) -// }) - - - - - - -//setTimeout(()=>console.log(a.transform()), 6000) // -// // Put an ellipse where we expect the object to be -// canvas.ellipse(30, 30).center(100, 500) -// .attr('opacity', 0.3) -// .addClass('dark-pink') - - -// var timeline = new SVG.Timeline().pause() -// var runner = new SVG.Runner(100000) -// runner.queue(null, function (pos) { -// console.log(pos) -// }) -// timeline.schedule(runner) +// c.animate(1000) +// //.transform(obj) +// .transform(obj, true) // animation // -// runner.after(() => console.log('finished with after')) -// runner.on('finish', () => console.log('finished with on')) +// d.animate(3000) +// //.transform(obj) +// .transform(obj, true) // animation -//timeline.play() -//timeline.finish() +// a.clone().attr('fill', 'blue') +// //.transform(obj) +// .transform(obj2, true) // endPosition +let b = a.clone().animate(new SVG.Spring(1000, 15)) -// var circle = SVG('<rect>').addTo('svg').size(100, 100).center(200, 200) -// var runner = circle.animate(1000) -// .loop(3, true, 500) -// .reverse() -// .ease('<>') -// .center(500, 200) -// .during(t => circle.transform({rotate: 720 * t, origin: [200, 200]})) +SVG.on(document, 'mousemove', (e) => { + let {x, y} = canvas.point(e.clientX, e.clientY) + b.transform ({tx: x, ty: y, rotate: (x + y) / 3}, true) +}) -// var r = new SVG.Runner(1000).loop(10, false, 100) -// r.queue(null, console.log) -// -// r.step(1200) // should be 0.1s -// r.step(-300) // should be 0.9s -/* -var timer = 0 -SVG('svg').viewbox(-150, -150, 1000, 1000) -let rec1 = SVG('<rect>').addTo('svg') - .size(100, 100) - //.transform({translateX: -50, translateY: -50}) -rec1.timeline().source(() => { - timer += 2 - document.querySelector('#absolute span').textContent = timer - return timer -}) -var runner = rec1 - // .animate(100).attr('fill', '#fff') - //.animate().transform({rotate: -45, origin: [50, 50]}) - .animate(200) - .transform({ - rotate: 320, - //origin: [200, 200], - }, true) - - rec1.animate(150, 150, 'absolute') - .transform({ - scale:2 - }) -*/ - // .animate(400, 0, 'absolute') - // .transform({ - // rotate: 360, - // //origin: [200, 200], - // }, true) - - // .animate(500, 0) - // .transform({scale:2}) - // .transform({rotate: 360}, true) - // .transform({translateX: 50, translateY: 50}, true) - //.transform({translateX: 50, translateY: 50}, true) - // .animate(500, 0, 'absolute') - // .transform({scale: 2}, true) - // .animate(2000, 0, 'absolute') - // .transform({rotate: -300}) - -// r.step(300) // should be 0.1 -// r.step(2 * 1100) // should be 0 -// r.step(-50) // 0.05 -// r.step(-100) -// r.step(-100) // 0.95 </script> diff --git a/src/morph.js b/src/morph.js index 18ed6b5..6a9e651 100644 --- a/src/morph.js +++ b/src/morph.js @@ -8,7 +8,6 @@ SVG.Morphable = SVG.invent({ this._to = null this._type = null this._context = null - this.modifier = function(arr) { return arr } this._morphObj = null }, @@ -106,21 +105,11 @@ SVG.Morphable = SVG.invent({ at: function (pos) { var _this = this - // for(var i = 0, len = this._from.length; i < len; ++i) { - // arr.push(this.stepper(this._from[i], this._to[i])) - // } - return this._morphObj.fromArray( - this.modifier( - this._from.map(function (i, index) { - return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context) - }) - ) + this._from.map(function (i, index) { + return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context) + }) ) - }, - - valueOf: function () { - return this._value } } }) @@ -142,6 +131,42 @@ SVG.Morphable.NonMorphable = SVG.invent({ } }) +SVG.Morphable.TransformBag2 = SVG.invent({ + create: function (obj) { + if(Array.isArray(obj)) { + obj = { + scaleX: obj[0], + scaleY: obj[1], + shear: obj[2], + rotate: obj[3], + translateX: obj[4], + translateY: obj[5], + originX: obj[6], + originY: obj[7] + } + } + + Object.assign(this, obj) + }, + + extend: { + toArray: function (){ + var v = this + + return [ + v.scaleX, + v.scaleY, + v.shear, + v.rotate, + v.translateX, + v.translateY, + v.originX, + v.originY, + ] + } + } +}) + SVG.Morphable.TransformBag = SVG.invent({ inherit: SVG.Matrix, create: function (obj) { @@ -239,6 +264,7 @@ SVG.MorphableTypes = [ SVG.PathArray, SVG.Morphable.NonMorphable, SVG.Morphable.TransformBag, + SVG.Morphable.TransformBag2, SVG.Morphable.ObjectBag, ] @@ -256,29 +282,6 @@ SVG.extend(SVG.MorphableTypes, { }) -// animate().ease(function(pos) { return pos}) -// function Ease (func) { -// return function eased (fromOrCurr, to, pos) { -// return fromOrCurr + func(pos) * (to - fromOrCurr) // normal easing -// } -// } - - -/// -/// el.animate() -/// .fill('#00f') -/// ---->> timeline.fill -/// val = new Morphable().to('#0ff').stepper(stepper) -/// func init() { -/// val.from(el.fill()) -/// } -/// func run (pos) { -/// curr = val.at(pos) -/// el.fill(curr) -/// } -/// this.queue(init, run) - - // - Objects are just variable bags @@ -304,42 +307,7 @@ SVG.extend(SVG.MorphableTypes, { - -// C R x = D C x = A x -// -// (C R inv(C)) C x -// -// -// C R = D C -// D = C R inv(C) - - -/* -absolute -> start at current - {affine params} -relative -> start at 0 always - {random stuff} -*/ - - - - - /** - INIT - - save the current transformation - - ELEMENT TIMELINE (one timeline per el) - - Reads the current transform and save it to the transformation stack - - Runs all available runners, runners will: - - Modify their transformation on the stack - - Mark their transformation as complete - - After each runner, we group the matrix (not for now) - - After running the runners, we bundle all contiguous transformations into - a single transformation - - - - transformtionstack is like this: [RunnerB, Matrix, RunnerC] - - skip merging for now (premature blabla) - el.loop({times: 5, swing: true, wait: [20, 50]}) diff --git a/src/runner.js b/src/runner.js index da494d3..ceb42fe 100644 --- a/src/runner.js +++ b/src/runner.js @@ -160,7 +160,7 @@ SVG.Runner = SVG.invent({ this._queue.push({ initialiser: initFn || SVG.void, runner: runFn || SVG.void, - isTransform: !!isTransform, + isTransform: isTransform, initialised: false, finished: false, }) @@ -364,7 +364,6 @@ SVG.Runner = SVG.invent({ // Save a morpher to the morpher list so that we can retarget it later _rememberMorpher: function (method, morpher) { - if (method == 'transform' && !this._isDeclarative) return this._history[method] = { morpher: morpher, caller: this._queue[this._queue.length - 1], @@ -374,9 +373,25 @@ SVG.Runner = SVG.invent({ // Try to set the target for a morpher if the morpher exists, otherwise // do nothing and return false _tryRetarget: function (method, target) { - if (method == 'transform' && !this._isDeclarative) return false if(this._history[method]) { - this._history[method].morpher.to(target) + + // if the last method wasnt even initialised, throw it away + if (!this._history[method].caller.initialised) { + let index = this._queue.indexOf(this._history[method].caller) + this._queue.splice(index, 1) + return false + } + + // for the case of transformations, we use the special retarget function + // which has access to the outer scope + if (this._history[method].caller.isTransform) { + this._history[method].caller.isTransform(target) + + // for everything else a simple morpher change is sufficient + } else { + this._history[method].morpher.to(target) + } + this._history[method].caller.finished = false var timeline = this.timeline() timeline && timeline._continue() @@ -480,9 +495,8 @@ function mergeTransforms () { let netTransform = runners .map(runner => runner.transforms) .reduce((last, curr) => last.lmultiply(curr)) - this.transform(netTransform) - this._currentMatrix = runners[0] + this.transform(netTransform) // Merge any two transformations in a row that are done let lastRunner = null @@ -490,7 +504,7 @@ function mergeTransforms () { if (lastRunner && runner.done && lastRunner.done) { delete runners[runner.id+1] runners[lastRunner.id+1] = { - transforms: runner.transforms.lmultiply(lastRunner.transforms), + transforms: runner.transforms.multiply(lastRunner.transforms), done: true, id: lastRunner.id, } @@ -499,7 +513,7 @@ function mergeTransforms () { lastRunner = runner }) - if (!lastRunner) { + if (lastRunner == runners[0]) { this._frameId = null } } @@ -621,154 +635,90 @@ SVG.extend(SVG.Runner, { ? transforms.affine : (affine != null ? affine : !isMatrix) - /** - The default of relative is false - affine defaults to true if transformations are used and to false when a matrix is given - - We end up with 4 possibilities: - false, false: absolute direct matrix morph with SVG.Matrix - true, false: relative direct matrix morph with SVG.Marix or relative whatever was passed transformation with ObjectBag - - false, true: absolute affine transformation with SVG.TransformBag - true, true: relative whatever was passed transformation with ObjectBag - **/ - - // If we have a relative transformation and its not a matrix - // we morph all parameters directly with the ObjectBag - // the following cases are covered here: - // - true, false with ObjectBag - // - true, true with ObjectBag - var morpher - var origin - var current - var element - if(relative && !isMatrix) { - morpher = new SVG.Morphable().type(SVG.Morphable.ObjectBag) - .stepper(this._stepper) - - this.queue(function() { - element = this.element() - element.addRunner(this) - - if (origin == null) { - origin = new SVG.Point(getOrigin (transforms, element)) - transforms = {...transforms, origin: [origin.x, origin.y]} - morpher.to(formatTransforms(transforms)) - } - - let from = current || {origin} - morpher.from(formatTransforms(from)) - - }, function (pos) { - let currentMatrix = element._currentTransform(this) - let {x, y} = origin.transform(currentMatrix) - - - /* - 1. Transform the origin by figuring out the delta - - - At the start, we had: - - let Sinv = new SVG.Matrix(element).inverse() - let origin = getOrigin(element) - - - At a particular frame we have: - - let C = Matrix(element) - let newOrigin = origin.transform(S.inv).transform(C) - */ - - // this is an ugly hack to update the origins in the morpher - let index = morpher._from.indexOf('ox') - morpher._from.splice(index, 4, 'ox', x, 'oy', y) - morpher._to.splice(index, 4, 'ox', x, 'oy', y) - - current = morpher.at(pos).valueOf() - let matrix = new SVG.Matrix(current) - this.addTransform(matrix) - - return morpher.done() - }, true) - this._isDeclarative && this._rememberMorpher('transform', morpher) - return this - } - - // what is left is affine morphing for SVG.Matrix and absolute transformations with TransformBag - // also non affine direct and relative morhing with SVG.Matrix - // the following cases are covered here: - // - false, true with SVG.Matrix - // - false, true with SVG.TransformBag - // - true, false with SVG.Matrix - // - false, false with SVG.Matrix + const morpher = new SVG.Morphable().type( + affine ? SVG.Morphable.TransformBag2 : SVG.Matrix + ).stepper(this._stepper) - var morphType = (isMatrix && !affine) - ? SVG.Matrix - : SVG.Morphable.TransformBag + let origin + let element + let current + let currentAngle + var u = 0 + this.queue(function () { - morpher = new SVG.Morphable().type(morphType) - morpher.stepper(this._stepper) - morpher.to(transforms) + // make sure element and origin is defined + element = element || this.element() + origin = origin || getOrigin(transforms, element) - this.queue(function() { - element = this.element() + // add the runner to the element so it can merge transformations element.addRunner(this) - if (!origin && affine) { - origin = new SVG.Point(getOrigin (transforms, element)) - transforms = {...transforms, origin: [origin.x, origin.y]} - morpher.to(transforms) - } - if (!relative) { // Deactivate all transforms that have run so far if we are absolute element._clearTransformRunnersBefore(this) } - // FIXME: This should be current = current || ... - // Define the starting point for the morpher - let startMatrix = new SVG.Matrix(relative ? undefined : element) + let target = new SVG.Matrix({...transforms, origin}) + let start = current || new SVG.Matrix(relative ? undefined : element) - // make sure to add an origin if we morph affine if (affine) { - startMatrix.origin = origin + target = target.decompose(origin[0], origin[1]) + start = start.decompose(origin[0], origin[1]) // Get the current and target angle as it was set - const rTarget = morpher._to[3] - const rCurrent = startMatrix.decompose(origin[0], origin[1]).rotate + const rTarget = target.rotate + const rCurrent = start.rotate // Figure out the shortest path to rotate directly const possibilities = [rTarget - 360, rTarget, rTarget + 360] const distances = possibilities.map( a => Math.abs(a - rCurrent) ) const shortest = Math.min(...distances) const index = distances.indexOf(shortest) - const target = possibilities[index] + target.rotate = possibilities[index] + } - // HACK: We directly replace the rotation so that its faster - morpher._to.splice(3, 1, target) + if (relative) { + target.rotate = transforms.rotate || 0 + start.rotate = currentAngle || start.rotate } - // make sure morpher starts at the current matrix for declarative - // this happens only when the init function is called multiple times - // which is only true for declarative - morpher.from(current || startMatrix) + morpher.from(start) + morpher.to(target) + }, function (pos) { + // clear all other transforms before this in case something is saved + // on this runner. We are absolute. We dont need these! if (!relative) this.clearTransform() - // Retarget the origin if we are in an + // fix the origin so is in the right space if (affine) { let currentMatrix = element._currentTransform(this) - let {x, y} = origin.transform(currentMatrix) + let {x, y} = new SVG.Point(origin).transform(currentMatrix) morpher._from.splice(-2, 2, x, y) morpher._to.splice(-2, 2, x, y) } - current = morpher.at(pos) + let affineParameters = morpher.at(pos) + currentAngle = affineParameters.rotate + current = new SVG.Matrix(affineParameters) + this.addTransform(current) + return morpher.done() - }, true) + }, function (newTransforms) { + + // only get a new origin if it changed since the last call + if ((newTransforms.origin || 'center').toString() != (transforms.origin || 'center').toString()) { + origin = getOrigin (transforms, element) + } + + // overwrite the old transformations with the new ones + transforms = {...newTransforms, origin} + }) + this._isDeclarative && this._rememberMorpher('transform', morpher) |