Added some timeline and Morphing functions

This commit is contained in:
Saivan 2018-04-23 20:25:41 +10:00
parent 7a4979d0cf
commit 64b3144c89
8 changed files with 601 additions and 51 deletions

View File

@ -41,13 +41,14 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
<script src="svg.bench.js"></script>
<!--<script src="tests/10000-each.js"></script> -->
<script src="tests/10000-rects.js"></script>
<!-- <script src="tests/10000-rects.js"></script>
<script src="tests/10000-circles.js"></script>
<script src="tests/10000-paths.js"></script>
<script src="tests/10000-boxes.js"></script>
<script src="tests/10000-pointArray-bbox.js"></script>
<script src="tests/10000-pointArray-bbox.js"></script> -->
<script src="tests/10000-accesses.js"></script>
<script>
SVG.bench.run()
</script>
</body>
</html>
</html>

View File

@ -0,0 +1,35 @@
SVG.bench.describe('Access a dom attribues vs dom properties vs object properties', function(bench) {
bench.test('using an object', function() {
var sum = 0
var obj = {x: "30"}
for (var i = 0; i < 1000000; i++) {
sum += obj.x * i
}
console.log(sum)
})
bench.test('figure out what the overhead is', function () {
var obj = bench.draw.rect(100, 100).move(0, 0)
})
bench.test('using dom attriutes', function () {
var sum = 0
var obj = bench.draw.rect(100, 100).move(0, 0)
var node = obj.node
for (var i = 0; i < 1000000; i++) {
sum += node.getAttribute('x') * i
}
console.log(sum, node.getAttribute('x'))
})
bench.test('using dom properties', function () {
var sum = 0
var obj = bench.draw.rect(100, 100).move(0, 0)
var node = obj.node
for (var i = 0; i < 1000000; i++) {
sum += node.x.baseVal * i
}
console.log(sum, node.x)
})
})

View File

