<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>
// .animate()
// .move(300, 200)
-
+//
// for (let i = 0 ; i < 15; i++) {
// for (let j = 0 ; j < 10; j++) {
//
// // 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)))
// }
origin: [bbox.cx, bbox.cy]
})*/
-
+//
let canvas = SVG('#canvas')
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
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
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>
this._to = null
this._type = null
this._context = null
- this.modifier = function(arr) { return arr }
this._morphObj = null
},
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
}
}
})
}
})
+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) {
SVG.PathArray,
SVG.Morphable.NonMorphable,
SVG.Morphable.TransformBag,
+ SVG.Morphable.TransformBag2,
SVG.Morphable.ObjectBag,
]
})
-// 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
-
-// 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]})
this._queue.push({
initialiser: initFn || SVG.void,
runner: runFn || SVG.void,
- isTransform: !!isTransform,
+ isTransform: isTransform,
initialised: false,
finished: false,
})
// 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],
// 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()
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
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,
}
lastRunner = runner
})
- if (!lastRunner) {
+ if (lastRunner == runners[0]) {
this._frameId = null
}
}
? 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)