Skip to content

Commit

Permalink
fix(performance): reduce object creation further
Browse files Browse the repository at this point in the history
Refs #71
  • Loading branch information
colinmeinke committed Sep 17, 2017
1 parent c92feed commit a4f6309
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 119 deletions.
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -50,8 +50,8 @@
}
},
"dependencies": {
"wilderness-core": "^2.2.1",
"wilderness-dom-node": "^1.3.1"
"wilderness-core": "^2.2.2",
"wilderness-dom-node": "^1.3.2"
},
"description": "An SVG animation API",
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion performance-tests/wilderness/single-item-morph.html
@@ -1,6 +1,6 @@
<!DOCTYPE html>
<head>
<title>Wilderness signle item morph</title>
<title>Wilderness single item morph</title>
</head>
<body>
<svg height="500" width="500" viewBox="0 0 500 500"></svg>
Expand Down
78 changes: 30 additions & 48 deletions src/render.js
Expand Up @@ -13,37 +13,6 @@ import { tick } from './timeline'
* @property {Timeline[]} timelines
*/

/**
* Appends or replaces a Shape Node within a container Node.
*
* @param {Node} container
* @param {Shape} shape
*
* @example
* appendNode(svg, shape)
*/
const appendNode = (container, shape) => {
if (shape.replace) {
shape.replace.parentNode.replaceChild(shape.node, shape.replace)
delete shape.replace
} else {
container.appendChild(shape.node)
}
}

/**
* Creates a Node for a Shape given a FrameShape.
*
* @param {Shape} shape
* @param {FrameShape} frameShape
*
* @example
* createNode(shape, frameShape)
*/
const createNode = (shape, frameShape) => {
shape.node = node(frameShape)
}