@ -1,6 +1,13 @@
SVG.defaults = {
// Default animation values
timeline: {
duration: 600,
ease: '>',
delay: 0,
},
// Default attribute values
attrs: {

View File

@ -142,12 +142,30 @@ Controlable ()
new Controller(target, controller)
Number
Array
PathArray
ViewBox
PointArray
Color
SVG.Timeline = {
styleAttr (type, name, val) {
let morpher = new Morph(this.controller).to(val)
let morpher = new Morph(val).controller(this.controller)
queue (
()=> {
morpher = morpher.from(element[type]('name'))
morpher = morpher.morph(element[type]('name'))
},
morpher.at
)
@ -159,7 +177,7 @@ SVG.Timeline = {
let morpher = declarative ? new Controller(target) : new Morph().to(val)
queue (
()=> {
morpher = morpher.from(element[type]('name'))
morpher = morpher.from(element[type](name))
},
() => {
this.element[type](name, morpher.at(pos))
@ -339,7 +357,6 @@ this.queue(fn, morpher)
new Morph(x(), xGiven)
x: function (x, relative) {
if (this.target() instanceof SVG.G) {
this.transform({x: x}, relative)

View File

@ -10,7 +10,11 @@ SVG.Matrix = SVG.invent({
source = source instanceof SVG.Element ? source.matrixify()
: typeof source === 'string' ? arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat))
: Array.isArray(source) ? arrayToMatrix(source)
: typeof source === 'object' ? source
: (typeof source === 'object' && (
source.a != null || source.b != null || source.c != null
|| source.d != null || source.e != null || source.f != null
)) ? source
: (typeof source === 'object') ? new SVG.Matrix().transform(source)
: arguments.length === 6 ? arrayToMatrix([].slice.call(arguments))
: base

394
src/morph.js Normal file
View File

@ -0,0 +1,394 @@
SVG.Morphable = SVG.invent{
create: function (controller) {
this.controller = controller || function (from, to, pos) {
return pos < 1 ? from : to
}
},
extend: {
from: function (val) {
this._from = this._set(val)
return this
}
to: function (val, modifier) {
this._to = this._set(val)
this.modifier = modifier
return this
}
type: function (type) {
this._type = type
return this
}
_set: function (value) {
if(!this._type) {
if (SVG.Color.isColor(val)) {
this._type = SVG.Color
} else if (SVG.regex.delimiter.test(val)) {
this._type = SVG.regex.pathLetters.test(val)
? SVG.PathArray
: SVG.Array
} else if (SVG.regex.numberAndUnit.test(val)) {
this._type = SVG.Number
} else if (value in SVG.MorphableTypes) {
this._type = value.constructor
// } else if (typeof value == 'object') {
// this._type = SVG.Morphable.TransformBag
} else {
this._type = SVG.Morphable.NonMorphable
}
}
return (new this._type(value)).toArray()
}
controller: function (controller) {
this._controller = controller
}
at: function (pos) {
var _this = this
// for(var i = 0, len = this._from.length; i < len; ++i) {
// arr.push(this.controller(this._from[i], this._to[i]))
// }
return this.type.fromArray(this.modifier(this._from.map(function (i, index) {
return _this.controller(i, _this._to[i], pos)
})))
},
valueOf: function () {
return this._value
}
}
}
SVG.Morphable.NonMorphable = SVG.invent({
create: function (val) {
this.value = val
},
extend: {
valueOf: function () {
return this.value
},
toArray: function () {
return [this.value]
},
fromArray: function (arr) {
return new SVG.Morphable.NonMorphable(arr[0])
}
}
})
SVG.Morphable.TransformBag = SVG.invent({
create: function (val) {
this.value = new Matrix(val).decompose()
},
extend: {
valueOf: function () {
return this.value
},
toArray: function (){
var v = this.value
return [
v.scaleX,
v.scaleY,
v.shear,
v.rotate,
v.translateX,
v.translateY
]
}
fromArray: function (arr) {
return new SVG.Morphable.TransformBag({
scaleX: arr[0],
scaleY: arr[1],
shear: arr[2],
rotate: arr[3],
translateX: arr[4],
translateY: arr[5]
})
}
})
SVG.MorphableTypes = [SVG.Number, SVG.Color, SVG.Box, SVG.Matrix, SVG.Morphable.NonMorphable, SVG.Morphable.TransformBag]
SVG.extend(SVG.MorphableTypes, {
to: (item, args) => {
let a = new SVG.Morphable().type(this.constructor).to(item, args)
},
})
// - Objects are just variable bags
// - morph rerutrns a morphable. No state on normal objects (like SVG.Color)
// - Objects can be represented as Array (with toArray())
// - Objects have an unmorph/fromarray function which converts it back to a normal object
// var b = new Color('#fff')
// b.morph('#000') === new Morph(b).to('#000')
// sweet = Color('#fff')
// dark = Color('#fef')
// sweet.to(dark, 'hsl')
// angle = Number(30)
// lastAngle = Number(300)
// angle.to(lastAngle, cyclic)
// mat1 = Matrix().transform({rotation: 30, scale: 0})
// mat2 = Matrix(30, 40, 50, 60, 10, 20)
// mat1.to(mat2)
/**
** absolute transformations
**/
// M v -----|-----(D M v = I v)------|-----> T v
//
// 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx]
// 2. on every frame: pull the current state of all previous transforms (M - m can change)
// and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
// 3. Find the interpolated matrix I(pos) = m + pos * (t - m)
// - Note I(0) = M
// - Note I(1) = T
// 4. Now you get the delta matrix as a result: D = I * inv(M)
el.animate().trasform({rotate: 720, scale: 2}, true)
el.animate().scale(2).rotate(720)
el.animate().transform({origin: traslate, })
absolute -> start at current - {affine params}
relative -> start at 0 always - {random stuff}
|> object.toArray()
|> (_) => _.map(() => {})
|> modifier
|> fromArray
function transform(transforms, relative, affine) {
affine = transforms.affine || affine
relative = transforms.relative || relative
// 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx]
var morpher = new SVG.Morphable.TransformBag().to(transforms)
// make sure you have an identity matrix defined as default for relative transforms
var morpher.from()
var el = this.target()
var initFn = relative ? function() {} : function() {
// 2. on every frame: pull the current state of all previous transforms (M - m can change)
morpher.from(el.transform())
}
this.queue(initFn, function (pos) {
// 3. Find the interpolated matrix I(pos) = m + pos * (t - m)
// - Note I(0) = M
// - Note I(1) = T
var matrix = morpher.at(pos)
if(!relative) {
// 4. Now you get the delta matrix as a result: D = I * inv(M)
matrix = matrix.multiply(el.transform().inverse())
}
el.pushTransform(matrix)
})
}
SVG.Morphable.TransformList = Object
if(affine) {
var morpher = new Matrix().to(transforms)
}
if(input is typeof plain object) {
// deal with a ttransformList
this.type = SVG.Morphable.TransformList
}
var morpher = new Morphable(modifier).to(transforms)
this.queue(() => {
morpher.from(this.transform())
}, (pos) => {
var matrix = morpher.at(pos)
el.transform(matrix)
})
el.transform({rotate: 720, sclae: 2, })
el.scale(2)
.rotate(720)
from -> 300
to -> [295, 305]
from -> 358
to -> 1
from -> 300
to -> 30
function transform(transforms, affine) {
if(relative) {
var morpher = new Morphable().to(transforms, affine)
this.queue(() => {}, (pos) => {
var matrix = morpher.at(pos)
el.transform(matrix)
})
} else {
this.queue(() => {
morpher
}, (pos) => {
var matrix = morpher.at(pos)
el.transform(matrix)
})
}
}
// el.animate().rotate(480)
function rotate(val) { // Relative
var morpher = new Morphable().from(0).to(val)
this.queue(() => {}, (pos)=> {
var rotation = morpher.at(pos)
el.rotate(rotation)
})
}
// morph = new Morphable(0).to(50, [0, 360]) -> in timeline
//
//
//
// on each frame
// el.rotate(morph.at(pos))
Morph.modifiers = {
hsb:
}
new Color(#fff).to('#000', 'hsb')
at returns a matrix anyway
new Number()
el.animate().fill('#000', 'hsb')
function fill(val, colorspace) {
var morpher = new Morphable().to(val, colorspace || 'rgb')
this.queue((val) => {
morpher.from(val)
}, (pos)=> {
var color = morpher.at(pos)
el.fill(color)
})
}
//
// Number.toArray() -> [3]
// Color.toArray() -> [red, green, blue]
//
//
//
//
//
//
//
//
//
new Color(30, 50, 40).toArray()
new PathArray([['M', 0, 3], ['L', 4, 5]]).morph(5, 3, 2, 8, 5)
controller = (s, e, p)=> {return s + (e-s) * p}
[['M', 0, 3], ['L', 4, 5], ['A', 120, 120, 1, 0]]
['1', '2', '3'] => parseFloat()
rect.anim()
.color('blue')
.anim()
.color(new Color('red'))
a = new SVG.Color('#3f2').to('#5f4').at(0.3)
new Morphable('#3f2').to('#5f4').at(0.4)
/*
zoom(level, point) {
let morpher = SVG.Number(level).controller(this.controller)
this.queue(
() => {morpher = morpher.from(element.zoom())},
(pos) => {element.zoom(morpher.at(pos), point)}
)
return this
}
*/

View File

@ -100,5 +100,11 @@ SVG.Number = SVG.invent({
.plus(this)
}
new SVG.Color('#2a4e5a').morph('#3b4f2a').at(0.4)
new Morph().from('#2a4e5a').to('#3b4f2a').at(0.3)
}
})

View File

@ -11,9 +11,9 @@ SVG.easing = {
function Runner (timeline) {
// We store a reference to the function to run and the timeline to use
this.functions = []
this.timeline = timeline
this.transforms = []
this.functions = []
this.done = false
// We copy the current values from the timeline because they can change
@ -29,11 +29,10 @@ Runner.prototype = {
this.functions.push(fn)
},
run: function (time) {
var line = this.timeline
step: function (time) {
// If it is time to do something, act now.
var end = this._start + this._duration
var running = this._start < time && time < end
if (running && this._running) {
var position = (time - this._startTime) / this._duration
@ -50,7 +49,7 @@ Runner.prototype = {
closure.finished = !running
},
stop: function () {
snap: function () {
},
@ -60,44 +59,128 @@ Runner.prototype = {
}
let time = window.performance || window.Date
SVG.Timeline = SVG.invent({
create: function (o, easy, delay, epoch) {
// Construct a new timeline on the given element
create: function (element) {
this.baseTransform = []
this.runners = []
this.controller = null
// Store a reference to the element to call its parent methods
this._element = element
if(o instanceof 'function') {
this.controller = o
// Store the timing variables
this._startTime = time.now()
this._duration = SVG.defaults.duration
this._ease = SVG.defaults.ease
} else if (typeof o === 'object') {
ease = o.ease
delay = o.delay
o = o.duration
}
// Play control variables control how the animation proceeds
this._controller = o instanceof 'function' ? o : null
this._backwards = false
this._reverse = false
this._loops = 0
this.ease = ease
this.delay = delay
this.duration = o
// Keep track of the running animations and their starting parameters
this._baseTransform = null
this._running = true
this._runners = []
},
extend: {
animate (duration, ease, delay, epoch)
loop (times, reverse)
duration (time)
delay (by, epoch)
ease (fn)
animate (duration, delay, now) {
play ()
pause ()
stop ()
finish (all=true)
speed (newSpeed)
seek (dt)
persist (dt || forever) // 0 by default
reverse ()
// Clear the controller and the looping parameters
this._controller = null
this._backwards = false
this._swing = false
this._loops = 0
// If we have a controller, we will use the declarative animation mode
if(duration instanceof 'function') {
this._controller = duration
// If we have an object we are declaring imperative animations
} else if (typeof duration === 'object') {
ease = duration.ease
delay = duration.delay
now = duration.now
duration = duration.duration
}
// We start the next animation after the old one is complete
this._startTime = now ? time.now() : (this._startTime + this._duration)
this._duration = duration || SVG.defaults.duration
// Make a new runner to take care of the
return this
},
duration (time) {
return this.animate(time, 0, false)
},
delay (by, now) {
return this.animate(0, by, now)
},
ease (fn) {
this._ease = SVG.easing[fn || SVG.defaults.ease] || fn
return this
},
loop (times, swing) {
this._loops = times
this._swing = swing
},
play () {
this._running = true
this._continue()
},
pause () {
this._running = false
},
stop () {
this.pause()
// Cancel all of the requested animation frames
},
finish (all=true) {
},
speed (newSpeed) {
},
seek (dt) {
},
persist (dt || forever) {
// 0 by default
},
reverse () {
},
queue (initialise, during) {
// Make a new runner
var runner = new Runner(this)
this._runners.push()
},
_step (dt) {
@ -105,23 +188,26 @@ SVG.Timeline = SVG.invent({
// Checks if we are running and continues the animation
_continue () {
, continue: function () {
if (this.paused) return
if (!this.nextFrame)
this.step()
return this
}
if (this._paused) return
// Go through each of the runners and step them
},
}
},
// Only elements are animatable
parent: SVG.Element,
// These methods will be added to all SVG.Element objects
construct: {
animate: function(o, ease, delay, epoch) {
return (this.timeline = this.timeline || new SVG.Timeline(o, ease, delay, epoch))
animate: function(o, delay, now) {
// Get the current timeline or construct a new one
this.timeline = (this.timeline || new SVG.Timeline(this))
.animate(o, delay, now)
return this.timeline
}
}
}
// Extend the attribute methods separately to avoid cluttering the main
@ -150,7 +236,7 @@ SVG.extend(SVG.Timeline, {
this.queue(
function () {
morpher = morpher.from(element[type]('name'))
morpher = morpher.from(element[type](name))
},
function () {
this.element[type](name, morpher.at(pos))