- 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 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 `SVG.List` (#645) | ||||
- added `words()` and `element()` to `Dom` because of (#935) | - 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 | ||||
- removed `SVG.Array.split()` function | - removed `SVG.Array.split()` function | ||||
- removed `SVG.Nested` (#809) | - removed `SVG.Nested` (#809) | ||||
- removed `show()` from `SVG.A` to avoid name clash (#802) | - removed `show()` from `SVG.A` to avoid name clash (#802) | ||||
- removed `size()` from `SVG.Text` to avoid name clash (#799) | - 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 `native()` function | ||||
- removed `Bare` in favour of `Dom` (#935) | - removed `Bare` in favour of `Dom` (#935) | ||||
Copyright (c) 2012-2017 Wout Fierens | |||||
Copyright (c) 2012-2018 Wout Fierens | |||||
https://svgdotjs.github.io/ | https://svgdotjs.github.io/ | ||||
Permission is hereby granted, free of charge, to any person obtaining | Permission is hereby granted, free of charge, to any person obtaining |
<!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> |
</body> | </body> | ||||
<script src="../../dist/svg.js" charset="utf-8"></script> | <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> | </html> |
// Add the squares | // Add the squares | ||||
for ( let i = 0; i < 20; i++ ) { | 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) | let rect = group.rect(100, 100) | ||||
.x( 20 + 100 * i ) | .x( 20 + 100 * i ) | ||||
.fill( color ) | .fill( color ) |
} | } | ||||
// Bind the drag tracker to this element directly | // Bind the drag tracker to this element directly | ||||
let parent = element.doc() | |||||
let parent = element.root() | |||||
let point = new SVG.Point() | let point = new SVG.Point() | ||||
element.mousedown(startDrag).touchstart(startDrag) | element.mousedown(startDrag).touchstart(startDrag) | ||||
} | } |
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<title>SVG Playground</title> | <title>SVG Playground</title> | ||||
<link rel="stylesheet" href="../playground.css"> | |||||
<link rel="stylesheet" href="style.css"> | |||||
</head> | </head> | ||||
<body> | <body> |
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<title>SVG Playground</title> | <title>SVG Playground</title> | ||||
<link rel="stylesheet" href="../playground.css"> | |||||
<link rel="stylesheet" href="style.css"> | |||||
</head> | </head> | ||||
<body> | <body> |
<!-- include source files here... --> | <!-- include source files here... --> | ||||
<script src="../dist/polyfills.js" charset="utf-8"></script> | <script src="../dist/polyfills.js" charset="utf-8"></script> | ||||
<script src="../dist/polyfillsIE.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> | </head> | ||||
<!--<script src="es5TestBundle.js"></script>--> | <!--<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/arrange.js"></script> | ||||
<script src="spec/array.js"></script> | <script src="spec/array.js"></script> | ||||
<script src="spec/bare.js"></script> | <script src="spec/bare.js"></script> | ||||
<script src="spec/boxes.js"></script> | <script src="spec/boxes.js"></script> | ||||
<script src="spec/circle.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/color.js"></script> | ||||
<!-- <script src="spec/container.js"></script> | |||||
<script src="spec/container.js"></script> | |||||
<script src="spec/defs.js"></script> | <script src="spec/defs.js"></script> | ||||
<script src="spec/doc.js"></script> | <script src="spec/doc.js"></script> | ||||
<script src="spec/easing.js"></script> | <script src="spec/easing.js"></script> | ||||
<script src="spec/morphing.js"></script> | <script src="spec/morphing.js"></script> | ||||
<script src="spec/animator.js"></script> | <script src="spec/animator.js"></script> | ||||
<script src="spec/runner.js"></script> | <script src="spec/runner.js"></script> | ||||
<script src="spec/queue.js"></script> --> | |||||
<script src="spec/queue.js"></script> | |||||
</body> | </body> | ||||
</html> | </html> |
import { globals } from '../../utils/window.js' | import { globals } from '../../utils/window.js' | ||||
let listenerId = 0 | 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 = {} | if (!n.events) n.events = {} | ||||
return 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 = {} | if (n.events) n.events = {} | ||||
} | } | ||||
// Add event binder in the SVG namespace | // Add event binder in the SVG namespace | ||||
export function on (node, events, listener, binding, options) { | export function on (node, events, listener, binding, options) { | ||||
var l = listener.bind(binding || node) | 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 can be an array of events or a string of events | ||||
events = Array.isArray(events) ? events : events.split(delimiter) | events = Array.isArray(events) ? events : events.split(delimiter) | ||||
// Add event unbinder in the SVG namespace | // Add event unbinder in the SVG namespace | ||||
export function off (node, events, listener, options) { | 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 | // listener can be a function or a number | ||||
if (typeof listener === 'function') { | if (typeof listener === 'function') { | ||||
off(n, event) | off(n, event) | ||||
} | } | ||||
clearEvents(node) | |||||
clearEvents(instance) | |||||
} | } | ||||
}) | }) | ||||
} | } |
if (node.instance instanceof Base) return node.instance | if (node.instance instanceof Base) return node.instance | ||||
// initialize variables | // initialize variables | ||||
var className = capitalize(node.nodeName) | |||||
var className = capitalize(node.nodeName || 'Dom') | |||||
// Make sure that gradients are adopted correctly | // Make sure that gradients are adopted correctly | ||||
if (className === 'LinearGradient' || className === 'RadialGradient') { | if (className === 'LinearGradient' || className === 'RadialGradient') { |
# 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 () |
# 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 |