summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2018-12-07 15:35:41 +0100
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2018-12-07 15:35:41 +0100
commit7b02d60829d1151a9fd1e726a0a995b92b165328 (patch)
tree5a7c86988fedd7420d45336ee36edf98e109e8a1
parent5161dfdb3a08490da0ae1c5c8b6515eb0ae0da30 (diff)
downloadsvg.js-7b02d60829d1151a9fd1e726a0a995b92b165328.tar.gz
svg.js-7b02d60829d1151a9fd1e726a0a995b92b165328.zip
Release 3.0.43.0.4
-rw-r--r--CHANGELOG.md10
-rw-r--r--package.json2
-rw-r--r--spec/spec/types/Box.js19
-rw-r--r--src/animation/Animator.js21
-rw-r--r--src/animation/Queue.js2
-rw-r--r--src/animation/Runner.js45
-rw-r--r--src/animation/Timeline.js79
-rw-r--r--src/elements/Dom.js3
-rw-r--r--src/elements/Svg.js4
-rw-r--r--src/types/Box.js32
10 files changed, 158 insertions, 59 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3611fb8..b53f179 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,15 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
====
+## [3.0.4] - 2018-12-07
+
+### Fixed
+- fixed `zoom` which was added correctly and is animatable now
+- fixed `Runner` which merges transformations on the correct frame and in the correct way now
+- fixed condition on which transforms get deleted from an element when animating
+- fixed `Timeline` which executes Runner in the correct order now
+- fixed `Svg` which correctly deletes the defs reference on `clear()`
+
## [3.0.3] - 2018-12-05
### Fixed
@@ -745,6 +754,7 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
<!-- Headings above link to the releases listed here -->
+[3.0.4]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.4
[3.0.3]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.3
[3.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.2
[3.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.1
diff --git a/package.json b/package.json
index 9e11632..6485a38 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@svgdotjs/svg.js",
- "version": "3.0.3",
+ "version": "3.0.4",
"description": "A lightweight library for manipulating and animating SVG.",
"url": "https://svgdotjs.github.io/",
"homepage": "https://svgdotjs.github.io/",
diff --git a/spec/spec/types/Box.js b/spec/spec/types/Box.js
index 1e98982..56eb7da 100644
--- a/spec/spec/types/Box.js
+++ b/spec/spec/types/Box.js
@@ -8,7 +8,7 @@ import {
import { getMethodsFor } from '../../../src/utils/methods.js'
import { getWindow, withWindow } from '../../../src/utils/window.js'
-const viewbox = getMethodsFor('viewbox').viewbox
+const { zoom, viewbox} = getMethodsFor('viewbox')
const { any, objectContaining, arrayContaining } = jasmine
@@ -175,5 +175,22 @@ describe('Box.js', () => {
expect(viewbox.call(canvas).toArray()).toEqual([10, 10, 200, 200])
})
})
+
+ describe('zoom()', () => {
+ it('zooms around the center by default', () => {
+ const canvas = zoom.call(SVG().size(100, 50).viewbox(0, 0, 100, 50).addTo(container), 2)
+ expect(canvas.attr('viewBox')).toEqual('25 12.5 50 25')
+ })
+
+ it('zooms around a point', () => {
+ const canvas = zoom.call(SVG().size(100, 50).viewbox(0, 0, 100, 50).addTo(container), 2, [0, 0])
+ expect(canvas.attr('viewBox')).toEqual('0 0 50 25')
+ })
+
+ it('gets the zoom', () => {
+ const canvas = zoom.call(SVG().size(100, 50).viewbox(0, 0, 100, 50).addTo(container), 2)
+ expect(zoom.call(canvas)).toEqual(2)
+ })
+ })
})
})
diff --git a/src/animation/Animator.js b/src/animation/Animator.js
index 64570eb..390b200 100644
--- a/src/animation/Animator.js
+++ b/src/animation/Animator.js
@@ -5,6 +5,7 @@ const Animator = {
nextDraw: null,
frames: new Queue(),
timeouts: new Queue(),
+ immediates: new Queue(),
timer: () => globals.window.performance || globals.window.Date,
transforms: [],
@@ -38,6 +39,17 @@ const Animator = {
return node
},
+ immediate (fn) {
+ // Add the immediate fn to the end of the queue
+ var node = Animator.immediates.push(fn)
+ // Request another animation frame if we need one
+ if (Animator.nextDraw === null) {
+ Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)
+ }
+
+ return node
+ },
+
cancelFrame (node) {
node != null && Animator.frames.remove(node)
},
@@ -46,6 +58,10 @@ const Animator = {
node != null && Animator.timeouts.remove(node)
},
+ cancelImmediate (node) {
+ node != null && Animator.immediates.remove(node)
+ },
+
_draw (now) {
// Run all the timeouts we can run, if they are not ready yet, add them
// to the end of the queue immediately! (bad timeouts!!! [sarcasm])
@@ -70,6 +86,11 @@ const Animator = {
nextFrame.run()
}
+ var nextImmediate = null
+ while ((nextImmediate = Animator.immediates.shift())) {
+ nextImmediate()
+ }
+
// If we have remaining timeouts or frames, draw until we don't anymore
Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first()
? globals.window.requestAnimationFrame(Animator._draw)
diff --git a/src/animation/Queue.js b/src/animation/Queue.js
index 14b92b4..0d3cdcd 100644
--- a/src/animation/Queue.js
+++ b/src/animation/Queue.js
@@ -18,7 +18,7 @@ export default class Queue {
this._first = item
}
- // Update the length and return the current item
+ // Return the current item
return item
}
diff --git a/src/animation/Runner.js b/src/animation/Runner.js
index 6a085b6..8c26578 100644
--- a/src/animation/Runner.js
+++ b/src/animation/Runner.js
@@ -168,7 +168,7 @@ export default class Runner extends EventTarget {
}
after (fn) {
- return this.on('finish', fn)
+ return this.on('finished', fn)
}
/*
@@ -276,7 +276,8 @@ export default class Runner extends EventTarget {
// Figure out if we just started
var duration = this.duration()
var justStarted = this._lastTime <= 0 && this._time > 0
- var justFinished = this._lastTime < this._time && this.time > duration
+ var justFinished = this._lastTime < duration && this._time >= duration
+
this._lastTime = this._time
if (justStarted) {
this.fire('start', this)
@@ -304,15 +305,15 @@ export default class Runner extends EventTarget {
// correct the done flag here
// declaritive animations itself know when they converged
this.done = this.done || (converged && declarative)
- if (this.done) {
- this.fire('finish', this)
+ if (justFinished) {
+ this.fire('finished', this)
}
return this
}
reset () {
if (this._reseted) return this
- this.loops(0)
+ this.time(0)
this._reseted = true
return this
}
@@ -443,7 +444,7 @@ export default class Runner extends EventTarget {
// TODO: Keep track of all transformations so that deletion is faster
clearTransformsFromQueue () {
- if (!this.done || !this._timeline || !this._timeline._order.includes(this)) {
+ if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
this._queue = this._queue.filter((item) => {
return !item.isTransform
})
@@ -530,18 +531,10 @@ class RunnerArray {
add (runner) {
if (this.runners.includes(runner)) return
-
let id = runner.id + 1
- let leftSibling = this.ids.reduce((last, curr) => {
- if (curr > last && curr < id) return curr
- return last
- }, 0)
-
- let index = this.ids.indexOf(leftSibling) + 1
-
- this.ids.splice(index, 0, id)
- this.runners.splice(index, 0, runner)
+ this.runners.push(runner)
+ this.ids.push(id)
return this
}
@@ -564,10 +557,11 @@ class RunnerArray {
const condition = lastRunner
&& runner.done && lastRunner.done
// don't merge runner when persisted on timeline
- && (!runner._timeline || !runner._timeline._order.includes(runner.id))
- && (!lastRunner._timeline || !lastRunner._timeline._order.includes(lastRunner.id))
+ && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id))
+ && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id))
if (condition) {
+ // the +1 happens in the function
this.remove(runner.id)
this.edit(lastRunner.id, runner.mergeWith(lastRunner))
}
@@ -580,7 +574,7 @@ class RunnerArray {
edit (id, newRunner) {
let index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1, id)
+ this.ids.splice(index, 1, id + 1)
this.runners.splice(index, 1, newRunner)
return this
}
@@ -636,11 +630,10 @@ registerMethods({
this._transformationRunners.add(runner)
// Make sure that the runner merge is executed at the very end of
- // all Animator functions. Thats why we use setTimeout here
- setTimeout(() => {
- Animator.cancelFrame(this._frameId)
- this._frameId = Animator.frame(mergeTransforms.bind(this))
- }, 0)
+ // all Animator functions. Thats why we use immediate here to execute
+ // the merge right after all frames are run
+ Animator.cancelImmediate(this._frameId)
+ this._frameId = Animator.immediate(mergeTransforms.bind(this))
},
_prepareRunner () {
@@ -689,7 +682,7 @@ extend(Runner, {
var morpher = new Morphable(this._stepper).to(new SVGNumber(level))
this.queue(function () {
- morpher = morpher.from(this.zoom())
+ morpher = morpher.from(this.element().zoom())
}, function (pos) {
this.element().zoom(morpher.at(pos), point)
return morpher.done()
@@ -804,8 +797,8 @@ extend(Runner, {
currentAngle = affineParameters.rotate
current = new Matrix(affineParameters)
- element._addRunner(this)
this.addTransform(current)
+ element._addRunner(this)
return morpher.done()
}
diff --git a/src/animation/Timeline.js b/src/animation/Timeline.js
index 56198e0..f5460b3 100644
--- a/src/animation/Timeline.js
+++ b/src/animation/Timeline.js
@@ -33,7 +33,8 @@ export default class Timeline extends EventTarget {
this._nextFrame = null
this._paused = true
this._runners = []
- this._order = []
+ this._runnerIds = []
+ this._lastRunnerId = -1
this._time = 0
this._lastSourceTime = 0
this._lastStepTime = 0
@@ -45,7 +46,7 @@ export default class Timeline extends EventTarget {
// schedules a runner on the timeline
schedule (runner, delay, when) {
if (runner == null) {
- return this._order.map((id) => makeSchedule(this._runners[id]))
+ return this._runners.map(makeSchedule)
}
// The start time for the next animation can either be given explicitly,
@@ -80,34 +81,37 @@ export default class Timeline extends EventTarget {
runner.timeline(this)
const persist = runner.persist()
-
- // Save runnerInfo
- this._runners[runner.id] = {
+ const runnerInfo = {
persist: persist === null ? this._persist : persist,
- runner: runner,
- start: absoluteStartTime + delay
+ start: absoluteStartTime + delay,
+ runner
}
- // Save order, update Time if needed and continue
- this._order.push(runner.id)
+ this._lastRunnerId = runner.id
+
+ this._runners.push(runnerInfo)
+ this._runners.sort((a, b) => a.start - b.start)
+ this._runnerIds = this._runners.map(info => info.runner.id)
+
this.updateTime()._continue()
return this
}
// Remove the runner from this timeline
unschedule (runner) {
- var index = this._order.indexOf(runner.id)
+ var index = this._runnerIds.indexOf(runner.id)
if (index < 0) return this
- delete this._runners[runner.id]
- this._order.splice(index, 1)
+ this._runners.splice(index, 1)
+ this._runnerIds.splice(index, 1)
+
runner.timeline(null)
return this
}
// Calculates the end of the timeline
getEndTime () {
- var lastRunnerInfo = this._runners[this._order[this._order.length - 1]]
+ var lastRunnerInfo = this._runners[this._runnerIds.indexOf(this._lastRunnerId)]
var lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0
var lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : 0
return lastStartTime + lastDuration
@@ -200,12 +204,39 @@ export default class Timeline extends EventTarget {
this._lastStepTime = this._time
this.fire('time', this._time)
+ // This is for the case that the timeline was seeked so that the time
+ // is now before the startTime of the runner. Thats why we need to set
+ // the runner to position 0
+
+ // FIXME:
+ // However, reseting in insertion order leads to bugs. Considering the case,
+ // where 2 runners change the same attriute but in different times,
+ // reseting both of them will lead to the case where the later defined
+ // runner always wins the reset even if the other runner started earlier
+ // and therefore should win the attribute battle
+ // this can be solved by reseting them backwards
+ for (var k = this._runners.length; k--;) {
+ // Get and run the current runner and ignore it if its inactive
+ let runnerInfo = this._runners[k]
+ let runner = runnerInfo.runner
+
+ // Make sure that we give the actual difference
+ // between runner start time and now
+ let dtToStart = this._time - runnerInfo.start
+
+ // Dont run runner if not started yet
+ // and try to reset it
+ if (dtToStart <= 0) {
+ runner.reset()
+ }
+ }
+
// Run all of the runners directly
var runnersLeft = false
- for (var i = 0, len = this._order.length; i < len; i++) {
+ for (var i = 0, len = this._runners.length; i < len; i++) {
// Get and run the current runner and ignore it if its inactive
- var runnerInfo = this._runners[this._order[i]]
- var runner = runnerInfo.runner
+ let runnerInfo = this._runners[i]
+ let runner = runnerInfo.runner
let dt = dtTime
// Make sure that we give the actual difference
@@ -215,11 +246,6 @@ export default class Timeline extends EventTarget {
// Dont run runner if not started yet
if (dtToStart <= 0) {
runnersLeft = true
-
- // This is for the case that the timeline was seeked so that the time
- // is now before the startTime of the runner. Thats why we need to set
- // the runner to position 0
- runner.reset()
continue
} else if (dtToStart < dt) {
// Adjust dt to make sure that animation is on point
@@ -236,21 +262,20 @@ export default class Timeline extends EventTarget {
// continue
} else if (runnerInfo.persist !== true) {
// runner is finished. And runner might get removed
-
var endTime = runner.duration() - runner.time() + this._time
- if (endTime + this._persist < this._time) {
+ if (endTime + runnerInfo.persist < this._time) {
// Delete runner and correct index
- delete this._runners[this._order[i]]
- this._order.splice(i--, 1) && --len
- runner.timeline(null)
+ runner.unschedule()
+ --i
+ --len
}
}
}
// Basically: we continue when there are runners right from us in time
// when -->, and when runners are left from us when <--
- if ((runnersLeft && !(this._speed < 0 && this._time === 0)) || (this._order.length && this._speed < 0 && this._time > 0)) {
+ if ((runnersLeft && !(this._speed < 0 && this._time === 0)) || (this._runnerIds.length && this._speed < 0 && this._time > 0)) {
this._continue()
} else {
this.fire('finished')
diff --git a/src/elements/Dom.js b/src/elements/Dom.js
index 7e22b05..458ebbc 100644
--- a/src/elements/Dom.js
+++ b/src/elements/Dom.js
@@ -58,9 +58,6 @@ export default class Dom extends EventTarget {
this.node.removeChild(this.node.lastChild)
}
- // remove defs reference
- delete this._defs
-
return this
}
diff --git a/src/elements/Svg.js b/src/elements/Svg.js
index ab7d89f..53b488c 100644
--- a/src/elements/Svg.js
+++ b/src/elements/Svg.js
@@ -62,6 +62,10 @@ export default class Svg extends Container {
while (this.node.hasChildNodes()) {
this.node.removeChild(this.node.lastChild)
}
+
+ // remove defs reference
+ delete this._defs
+
return this
}
}
diff --git a/src/types/Box.js b/src/types/Box.js
index 3df1367..eb43d07 100644
--- a/src/types/Box.js
+++ b/src/types/Box.js
@@ -2,6 +2,7 @@ import { delimiter } from '../modules/core/regex.js'
import { globals } from '../utils/window.js'
import { register } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
+import Matrix from './Matrix.js'
import Point from './Point.js'
import parser from '../modules/core/parser.js'
@@ -150,6 +151,37 @@ registerMethods({
// act as setter
return this.attr('viewBox', new Box(x, y, width, height))
+ },
+
+ zoom (level, point) {
+ var style = window.getComputedStyle(this.node)
+
+ var width = parseFloat(style.getPropertyValue('width'))
+
+ var height = parseFloat(style.getPropertyValue('height'))
+
+ var v = this.viewbox()
+
+ var zoomX = width / v.width
+
+ var zoomY = height / v.height
+
+ var zoom = Math.min(zoomX, zoomY)
+
+ if (level == null) {
+ return zoom
+ }
+
+ var zoomAmount = zoom / level
+ if (zoomAmount === Infinity) zoomAmount = Number.MIN_VALUE
+
+ point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y)
+
+ var box = new Box(v).transform(
+ new Matrix({ scale: zoomAmount, origin: point })
+ )
+
+ return this.viewbox(box)
}
}
})