diff --git a/src/internal/animations.js b/src/internal/animations.js index 355c0254e7..031bacc920 100644 --- a/src/internal/animations.js +++ b/src/internal/animations.js @@ -1,4 +1,5 @@ import { transitionManager, linear, generateRule, hash } from './transitions.js'; +import { loop } from './loop.js'; export function wrapAnimation(node, from, fn, params) { if (!from) return; @@ -68,7 +69,35 @@ export function wrapAnimation(node, from, fn, params) { } }; - transitionManager.add(animation); + // transitionManager.add(animation); + + transitionManager.active += 1; + + const { abort, promise } = loop(() => { + const now = window.performance.now(); + + if (animation.program && now >= animation.program.end) { + animation.done(); + } + + if (animation.pending && now >= animation.pending.start) { + animation.start(animation.pending); + } + + if (animation.running) { + animation.update(now); + return true; + } + }); + + promise.then(() => { + transitionManager.active -= 1; + if (!transitionManager.active) { + let i = transitionManager.stylesheet.cssRules.length; + while (i--) transitionManager.stylesheet.deleteRule(i); + transitionManager.activeRules = {}; + } + }); if (info.tick) info.tick(0, 1); diff --git a/src/internal/index.js b/src/internal/index.js index 6b134b677d..f3654f6b77 100644 --- a/src/internal/index.js +++ b/src/internal/index.js @@ -3,6 +3,7 @@ export * from './await-block.js'; export * from './dom.js'; export * from './keyed-each.js'; export * from './lifecycle.js'; +export * from './loop.js'; export * from './scheduler.js'; export * from './spread.js'; export * from './ssr.js'; diff --git a/src/internal/loop.js b/src/internal/loop.js new file mode 100644 index 0000000000..602a910cba --- /dev/null +++ b/src/internal/loop.js @@ -0,0 +1,38 @@ +const tasks = new Set(); +let running = false; + +function run_tasks() { + tasks.forEach(task => { + if (!task[0]()) { + tasks.delete(task); + task[1](); + } + }); + + running = tasks.size > 0; + if (running) requestAnimationFrame(run_tasks); +} + +export function clear_loops() { + // for testing... + tasks.forEach(task => tasks.delete(task)); + running = false; +} + +export function loop(fn) { + let task; + + if (!running) { + running = true; + requestAnimationFrame(run_tasks); + } + + return { + promise: new Promise(fulfil => { + tasks.add(task = [fn, fulfil]); + }), + abort() { + tasks.delete(task); + } + }; +} \ No newline at end of file diff --git a/src/internal/transitions.js b/src/internal/transitions.js index 98418556a0..f90fa3d590 100644 --- a/src/internal/transitions.js +++ b/src/internal/transitions.js @@ -1,5 +1,6 @@ import { createElement } from './dom.js'; import { noop, run } from './utils.js'; +import { loop } from './loop.js'; export function linear(t) { return t; @@ -83,8 +84,35 @@ export function wrapTransition(component, node, fn, params, intro) { } if (!this.running) { + transitionManager.active += 1; + this.running = true; - transitionManager.add(this); + // transitionManager.add(this); + const { abort, promise } = loop(() => { + const now = window.performance.now(); + + if (this.program && now >= this.program.end) { + this.done(); + } + + if (this.pending && now >= this.pending.start) { + this.start(this.pending); + } + + if (this.running) { + this.update(now); + return true; + } + }); + + promise.then(() => { + transitionManager.active -= 1; + if (!transitionManager.active) { + let i = transitionManager.stylesheet.cssRules.length; + while (i--) transitionManager.stylesheet.deleteRule(i); + transitionManager.activeRules = {}; + } + }); } }, @@ -173,6 +201,7 @@ export function groupOutros() { } export var transitionManager = { + active: 0, running: false, transitions: [], bound: null, @@ -180,15 +209,6 @@ export var transitionManager = { activeRules: {}, promise: null, - add(transition) { - this.transitions.push(transition); - - if (!this.running) { - this.running = true; - requestAnimationFrame(this.bound || (this.bound = this.next.bind(this))); - } - }, - addRule(rule, name) { if (!this.stylesheet) { const style = createElement('style'); @@ -202,40 +222,6 @@ export var transitionManager = { } }, - next() { - this.running = false; - - const now = window.performance.now(); - let i = this.transitions.length; - - while (i--) { - const transition = this.transitions[i]; - - if (transition.program && now >= transition.program.end) { - transition.done(); - } - - if (transition.pending && now >= transition.pending.start) { - transition.start(transition.pending); - } - - if (transition.running) { - transition.update(now); - this.running = true; - } else if (!transition.pending) { - this.transitions.splice(i, 1); - } - } - - if (this.running) { - requestAnimationFrame(this.bound); - } else if (this.stylesheet) { - let i = this.stylesheet.cssRules.length; - while (i--) this.stylesheet.deleteRule(i); - this.activeRules = {}; - } - }, - deleteRule(node, name) { node.style.animation = node.style.animation .split(', ') diff --git a/src/motion/spring.js b/src/motion/spring.js index ebac993f06..7d68377bd0 100644 --- a/src/motion/spring.js +++ b/src/motion/spring.js @@ -1,5 +1,6 @@ import { writable } from 'svelte/store'; -import { add_task, is_date } from './utils.js'; +import { loop } from 'svelte/internal'; +import { is_date } from './utils.js'; function get_initial_velocity(value) { if (typeof value === 'number' || is_date(value)) return 0; @@ -116,7 +117,7 @@ export function spring(value, opts = {}) { last_time = window.performance.now(); settled = false; - task = add_task(() => { + task = loop(() => { const time = window.performance.now(); ({ value, settled } = tick_spring( diff --git a/src/motion/tweened.js b/src/motion/tweened.js index 020378408c..21ff8f70c7 100644 --- a/src/motion/tweened.js +++ b/src/motion/tweened.js @@ -1,7 +1,7 @@ import { writable } from 'svelte/store'; -import { assign } from 'svelte/internal'; +import { assign, loop } from 'svelte/internal'; import { linear } from 'svelte/easing'; -import { add_task, is_date } from './utils.js'; +import { is_date } from './utils.js'; function get_interpolator(a, b) { if (a === b || a !== a) return () => a; @@ -76,7 +76,7 @@ export function tweened(value, defaults = {}) { const start = window.performance.now() + delay; let fn; - task = add_task(() => { + task = loop(() => { const time = window.performance.now(); if (time < start) return true; diff --git a/src/motion/utils.js b/src/motion/utils.js index 986cec3ef4..97a764bf46 100644 --- a/src/motion/utils.js +++ b/src/motion/utils.js @@ -1,36 +1,3 @@ -const tasks = new Set(); -let running = false; - -function run_tasks() { - tasks.forEach(task => { - if (!task[0]()) { - tasks.delete(task); - task[1](); - } - }); - - running = tasks.size > 0; - if (running) requestAnimationFrame(run_tasks); -} - -export function add_task(fn) { - let task; - - if (!running) { - running = true; - requestAnimationFrame(run_tasks); - } - - return { - promise: new Promise(fulfil => { - tasks.add(task = [fn, fulfil]); - }), - abort() { - tasks.delete(task); - } - }; -} - export function is_date(obj) { return Object.prototype.toString.call(obj) === '[object Date]'; } \ No newline at end of file diff --git a/test/runtime/index.js b/test/runtime/index.js index 88e2d0fe84..5569b444d1 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -3,7 +3,7 @@ import * as path from "path"; import * as fs from "fs"; import { rollup } from 'rollup'; import * as virtual from 'rollup-plugin-virtual'; -import { transitionManager } from "../../internal.js"; +import { transitionManager, clear_loops } from "../../internal.js"; import { showOutput, @@ -96,6 +96,7 @@ describe("runtime", () => { // set of hacks to support transition tests transitionManager.running = false; transitionManager.transitions = []; + clear_loops(); const raf = { time: 0,