@@ -29,6 +29,8 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http: | |||
- added possibility to pass in additional attribues to element creators e.g. `canvas.rect({x:100})` or `canvas.rect(100, 100, {x:100})` (#796) | |||
- added `SVG.List` (#645) | |||
- added `words()` and `element()` to `Dom` because of (#935) | |||
- added lab, lch, hsl and cmyk color spaces (#790) | |||
- added `random()` method on `SVG.Color` to create random colors of different kinds (#939) | |||
### Removed | |||
- removed `SVG.Array.split()` function | |||
@@ -41,7 +43,6 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http: | |||
- removed `SVG.Nested` (#809) | |||
- removed `show()` from `SVG.A` to avoid name clash (#802) | |||
- removed `size()` from `SVG.Text` to avoid name clash (#799) | |||
- removed `move(), dmove()` etc for groups to avoid inconsistencies, we will expect users to use transforms to move around groups as they should (especially since they are much simpler now). | |||
- removed `native()` function | |||
- removed `Bare` in favour of `Dom` (#935) | |||
@@ -1,4 +1,4 @@ | |||
Copyright (c) 2012-2017 Wout Fierens | |||
Copyright (c) 2012-2018 Wout Fierens | |||
https://svgdotjs.github.io/ | |||
Permission is hereby granted, free of charge, to any person obtaining |
@@ -1,520 +0,0 @@ | |||
<!DOCTYPE html> | |||
<html lang="en" dir="ltr"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title></title> | |||
<script type="text/javascript" src="dist/polyfills.js"></script> | |||
<script type="text/javascript" src="dist/polyfillsIE.js"></script> | |||
<script type="text/javascript" src="dist/svg.min.js"></script> | |||
</head> | |||
<body style="background-color: #c7c7ff"> | |||
<!-- <div id="absolute"><label>Absolute: <input type="range" min="0" max="1" step="0.01"></slider></label><span></span></div> | |||
<div id="position"><label>Position: <input type="range" min="0" max="5" step="0.01"></slider></label><span></span></div> --> | |||
<button name="back1000">Back 1000</button> | |||
<button name="back100">Back 100</button> | |||
<button name="forth100">Forth 100</button> | |||
<button name="forth1000">Forth 1000</button> | |||
<button name="speed2">Speed x2</button> | |||
<button name="speed05">Speed x0.5</button> | |||
<button name="stop">Stop</button> | |||
<button name="finish">Finish</button> | |||
<button name="pause">Pause</button> | |||
<button name="play">Play</button> | |||
<button name="reverse">Reverse</button> | |||
<span id="displayText"></span> | |||
<br> | |||
<!-- Making the svg --> | |||
<svg width=1000px height=500px id="canvas"> | |||
<rect x=50 y=100 width=200 height=100 stroke=none stroke-width=2 /> | |||
</svg> | |||
<!-- Modifying the svg --> | |||
<script type="text/javascript"> | |||
// import SVG from './src/svg.js' | |||
// | |||
// window.SVG = SVG | |||
var rect = SVG('rect').hide() | |||
var sin = Math.sin | |||
var pi = Math.PI | |||
var round = Math.round | |||
function getColor(t) { | |||
var a = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150) | |||
var b = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200) | |||
var c = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150) | |||
var color = 'rgb('+ a +','+ b +','+ c +')' | |||
return color | |||
} | |||
// var rect1 = SVG('<rect>').addTo('svg').size(50, 50).move(100, 100) | |||
// var rect2 = SVG('<rect>').addTo('svg').size(50, 50).move(100, 200) | |||
// | |||
// var anim1 = new SVG.Runner(1000).element(rect1).loop(5, true, 1000).move(200, 100) | |||
// var anim2 = new SVG.Runner(1000).element(rect2).loop(5, true, 1000).move(200, 200) | |||
// | |||
// SVG('#absolute').on('input slide', function (e) { | |||
// var val = e.target.value | |||
// document.querySelector('#absolute span').textContent = val | |||
// anim1.absolute(val) | |||
// }) | |||
// | |||
// SVG('#position').on('input slide', function (e) { | |||
// var val = e.target.value | |||
// document.querySelector('#position span').textContent = val | |||
// anim2.position(val) | |||
// }) | |||
// rect.animate(4000) | |||
// .during(t => rect.transform({scale: sqrt(1 + t), rotate: 720 * t})) | |||
// .after(500, ()=> {rect.attr('stroke', 'white')}) | |||
// .queue(() => rect.attr('fill', getColor(0))) | |||
// .animate(1500, 500, true) | |||
// .queue(()=> {}, t => rect.attr('fill', getColor(t))) | |||
// .animate(1000, true) | |||
// .move(200, 200) | |||
// .size(300, 300) | |||
// SVG.Animator.timeout(()=> { rect.animate().pause() }, 1000 - 10) | |||
// SVG.Animator.timeout(()=> { rect.animate().play() }, 2000 - 10) | |||
// SVG('rect') | |||
// .clone().show() | |||
// .animate() | |||
// .move(300, 200) | |||
// | |||
// for (let i = 0 ; i < 15; i++) { | |||
// for (let j = 0 ; j < 10; j++) { | |||
// | |||
// // Make the rect | |||
// let o = i + j | |||
// let rect = SVG('rect').clone().show() | |||
// .width(40).height(40) | |||
// .x(50 * i).y(50 * j) | |||
// .attr('fill', getColor(o * 0.1)) | |||
// | |||
// // Move the rect | |||
// let {cx, cy} = rect.bbox() | |||
// | |||
// // Animate the rect | |||
// rect.animate(3000, Math.random() * 2000) | |||
// // .during(t => rect.transform({rotate: 700 * t, 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))) | |||
// } | |||
// } | |||
var canvas = SVG('#canvas') | |||
canvas.attr('viewBox', null) | |||
SVG.defaults.timeline.ease = '-' | |||
var r = new SVG.Runner(1000) | |||
var t = new SVG.Timeline().persist(true) | |||
r.schedule(t, 200) | |||
.animate(500).loop(5, true, 100) | |||
.animate(600, 200, 'absolute') | |||
.animate(600, 300) | |||
.animate(600, 300, 'now') | |||
.animate(1000, 0, 'absolute').loop(6, true) | |||
var schedule = t.schedule() | |||
schedule.forEach(function (s, i) { | |||
var rect = canvas.rect(s.duration / 10, 25) | |||
.move(100 + s.start/10, 100 + i*30) | |||
.attr('fill', '#000') | |||
s.runner.element(rect) | |||
.attr('fill', getColor(i*0.1)) | |||
// if (i===0) | |||
// s.runner.during(console.log) | |||
}) | |||
// t.play() | |||
var mover = canvas.line(100, 100, 100, 300).attr('stroke', 'black') | |||
mover.clone().insertAfter(mover) | |||
canvas.line(100, 300, 800, 300).attr('stroke', 'black') | |||
var text = SVG('#displayText') | |||
t.on('time', function (e) { | |||
mover.x(100 + e.detail/10) | |||
text.node.textContent = e.detail | |||
}) | |||
t.on('finished', function (e) { | |||
console.log('finished') | |||
}) | |||
SVG('button[name="back100"]').on('click', function (e) { | |||
t.seek(-100) | |||
}) | |||
SVG('button[name="back1000"]').on('click', function (e) { | |||
t.seek(-1000) | |||
}) | |||
SVG('button[name="forth100"]').on('click', function (e) { | |||
t.seek(100) | |||
}) | |||
SVG('button[name="forth1000"]').on('click', function (e) { | |||
t.seek(1000) | |||
}) | |||
SVG('button[name="speed2"]').on('click', function (e) { | |||
t.speed(2) | |||
}) | |||
SVG('button[name="speed05"]').on('click', function (e) { | |||
t.speed(0.5) | |||
}) | |||
SVG('button[name="pause"]').on('click', function (e) { | |||
t.pause() | |||
}) | |||
SVG('button[name="play"]').on('click', function (e) { | |||
t.play() | |||
}) | |||
SVG('button[name="stop"]').on('click', function (e) { | |||
t.stop() | |||
}) | |||
SVG('button[name="finish"]').on('click', function (e) { | |||
t.finish() | |||
}) | |||
SVG('button[name="reverse"]').on('click', function (e) { | |||
t.reverse() | |||
}) | |||
canvas.rect(100, 100).on('click', function (e) { | |||
e.target.instance.animate() | |||
.move(Math.random()*1000, Math.random()*750) | |||
.timeline().on('finished', function (e) { | |||
console.log('rect finished') | |||
}) | |||
}) | |||
console.log(schedule) | |||
// var bla = SVG('<rect>').size(0, 0).move(200, 200).addTo('svg') | |||
// bla.animate().size(220, 200).queue(null, console.log) | |||
// var randPoint = (x = 50, y = 50) => [ | |||
// Math.random() * 100 - 50 + x, | |||
// Math.random() * 100 - 50 + y | |||
// ] | |||
// | |||
// var poly = SVG('<polygon>').plot([ | |||
// randPoint(), | |||
// randPoint(), | |||
// randPoint(), | |||
// randPoint(), | |||
// randPoint() | |||
// ]).attr({fill: 'none', stroke: 'black'}).addTo('svg') | |||
// var polyAni = poly.animate(new SVG.Spring(3000, 0)) | |||
// | |||
// SVG.on(document, 'click', function (e) { | |||
// polyAni.plot([ | |||
// randPoint(e.pageX-50, e.pageY-50), | |||
// randPoint(e.pageX+50, e.pageY-50), | |||
// randPoint(e.pageX+50, e.pageY), | |||
// randPoint(e.pageX+50, e.pageY+50), | |||
// randPoint(e.pageX-50, e.pageY+50) | |||
// ]) | |||
// }) | |||
// var mover = SVG('<ellipse>').size(50, 50).center(100, 100).addTo('svg') | |||
// var anim = mover.animate(new SVG.Spring(500, 10)) | |||
// | |||
// let date = +new Date | |||
// SVG.on(document, 'mousemove', function (e) { | |||
// // if (+new Date - date > 50) { | |||
// // date = +new Date | |||
// // } else { | |||
// // return | |||
// // } | |||
// | |||
// //var p = mover.point(e.pageX, e.pageY) | |||
// var p = mover.doc().point(e.clientX, e.clientY) | |||
// //var p = {x: e.pageX, y: e.pageY} | |||
// //console.log(p) | |||
// anim.transform({px: p.x - 100, py: p.y - 100}) | |||
// //anim.center(p.x, p.y) | |||
// }) | |||
/* | |||
let canvas = SVG('#canvas') | |||
let rectangle = canvas.rect(100, 200).move(100, 0) | |||
let bbox = rectangle.bbox() | |||
var timer = 0 | |||
rectangle.timeline().source(() => { | |||
timer += 2 | |||
document.querySelector('#absolute span').textContent = timer | |||
return timer | |||
}) | |||
rectangle.animate().transform({ | |||
rotate: 90, | |||
origin: [bbox.cx, bbox.cy] | |||
})*/ | |||
// | |||
// SVG('#absolute').on('input slide', function (e) { | |||
// var val = e.target.value | |||
// | |||
// canvas.clear() | |||
// canvas.ellipse(20, 20) | |||
// let re = canvas.rect(300, 150).move(100, 150).attr('opacity', 0.5) | |||
// re.clone() | |||
// .transform({rotate: 45, skew: 30}, true) | |||
// | |||
// re.clone() | |||
// .transform({ | |||
// rotate: 45, skew: 30, /*translate: [150, 140], */ | |||
// }, true) | |||
// .transform({rotate: val, origin: 'bottom-right'}, true) | |||
// | |||
// re.clone() | |||
// .transform({rotate: 45, skew: 30}, true) | |||
// .transform({rotate: val, origin: 'bottom-right'}, true) | |||
// .transform({skew}, true) | |||
// let a = canvas.rect(200, 400).move(500, 400) | |||
// .attr('opacity', 0.3) | |||
// .addClass('pink') | |||
// //.transform({ px: 100, py: 500, origin: 'top-left' }) | |||
// | |||
// | |||
// var timer = 0 | |||
// a.timeline().source(() => { | |||
// timer += 1 | |||
// document.querySelector('#absolute span').textContent = timer | |||
// return timer | |||
// }) | |||
// | |||
// 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 | |||
// }) | |||
// let a = canvas.rect(200, 400).move(500, 400) | |||
// | |||
// a.animate(1000, 500).move(100, 100).animate(1000, 500).move(500, 400) | |||
/* FUZZYMS PLANETS!! */ | |||
// let canvas = SVG('#canvas') | |||
// let gradient = canvas.gradient('radial', function(gradient) { | |||
// gradient.stop(0, '#f00') | |||
// gradient.stop(1, '#ff0') | |||
// }) | |||
// | |||
// let gradientEarth = canvas.gradient('linear', function(gradient) { | |||
// gradient.stop(0, '#00f') | |||
// gradient.stop(1, '#0f0') | |||
// }) | |||
// | |||
// let sun = canvas.circle(200).center(500, 300).attr({ fill: gradient }) | |||
// | |||
// let earth = canvas.circle(100).center(1000, 300).attr({fill: gradientEarth}) | |||
// | |||
// let moon = canvas.circle(50).center(1200, 300).attr({fill: '#ffa'}) | |||
// | |||
// earth.animate(10000).loop().ease('-') | |||
// .transform({rotate: 360, origin: [500, 300]}, true) | |||
// .transform({rotate: 720, origin: 'center'}, true) | |||
// .on('step', (e) => { | |||
// // console.log(e) | |||
// }) | |||
// | |||
// moon.animate(10000).loop().ease('-') | |||
// .transform({rotate: 360, origin: [500, 300]}, true) | |||
// .transform({rotate: 3600, origin: [1000, 300]}, true) | |||
/** | |||
* FUZZYS EXAMPLE | |||
*/ | |||
// let a = canvas.rect(200, 400).move(500, 400) | |||
// .attr('opacity', 0.3) | |||
// .addClass('pink') | |||
// .transform({ tx: 300, ty: 500, origin: 'top-left' }) | |||
// | |||
// let obj = { rotate: 100, origin: 'top-left'} | |||
// let obj2 = { rotate: 280, origin: 'center' } | |||
// let obj3 = { rotate: 1000, origin: 'center', translate: [300, 200]} | |||
// | |||
// | |||
// var c = a.clone() | |||
// | |||
// c.animate(3000) | |||
// .transform(obj) | |||
// .transform(obj2, true) // animation | |||
// | |||
// a.clone().attr('fill', 'blue') | |||
// .transform(obj) | |||
// .transform(obj2, true) // endPosition | |||
// SAIVANS EXAMPLE | |||
// canvas.ellipse(20, 20).center(100, 100) | |||
// let r = canvas.rect(200, 400).move(100, 100) | |||
// .attr('opacity', 0.3) | |||
// //.transform({ tx: 300, ty: 500, origin: 'top-left' }) | |||
// | |||
// // Normal usage | |||
// let wait = 3000 | |||
// let rAnim = r.clone().attr('fill', '#f00').animate(wait).attr('fill', '#0f0') | |||
// let rDecl = r.clone().attr('fill', 'blue').animate(new SVG.Spring(wait, 15)) | |||
// //let rDecl = r.clone().attr('fill', 'blue').animate(new SVG.PID(0.01, 0.001, 1, 10)) | |||
// | |||
// // var timer = 0 | |||
// // rDecl.timeline().source(() => { | |||
// // timer += 100 | |||
// // return timer | |||
// // }) | |||
// | |||
// let runTransformation = (transform) => { | |||
// return () => { | |||
// //transform = new SVG.Matrix(transform) | |||
// let relative = true | |||
// let affine = true | |||
// r.transform(transform, relative) | |||
// rAnim.animate(wait).transform(transform, relative, affine) | |||
// rDecl.transform(transform, relative, affine) | |||
// } | |||
// } | |||
// | |||
// setTimeout(runTransformation({ | |||
// origin: 'top-left', | |||
// translate: [530, 250], | |||
// rotate: 300, | |||
// scale: 2, | |||
// shear: 1, | |||
// }), 0.1 * wait ) | |||
// | |||
// | |||
// | |||
// setTimeout(runTransformation({ | |||
// origin: 'top-left', | |||
// translate: [530, 250], | |||
// rotate: 100, | |||
// scale: 2, | |||
// shear: 1, | |||
// }), 0.4 * wait) | |||
// | |||
// setTimeout(runTransformation({ | |||
// origin: 'top-left', | |||
// translate: [530, 250], | |||
// rotate: 100, | |||
// scale: 2, | |||
// shear: 1, | |||
// }), 0.6 * wait) | |||
// canvas.circle(50).center(100, 0).attr('fill', 'gray') | |||
// | |||
// setTimeout(runTransformation({ | |||
// rotate: 360, | |||
// origin: [100, 0] | |||
// }), 0.1 * wait ) | |||
// | |||
// | |||
// setTimeout(runTransformation({ | |||
// rotate: 360, | |||
// }), 0.4 * wait) | |||
// const getConsole = (time) => { | |||
// return () => { | |||
// console.group(time) | |||
// console.log(0, rAnim.element()._transformationRunners[0] && rAnim.element()._transformationRunners[0].transforms.decompose().rotate) | |||
// console.log(1, rAnim.element()._transformationRunners[1] && rAnim.element()._transformationRunners[1].transforms.decompose().rotate) | |||
// console.log(2, rAnim.element()._transformationRunners[2] && rAnim.element()._transformationRunners[2].transforms.decompose().rotate) | |||
// console.log(3, rAnim.element()._transformationRunners[3] && rAnim.element()._transformationRunners[3].transforms.decompose().rotate) | |||
// console.log(4, rAnim.element()._transformationRunners[4] && rAnim.element()._transformationRunners[4].transforms.decompose().rotate) | |||
// console.groupEnd(time) | |||
// } | |||
// } | |||
// | |||
// logCall = (time) => { | |||
// setTimeout(getConsole(time), time) | |||
// } | |||
// | |||
// logCall(0.2 * wait) | |||
// logCall(0.3 * wait) | |||
// logCall(0.4 * wait) | |||
// logCall(0.5 * wait) | |||
// logCall(0.6 * wait) | |||
// logCall(0.7 * wait) | |||
// logCall(0.8 * wait) | |||
// logCall(0.9 * wait) | |||
// logCall(1 * wait) | |||
// logCall(1.1 * wait) | |||
// logCall(1.2 * wait) | |||
// logCall(1.3 * wait) | |||
// logCall(1.4 * wait) | |||
// logCall(1.5 * wait) | |||
</script> | |||
</body> | |||
</html> |
@@ -21,6 +21,6 @@ | |||
</body> | |||
<script src="../../dist/svg.js" charset="utf-8"></script> | |||
<script src="bundle.js" charset="utf-8"></script> | |||
<script src="main.js" charset="utf-8"></script> | |||
</html> |
@@ -12,7 +12,7 @@ function rectangles ( method='Vibrant') { | |||
// Add the squares | |||
for ( let i = 0; i < 20; i++ ) { | |||
let color = SVG.Color.random( method.toLowerCase() ).hex() | |||
let color = SVG.Color.random( method.toLowerCase() ).toHex() | |||
let rect = group.rect(100, 100) | |||
.x( 20 + 100 * i ) | |||
.fill( color ) |
@@ -35,7 +35,7 @@ function reactToDrag(element, onDrag, beforeDrag) { | |||
} | |||
// Bind the drag tracker to this element directly | |||
let parent = element.doc() | |||
let parent = element.root() | |||
let point = new SVG.Point() | |||
element.mousedown(startDrag).touchstart(startDrag) | |||
} |
@@ -5,7 +5,7 @@ | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>SVG Playground</title> | |||
<link rel="stylesheet" href="../playground.css"> | |||
<link rel="stylesheet" href="style.css"> | |||
</head> | |||
<body> |
@@ -5,7 +5,7 @@ | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>SVG Playground</title> | |||
<link rel="stylesheet" href="../playground.css"> | |||
<link rel="stylesheet" href="style.css"> | |||
</head> | |||
<body> |
@@ -17,7 +17,7 @@ | |||
<!-- include source files here... --> | |||
<script src="../dist/polyfills.js" charset="utf-8"></script> | |||
<script src="../dist/polyfillsIE.js" charset="utf-8"></script> | |||
<script src="../dist/svg.min.js" charset="utf-8"></script> | |||
<script src="../dist/svg.js" charset="utf-8"></script> | |||
</head> | |||
@@ -58,15 +58,15 @@ | |||
<!--<script src="es5TestBundle.js"></script>--> | |||
<!-- <script src="spec/adopter.js"></script> | |||
<script src="spec/adopter.js"></script> | |||
<script src="spec/arrange.js"></script> | |||
<script src="spec/array.js"></script> | |||
<script src="spec/bare.js"></script> | |||
<script src="spec/boxes.js"></script> | |||
<script src="spec/circle.js"></script> | |||
<script src="spec/clip.js"></script> --> | |||
<script src="spec/clip.js"></script> | |||
<script src="spec/color.js"></script> | |||
<!-- <script src="spec/container.js"></script> | |||
<script src="spec/container.js"></script> | |||
<script src="spec/defs.js"></script> | |||
<script src="spec/doc.js"></script> | |||
<script src="spec/easing.js"></script> | |||
@@ -104,6 +104,6 @@ | |||
<script src="spec/morphing.js"></script> | |||
<script src="spec/animator.js"></script> | |||
<script src="spec/runner.js"></script> | |||
<script src="spec/queue.js"></script> --> | |||
<script src="spec/queue.js"></script> | |||
</body> | |||
</html> |
@@ -3,27 +3,32 @@ import { makeInstance } from '../../utils/adopter.js' | |||
import { globals } from '../../utils/window.js' | |||
let listenerId = 0 | |||
let windowEvents = {} | |||
function getEvents (node) { | |||
const n = makeInstance(node).getEventHolder() | |||
function getEvents (instance) { | |||
let n = instance.getEventHolder() | |||
// We dont want to save events in global space | |||
if (n === globals.window) n = windowEvents | |||
if (!n.events) n.events = {} | |||
return n.events | |||
} | |||
function getEventTarget (node) { | |||
return makeInstance(node).getEventTarget() | |||
function getEventTarget (instance) { | |||
return instance.getEventTarget() | |||
} | |||
function clearEvents (node) { | |||
const n = makeInstance(node).getEventHolder() | |||
function clearEvents (instance) { | |||
const n = instance.getEventHolder() | |||
if (n.events) n.events = {} | |||
} | |||
// Add event binder in the SVG namespace | |||
export function on (node, events, listener, binding, options) { | |||
var l = listener.bind(binding || node) | |||
var bag = getEvents(node) | |||
var n = getEventTarget(node) | |||
var instance = makeInstance(node) | |||
var bag = getEvents(instance) | |||
var n = getEventTarget(instance) | |||
// events can be an array of events or a string of events | |||
events = Array.isArray(events) ? events : events.split(delimiter) | |||
@@ -51,8 +56,9 @@ export function on (node, events, listener, binding, options) { | |||
// Add event unbinder in the SVG namespace | |||
export function off (node, events, listener, options) { | |||
var bag = getEvents(node) | |||
var n = getEventTarget(node) | |||
var instance = makeInstance(node) | |||
var bag = getEvents(instance) | |||
var n = getEventTarget(instance) | |||
// listener can be a function or a number | |||
if (typeof listener === 'function') { | |||
@@ -109,7 +115,7 @@ export function off (node, events, listener, options) { | |||
off(n, event) | |||
} | |||
clearEvents(node) | |||
clearEvents(instance) | |||
} | |||
}) | |||
} |
@@ -51,7 +51,7 @@ export function adopt (node) { | |||
if (node.instance instanceof Base) return node.instance | |||
// initialize variables | |||
var className = capitalize(node.nodeName) | |||
var className = capitalize(node.nodeName || 'Dom') | |||
// Make sure that gradients are adopted correctly | |||
if (className === 'LinearGradient' || className === 'RadialGradient') { |
@@ -1,107 +0,0 @@ | |||
# tests to write | |||
- sugar.js | |||
- insertBefore | |||
- insertAfter | |||
- after | |||
- before | |||
- ax, ay, amove | |||
- Path.js | |||
- targets | |||
- Element.js | |||
- svg | |||
- attr.js | |||
- Style.js | |||
# Where We Left Off | |||
Saivan | |||
====== | |||
Ulima | |||
===== | |||
- Use runners[runnerid] = {startTime, runner, persist} | |||
timeline.persist('monkey-in', Infinity) | |||
- folding transformations | |||
- testing direct non affine morph | |||
- why cant i use current? | |||
- handle null values | |||
Both | |||
==== | |||
- We discussed that matrices should always be applied from the left for animation, so we have: | |||
- If we have C R x where C is the current Matrix and R is the relative matrix that we want to apply | |||
- It could be animated by instead left multiplying (C R inv(C)) so that we have (C R inv(C)) C R | |||
- This allows us to always left multiply (which greatly simplifies things) | |||
=> Conclusion: We dont do this. We apply transformations left or right whatever is necessary | |||
Latest | |||
====== | |||
- Runners would call an element.mergeMatrix() function that requests a native animation frame. Each runner would cancel the call made by the last runner, so that the function only runs once per frame. | |||
-https://en.wikipedia.org/wiki/Change_of_basis#Change_of_coordinates_of_a_vector | |||
# Timeline Description | |||
- [T] Timeline constructors | |||
- [T] timeline () - Returns the timeline context to the user | |||
- [T] Time Management | |||
- [T] play () - Lets the timeline keep playing from here | |||
- [T] pause () - Pauses the timeline where it currently is | |||
- [T] stop () - Pauses the timeline and sets time = 0 | |||
- [T] finish () - Moves the time to the final time for the final animation, forces declaratives to snap to their final positions | |||
- [T] speed (newSpeed) - Sets the playback speed | |||
- [T] seek (dt) - Scrubs the timeline time forward or backward by dt | |||
- [T] time (t) - Sets the absolute time to t | |||
- [T] backwards (back) - Sets the speed to (back ? speed : -speed) | |||
- [T] position (p) - sets the position in range [0, 1] | |||
- [T] loop (times, swing, waits) | |||
- [T] Runner Management | |||
- [T] remove(tagOrRunner, end) - Removes all runners with tag from the timeline | |||
- [T] reset () - Deletes all of the runners and resets the timeline | |||
- [T] persist (tag, lifetime) - how long to keep a reference to an animation after it is completed | |||
- [T] schedule (tag, time, when) - move the start time of the runner to time otherwise, returns all of the scheduled runners start and end times. | |||
- [T] Hidden Methods | |||
- [x] `_step (dt)` | |||
- [x] `_continue ()` | |||
# Runner | |||
- [x] Constructors | |||
- [x] animate (duration, delay, when) - Makes a new runner and returns the timeline context to the user with the new runner active. | |||
- [x] loop (duration, times, swing) - Makes a new runner with the looping set as described by the parameters, returns timeline | |||
- [x] delay (by, when) - Makes a new runner to start <by> ms after the last active runner is complete | |||
- [x] Runner Methods | |||
- [x] element (svgElement) - Given an element, you can bind it directly | |||
- [x] animate (args) - Calls animate if we have an element set | |||
- [x] loop (args) - Calls loop with arguments if we have an element | |||
- [x] delay (args) - calls delay if we have an element | |||
- [x] Runner Events | |||
- [x] on (eventName, fn) - Binds a function to an event | |||
- [x] off (eventName) - Unbinds all function from that event | |||
- [x] fire () - Fires an event | |||
- [x] Basic Functionality | |||
- [x] queue (initFn, runFn, alwaysInitialise) - Given two functions, the runner will run initFn once, and run runFn on every step. If alwaysInitialise is true, it will always run the initialisation as well. | |||
- [x] during (runFn) - The function to run on each frame | |||
- [x] Runner Animation Methods | |||
- [x] time (time) - Sets the time to the given time and runs the runner | |||
- [x] step (dt) - Runs the runner method if | |||
- [x] finish () - runs step with dT = Infinity | |||
- [x] reverse () - Makes non-declarative runners play backwards | |||
- [x] ease (fn) - Sets the easing function, can not be used to convert a non-declarative to a declarative animation. | |||
- [x] active (activated) - Activates or deactivates a runner | |||
- [x] loop (o) - Activates a loop sequence | |||
- [x] Runner Management | |||
- [x] tag (name) - Name a runner or act as a getter | |||
- [x] untag () |
@@ -1,330 +0,0 @@ | |||
# Tagged Animations | |||
The user can tag and control the runner for any animation | |||
```js | |||
var animation = element | |||
.loop(300, true) | |||
.tag('first') | |||
.rotate(360) | |||
.translate(50) | |||
.animate(300, 200) | |||
.tag('second') | |||
.scale(3) | |||
element.timeline.finish() | |||
element.timeline.pause() | |||
element.timeline.stop() | |||
element.timeline.play() | |||
``` | |||
# Absolute Timeline Times | |||
The user can specify their time which is relative to the timelines time. | |||
```js | |||
var animation = element.animate(2000).move(200, 200) | |||
// after 1000 ms | |||
animation.animate(1000, 500, 'absolute').scale(2) | |||
var runner = elemenet.move(0, 0).animate(1000) | |||
// after 500ms | |||
runner.move(200, 200) | |||
``` | |||
This block of code would: | |||
- Spend the first 1000ms moving the element | |||
- At this time, it will snap the scale to 1.5 (halfway to 2) | |||
- After this time, the scale and the move should go together | |||
# Rotating While Scaling | |||
The user may want to run multiple animations concurrently and have | |||
control over each animation that they define. | |||
```js | |||
let animationA = element.loop(300, ">").rotate(360) | |||
let animationB = element.loop(200, "><").scale(2) | |||
// Maybe they want to disable a runner - which acts like pausing | |||
animationB.active(false) | |||
// Maybe they want to remove an animation matching a tag | |||
animationB.tag('B') | |||
element.timeline().remove('B') | |||
// They can move around a runner as well | |||
element.timeline() | |||
.schedule('B', 300, 'absolute') // Moves a runner to start at 300 | |||
// time(currentAbsolute - newAbsolute) | |||
.shift('B', 300) // Shifts the runner start time by 300 | |||
// which is sugar to | |||
.schedule('B', 300, 'relative') | |||
// seek(shiftTime) | |||
``` | |||
Lets demonstrate the difference between the schedule and shift | |||
``` | |||
Given this: | |||
-------- | |||
-------------- | |||
---------------- | |||
Schedule: | |||
-------- | |||
-------------- | |||
---------------- | |||
Shift: | |||
-------- | |||
-------------- | |||
---------------- | |||
``` | |||
# A Sequenced Animation | |||
The user might want to be able to run a long sequenced animation that they have | |||
predesigned as they please. | |||
```js | |||
let timeline = element.loop(300, "><").scale(2) | |||
.animate(300).rotate(30) | |||
.animate(300, 200).fill(blue) | |||
// They might want to move forwards or backwards | |||
timeline.seek(-300) | |||
// They might want to set a specific time | |||
timeline.time(200) | |||
// Or a specific position | |||
timeline.position(0.3) | |||
// Maybe they want to clear the timeline | |||
timeline.reset() | |||
``` | |||
# User wants to Loop Something | |||
If the user wants to loop something, they should be able to call the loop | |||
method at any time, and it will just change the behaviour of the current | |||
runner. If we are running declaratively, we will throw an error. | |||
## Correct Usages | |||
They can invoke this from the timeline | |||
```js | |||
element.loop(duration, times, swing) | |||
``` | |||
If they want to work with absolute times, they should animate first | |||
```js | |||
element.animate(300, 200, true) | |||
.loop(Infinity, true) | |||
``` | |||
Or alternatively, they could equivalently do this: | |||
```js | |||
element.loop({ | |||
now: true, | |||
times: Infinity, | |||
delay: 200, | |||
duration: 300, | |||
swing: true, | |||
wait: [200, 300] | |||
}) | |||
``` | |||
## Error Case | |||
# Declarative Animations | |||
The user might want to have something chase their mouse around. This would | |||
require a declarative animation. | |||
```js | |||
el.animate((curr, target, dt, ctx) => { | |||
// Find the error and the value | |||
let error = target - current | |||
ctx.speed = (ctx.error - error) / dt | |||
ctx.error = error | |||
return newPos | |||
}) | |||
SVG.on(document, 'mousemove', (ev) => { | |||
el.timeline(controller) | |||
.move(ev.pageX, ev.pageY) | |||
}) | |||
``` | |||
## Springy Mouse Chaser | |||
Pretend we gave the user a springy controller that basically springs to a | |||
target in 300ms for example. They might be constantly changing the target with: | |||
```js | |||
el.animate(Spring(500), 200) | |||
.tag('declarative') | |||
.persist() | |||
.move(10, 10) | |||
el.animate('declarative') | |||
.move(300, 200) | |||
SVG.on(document, 'mousemove', function (ev) { | |||
el.animate(springy, 200) | |||
.tag('declarative') | |||
.move(ev.pageX, ev.pageY) | |||
}) | |||
``` | |||
# Repeated Animations | |||
The user might want to duplicate an animation and have it rerun a few times | |||
```js | |||
// User makes two copies of an animation | |||
let animA = el.animate(300, 300, 'now')...(animation)... | |||
let animB = animA.clone() // Deep copy | |||
// Now let the user attach and reschedule their animations | |||
el.timeline() | |||
.schedule(animA, 500, 'absolute') | |||
.schedule(animB, 2000, 'absolute') | |||
``` | |||
Then the user can loop the timeline, by changing its play mode | |||
```js | |||
el.timeline() | |||
.loop(times, swing, waits) | |||
``` | |||
# Advanced Animations | |||
The user can create their own runners and then attach it to the timeline | |||
themselves if they like. | |||
```js | |||
// They declare their animation | |||
let rotation = () => new SVG.Runner().rotate(500) | |||
// They attach an element, and schedule the runner | |||
let leftAnimation = rotation().element(leftSquare).reverse() | |||
// They might want to animate another | |||
let rightAnimation = rotation().element(rightSquare) | |||
// They can schedule these two runners to a master element | |||
timelineElement.timeline() | |||
.schedule(leftAnimation, 300, 'absolute') | |||
.schedule(rightAnimation, 500, 'now') | |||
.schedule(rightAnimation, 300, 'end') | |||
// Or they can schedule it to a timeline as well | |||
let timeline = new SVG.Timeline() | |||
.schedule(leftAnimation, 300, 'absolute') | |||
.schedule(rightAnimation, 500, 'now') | |||
``` | |||
# Modifying Controller Parameters | |||
Some user might want to change the speed of a controller, or how the controller | |||
works in the middle of an animation. For example, they might do: | |||
```js | |||
var pid = PID(30, 20, 40) | |||
let animation = el.animate(pid).move(.., ..) | |||
// Some time later, the user slides a slider, and they can do: | |||
slider1.onSlide( v => pid.p(v) ) | |||
``` | |||
# Bidirectional Scheduling **(TODO)** | |||
We would like to schedule a runner to a timeline, or to do the opposite | |||
```js | |||
// If we have a runner and a timeline | |||
let timeline = new Timeline()... | |||
let runner = new Runner()... | |||
// Since the user can schedule a runner onto a timeline | |||
timeline.schedule(runner, ...rest) | |||
// It should be possible to do the opposite | |||
runner.schedule(timeline, ...rest) | |||
// It could be Implemented like this | |||
runner.schedule = (t, duration, delay, now) { | |||
this._timeline.remove(this) // Should work even if its not scheduled | |||
t.schedule(this, duration, delay, now) | |||
return this | |||
} | |||
// The benefit would be that they could call animate afterwards: eg: | |||
runner.schedule(timeline, ...rest) | |||
.animate()... | |||
``` | |||
# Binding Events | |||
The user might want to react to some events that the runner might emit. We will | |||
emit the following events from the runner: | |||
- start - when a runner first initialises | |||
- finish - when a runner finishes | |||
- step - on every step | |||
Maybe they also want to react to timeline events as well |