/**
* Renders Shapes or Timelines to a container Node.
*
Expand All @@ -60,29 +29,42 @@ const render = (container, ...shapesAndTimelines) => {
}
}

const { shapes, timelines } = split(shapesAndTimelines)
const shapesToRender = []
const result = split(shapesAndTimelines)
const shapes = result.shapes
const timelines = result.timelines

shapes.map(shape => {
createNode(shape, shape.keyframes[ 0 ].frameShape)
})
for (let i = 0, l = shapes.length; i < l; i++) {
const shape = shapes[ i ]
shape.node = node(shape.keyframes[ 0 ].frameShape)
shapesToRender.push(shape)
shape.rendered = true
}

timelines.map(timeline => {
for (let i = 0, l = timelines.length; i < l; i++) {
const timeline = timelines[ i ]
const timelineShapes = timeline.timelineShapes
const frameShapes = frame(timeline)

timeline.timelineShapes.map(({ shape }, i) => {
createNode(shape, frameShapes[ i ])
})
})

shapes.map(shape => {
appendNode(container, shape)
shape.rendered = true
})
for (let _i = 0, _l = timelineShapes.length; _i < _l; _i++) {
const shape = timelineShapes[ _i ].shape
shape.node = node(frameShapes[ _i ])
shapesToRender.push(shape)
}

timelines.map(timeline => {
timeline.timelineShapes.map(({ shape }) => appendNode(container, shape))
timeline.state.rendered = true
})
}

for (let i = 0, l = shapesToRender.length; i < l; i++) {
const shape = shapesToRender[ i ]

if (shape.replace) {
shape.replace.parentNode.replaceChild(shape.node, shape.replace)
delete shape.replace
} else {
container.appendChild(shape.node)
}
}

tick()
}
Expand Down
18 changes: 13 additions & 5 deletions src/shape.js
Expand Up @@ -16,7 +16,11 @@ import { shape as coreShape } from 'wilderness-core'
* shape({ el, style: '' }, { replace: el })
*/
const shape = (...props) => {
const s = coreShape(...props.map(prop => {
const args = []

for (let i = 0, l = props.length; i < l; i++) {
const prop = props[ i ]

if (prop.el) {
const p = {
...plainShapeObject(prop.el),
Expand All @@ -25,16 +29,20 @@ const shape = (...props) => {

delete p.el

return p
args.push(p)
}

return prop
}))
args.push(prop)
}

const { replace } = props.length > 1 && typeof props[ props.length - 1 ].type === 'undefined'
const s = coreShape(...args)

const options = props.length > 1 && typeof props[ props.length - 1 ].type === 'undefined'
? props[ props.length - 1 ]
: {}

const replace = options.replace

if (replace) {
if (__DEV__ && (typeof replace !== 'object' || !replace.nodeName)) {
throw new TypeError(`The replace option must be a DOM node`)
Expand Down
59 changes: 27 additions & 32 deletions src/timeline.js
Expand Up @@ -6,19 +6,7 @@ import { updateNode } from 'wilderness-dom-node'
/**
* Is the tick function running?
*/
let ticking = false

/**
* Is the Timeline rendered and playing?
*
* @param {Timeline} timeline
*
* @returns {boolean}
*
* @example
* active(timeline)
*/
const active = ({ state }) => state.started && !state.finished && state.rendered
let ticks = 0

/**
* Extends the Wilderness core play function.
Expand All @@ -37,40 +25,47 @@ const play = (t, playbackOptions, at) => {
* Calculate the active Timeline Shapes and update the corresponding Nodes.
* Call recursively until there are no longer any active Timelines.
*
* @param {boolean} [bypassTickingCheck=false]
* @param {boolean} [recurse=true]
* @param {number} [at]
*
* @example
* tick()
*/
const tick = (bypassTickingCheck = false, recurse = true, at) => {
if (!ticking || bypassTickingCheck) {
const tick = at => {
if (!ticks) {

if (__DEV__ && typeof at !== 'undefined' && typeof at !== 'number') {
throw new TypeError(`The tick functions at option must be of type number`)
}

ticking = true

window.requestAnimationFrame(() => {
const a = typeof at !== 'undefined' ? at : Date.now()
const activeTimelines = timelines.filter(active)

for (let i = 0, l = activeTimelines.length; i < l; i++) {
const t = activeTimelines[ i ]
const frameShapes = frame(t, a)
let retick = false

ticks++

for (let i = 0, l = timelines.length; i < l; i++) {
const t = timelines[ i ]
const state = t.state

t.timelineShapes.map(({ shape }, i) => {
updateNode(shape.node, frameShapes[ i ])
})
if (state.started && !state.finished && state.rendered) {
const timelineShapes = t.timelineShapes
const frameShapes = frame(t, a)

events(t)
for (let _i = 0, _l = timelineShapes.length; _i < _l; _i++) {
updateNode(timelineShapes[ _i ].shape.node, frameShapes[ _i ])
}

events(t)

retick = true
}
}

if (activeTimelines.length && recurse) {
tick(true)
} else {
ticking = false
ticks--

if (retick) {
tick()
}
})
}
Expand Down Expand Up @@ -98,5 +93,5 @@ const timeline = (...props) => {
*/
const timelines = []

export { active, play, tick }
export { play, tick }
export default timeline
28 changes: 3 additions & 25 deletions tests/timeline.js
Expand Up @@ -3,29 +3,7 @@
import { node } from 'wilderness-dom-node'
import render from '../src/render'
import shape from '../src/shape'
import timeline, { active, play, tick } from '../src/timeline'

describe('active', () => {
it('should return false if not started', () => {
const state = { started: false, finished: false, rendered: true }
expect(active({ state })).to.equal(false)
})

it('should return false if finished', () => {
const state = { started: true, finished: true, rendered: true }
expect(active({ state })).to.equal(false)
})

it('should return false if not rendered', () => {
const state = { started: true, finished: false, rendered: false }
expect(active({ state })).to.equal(false)
})

it('should return true if started, not finished and rendered', () => {
const state = { started: true, finished: false, rendered: true }
expect(active({ state })).to.equal(true)
})
})
import timeline, { play, tick } from '../src/timeline'

describe('timeline', () => {
it('should not change core timeline behaviour', () => {
Expand Down Expand Up @@ -64,7 +42,7 @@ describe('play', () => {

describe('tick', () => {
it('should throw if passed an invalid at option', () => {
expect(() => tick(true, true, 'potato'))
expect(() => tick('potato'))
.to.throw('The tick functions at option must be of type number')
})

Expand All @@ -86,7 +64,7 @@ describe('tick', () => {

expect(s.node.toString()).to.eql(preTickExpectedEl.toString())

tick(false, false, 500)
tick(500)

expect(s.node.toString()).to.eql(postTickExpectedEl.toString())
})
Expand Down
12 changes: 6 additions & 6 deletions yarn.lock
Expand Up @@ -4931,17 +4931,17 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2"

wilderness-core@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/wilderness-core/-/wilderness-core-2.2.1.tgz#7438109a34d1ef9c50213611641802bc63b98b6a"
wilderness-core@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/wilderness-core/-/wilderness-core-2.2.2.tgz#f88e281b75d8a15e40395f7b52f5a97f049c3b2c"
dependencies:
points "^3.1.0"
svg-points "^6.0.0"
tween-functions "^1.2.0"

wilderness-dom-node@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/wilderness-dom-node/-/wilderness-dom-node-1.3.1.tgz#f2dd70d40fcffc34744c4e5ec92c35bc6472d2b7"
wilderness-dom-node@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/wilderness-dom-node/-/wilderness-dom-node-1.3.2.tgz#70d48e8310896e19cbf79822f7eb8d5434da8e9a"
dependencies:
svg-points "^6.0.0"

Expand Down

0 comments on commit a4f6309

Please sign in to comment.