Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animation Paths #418

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e1fb80b
First take at the animation paths API (a new Path builder and path op…
tisho Jan 5, 2015
60246c2
Added another transition example from material design spec. Fixed an …
Jan 7, 2015
dea5451
Added arc convenience initializer. Cached path length.
Jan 7, 2015
07e47b9
Removed 'from' param from curve/arc initializers. You can now pass in…
Jan 8, 2015
037f325
Have animation path respect layer origin.
Jan 8, 2015
59a150f
Paths will now attach to layer origin, instead of always the center o…
Jan 10, 2015
5436f08
Restored CactusFramer template I was using for testing.
Jan 10, 2015
11f56f2
Attempting to fix whitespace issues.
Jan 11, 2015
8ae7b6e
Merged in master.
tisho Dec 15, 2015
fa4a87a
Fixed call to setDefaultProperties function, which no longer exists. …
tisho Dec 15, 2015
48ee9e8
Merged in master.
tisho Dec 15, 2015
e98ed7d
Merged in master.
tisho Jan 2, 2016
92b7a90
Merged in master.
tisho Jan 14, 2016
40ae942
Merge branch 'master' into tisho/animation-paths
tisho Jan 22, 2016
e516e98
Updated framer build in path animation example. Added more complex ex…
tisho Jan 22, 2016
707f464
Merged in master.
tisho Feb 14, 2016
4f907f6
Updated framer build in path animation example.
tisho Feb 14, 2016
0543669
Exposed points method for paths. Added a few tests.
tisho Feb 16, 2016
e1f8b3c
Added more tests.
tisho Mar 6, 2016
826a588
Merge branch 'master' into tisho/animation-paths
tisho Mar 6, 2016
da55b8b
Fixed an issue with the arc sweep flag not getting set to anything ot…
tisho Mar 8, 2016
f3ae600
Merge branch 'master' into tisho/animation-paths
tisho Mar 23, 2016
3c251d5
Merge branch 'master' into tisho/animation-paths
tisho Apr 2, 2016
e77fd8d
Updated Framer build in test project.
tisho Apr 2, 2016
457d028
Merged in master
tisho Aug 11, 2016
e6b9505
Merge remote-tracking branch 'upstream/master' into tisho/animation-p…
tisho Aug 11, 2016
8763d8b
Updated PathAnimation studio test to match the new animateTo API.
tisho Aug 11, 2016
1978ecb
Refactored path animation code to make use of the valueUpdaters and b…
tisho Aug 29, 2016
6525d09
The path animation option now accepts an SVG path description as a st…
tisho Aug 29, 2016
ece6e4a
Removed Penner's easing equations, as they were out of scope for this…
tisho Aug 29, 2016
06da409
Took out the parts of Path's tests that were not relevant to SVGPathP…
tisho Aug 29, 2016
2bdaa46
Merge branch 'master' into tisho/animation-paths
tisho Aug 29, 2016
e9f7591
Removed Path dependency (it's being moved into a separate module).
tisho Aug 29, 2016
b61c3e4
Updated PathAnimation studio project.
tisho Aug 29, 2016
a85dc38
Check for @options.debug, instead of @_debugLayer. Check for existenc…
tisho Sep 14, 2016
17cd93c
Replaced namespaced SVG Utils with two individual functions (getSVGCo…
tisho Sep 14, 2016
e6343cb
Added tests for the getSVGContext and createSVGElement. Simplified de…
tisho Sep 14, 2016
0b6db97
Tidied up debug code by moving it into its own class.
tisho Sep 15, 2016
8be5343
Warn when trying to animate x or y while animating on a path.
tisho Sep 15, 2016
0c95112
Merge branch 'master' into tisho/animation-paths
nvh Sep 15, 2016
6fdb987
animateTo -> animate
nvh Sep 16, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions extras/CactusFramer/static/path.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 46 additions & 2 deletions framer/Animation.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Utils = require "./Utils"
{BezierCurveAnimator} = require "./Animators/BezierCurveAnimator"
{SpringRK4Animator} = require "./Animators/SpringRK4Animator"
{SpringDHOAnimator} = require "./Animators/SpringDHOAnimator"
{SVGPathProxy} = require "./SVGPathProxy"

AnimatorClasses =
"linear": LinearAnimator
Expand Down Expand Up @@ -60,6 +61,18 @@ class exports.Animation extends BaseClass
if properties.origin
console.warn "Animation.origin: please use layer.originX and layer.originY"

if @properties.path?
if @properties.x? or @properties.y?
console.warn "Animation.path: the x and y animation properties are ignored when animating on a path"

@_path = new SVGPathProxy(@properties.path)
@properties.x = @layer.x + @_path.end.x - @_path.start.x
@properties.y = @layer.y + @_path.end.y - @_path.start.x
delete @properties.path

if @options.debug
@_debugLayer = new SVGPathDebugLayer(path: @_path, alignedToLayer: @layer)

@_parseAnimatorOptions()
@_originalState = @_currentState()
@_repeatCounter = @options.repeat
Expand Down Expand Up @@ -156,6 +169,14 @@ class exports.Animation extends BaseClass
@emit("stop") if emit
Framer.Loop.off("update", @_update)

if @options.debug
animation = @_debugLayer.animate
properties: { opacity: 0 }
curve: 'linear'
time: 0.25
animation.on 'end', =>
@_debugLayer.destroy()

reverse: ->
# TODO: Add some tests
properties = _.clone(@_originalState)
Expand Down Expand Up @@ -223,16 +244,39 @@ class exports.Animation extends BaseClass
for k, v of @_stateB
if Color.isColorObject(v) or Color.isColorObject(@_stateA[k])
@_valueUpdaters[k] = @_updateColorValue
else if @_path? and k in ['x', 'y']
@_valueUpdaters[k] = @_updatePositionAlongPath
else
@_valueUpdaters[k] = @_updateNumberValue

_updateValues: (value) =>
@_valueUpdaters[k](k, value) for k, v of @_stateB
for k, v of @_stateB
@_valueUpdaters[k](k, value)
return null

_updateNumberValue: (key, value) =>
@_target[key] = Utils.mapRange(value, 0, 1, @_stateA[key], @_stateB[key])

return

_updatePositionAlongPath: (key, value) =>
position = @_path.getPointAtLength(@_path.length * value)
position.x += @_stateA.x - @_path.start.x
position.y += @_stateA.y - @_path.start.y

if @options.debug
@_debugLayer.updatePositionAlongPath(@_path.length * (1 - value))

if @options.autoRotate
angle = Math.atan2(position.y - @_target.y, position.x - @_target.x) * 180 / Math.PI
if position.y != @_target.y && position.x != @_target.x
@_target.rotationZ = angle

@_target.x = position.x
@_target.y = position.y

return

_updateColorValue: (key, value) =>
@_target[key] = Color.mix(@_stateA[key], @_stateB[key], value, false, @options.colorModel)

Expand Down Expand Up @@ -310,7 +354,7 @@ class exports.Animation extends BaseClass

# Only animate numeric properties for now
for k, v of properties
if @isAnimatable(v)
if @isAnimatable(v) or k == 'path'
animatableProperties[k] = v
else if Color.isValidColorProperty(k, v)
animatableProperties[k] = new Color(v)
Expand Down
1 change: 1 addition & 0 deletions framer/Animators/BezierCurveAnimator.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ BezierCurveDefaults =
"ease-out": [0, 0, .58, 1]
"ease-in-out": [.42, 0, .58, 1]


class exports.BezierCurveAnimator extends Animator

setup: (options) ->
Expand Down
5 changes: 4 additions & 1 deletion framer/Framer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Framer.Gestures = (require "./Gestures").Gestures
Framer.Animation = (require "./Animation").Animation
Framer.AnimationGroup = (require "./AnimationGroup").AnimationGroup
Framer.Screen = (require "./Screen").Screen
Framer.SVGPathProxy = (require "./SVGPathProxy").SVGPathProxy
Framer.SVGPathDebugLayer = (require "./SVGPathDebugLayer").SVGPathDebugLayer
Framer.Canvas = (require "./Canvas").Canvas
Framer.Align = (require "./Align").Align
Framer.print = (require "./Print").print

Expand Down Expand Up @@ -82,4 +85,4 @@ Framer.Extras.Hints.enable() if not Utils.isFramerStudio()
# action in the future, so we can't know wether that will happen or not.
Framer.DefaultContext.visible = true unless Framer.Preloader

Utils.domComplete(Framer.Loop.start)
Utils.domComplete(Framer.Loop.start)
185 changes: 185 additions & 0 deletions framer/SVGPathDebugLayer.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
{_} = require "./Underscore"
{Layer} = require "./Layer"
{getSVGContext, createSVGElement} = require "./Utils"

setAttributes = (el, attributes) ->
for key, value of attributes
el.setAttribute key, value

class exports.SVGPathDebugLayer extends Layer

constructor: (options={}) ->

super _.defaults options,
backgroundColor: "transparent"
name: "debug-path"

path = options.path
alignedToLayer = options.alignedToLayer

padding = 10
debugElementsGroup = @_elementForDebugRepresentationOfPath(path)

# Add the path the hidden shared context temporarily in order to get its bounding box
sharedContext = getSVGContext()
sharedContext.appendChild(debugElementsGroup)
bbox = debugElementsGroup.getBBox()
sharedContext.removeChild(debugElementsGroup)

# Add the path to this layer and adjust the size to fit the path
svg = createSVGElement("svg", width: "100%", height: "100%")
@_element.appendChild(svg)
svg.appendChild(debugElementsGroup)

@width = bbox.width + Math.abs(bbox.x) + padding * 2
@height = bbox.height + Math.abs(bbox.y) + padding * 2

pathOffset = { x: bbox.x - padding, y: bbox.y - padding }
@_animatedPath = debugElementsGroup.getElementsByClassName("animated-path")?[0]

debugElementsGroup.setAttribute("transform", "translate(#{-bbox.x + padding}, #{-bbox.y + padding})")

# Align to layer that is being animated
layerScreenFrame = alignedToLayer.screenFrame
layerOriginX = layerScreenFrame.x + alignedToLayer.originX * layerScreenFrame.width
layerOriginY = layerScreenFrame.y + alignedToLayer.originY * layerScreenFrame.height

@x = layerOriginX - path.start.x + pathOffset.x
@y = layerOriginY - path.start.y + pathOffset.y

_elementForDebugRepresentationOfPath: (path) ->
group = createSVGElement 'g'

marker = createSVGElement 'circle',
r: 2
cx: 0
cy: 0
fill: 'red'

controlMarker = createSVGElement 'circle',
r: 2
cx: 0
cy: 0
fill: 'white'
stroke: '#aaa'
'stroke-width': '1px'

connector = createSVGElement 'path',
d: 'M0,0'
fill: 'transparent'
stroke: 'rgba(0, 0, 0, 0.25)'
'stroke-width': '1px'
'stroke-dasharray': '4 4'

debugPath = path.node.cloneNode()

lx = 0
ly = 0
segments = debugPath.pathSegList
relativeSegmentTypes = [
SVGPathSeg.PATHSEG_MOVETO_REL,
SVGPathSeg.PATHSEG_LINETO_REL,
SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL,
SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL,
SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL,
SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,
SVGPathSeg.PATHSEG_ARC_REL,
SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL,
SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL
]

elements = []
for i in [0...segments.numberOfItems]
segment = segments.getItem(i)

if segment.pathSegType in relativeSegmentTypes
addx = lx
addy = ly
else
addx = 0
addy = 0

olx = lx
oly = ly

if segment.x? || segment.y?
m = marker.cloneNode()
mx = addx + (if typeof segment.x is 'undefined' then lx else segment.x)
my = addy + (if typeof segment.y is 'undefined' then ly else segment.y)
setAttributes m,
cx: mx
cy: my
class: 'debug-marker'

lx = mx
ly = my

elements.push m

if segment.x1?
c1 = controlMarker.cloneNode()
c1x = addx + segment.x1
c1y = addy + segment.y1
setAttributes c1,
cx: c1x
cy: c1y
class: 'debug-control-marker'

conn = connector.cloneNode()
setAttributes conn,
d: "M#{olx},#{oly} L#{c1x},#{c1y}"
class: 'debug-connector'

elements.push c1
elements.push conn

# quadratic curves have a line from the control point to both anchors
if !segment.x2?
conn2 = connector.cloneNode()
setAttributes conn2,
d: "M#{mx},#{my} L#{c1x},#{c1y}"
class: 'debug-connector'
elements.push conn2

if segment.x2?
c2 = controlMarker.cloneNode()
c2x = addx + segment.x2
c2y = addy + segment.y2
setAttributes c2,
cx: c2x
cy: c2y
class: 'debug-control-marker'

conn = connector.cloneNode()
setAttributes conn,
d: "M#{mx},#{my} L#{c2x},#{c2y}"
class: 'debug-connector'

elements.push conn
elements.push c2

group.appendChild(element) for element in elements

setAttributes debugPath,
stroke: 'rgba(0, 0, 0, 0.1)'
# stroke: 'rgba(255, 0, 0, 0.75)'
'stroke-width': 1
fill: 'transparent'
class: 'debug-path'

animatedPath = debugPath.cloneNode()
setAttributes animatedPath,
class: 'animated-path'
stroke: 'rgba(255, 0, 0, 0.75)'
# stroke: 'transparent'
'stroke-dasharray': path.length
'stroke-dashoffset': path.length
fill: 'transparent'

group.appendChild animatedPath
group.appendChild debugPath

group

updatePositionAlongPath: (position) ->
@_animatedPath.setAttribute('stroke-dashoffset', position)
17 changes: 17 additions & 0 deletions framer/SVGPathProxy.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{createSVGElement} = require "./Utils"

class exports.SVGPathProxy
constructor: (string) ->
@node = createSVGElement "path",
d: string
fill: "transparent"

@length = @getTotalLength()
@start = @getPointAtLength(0)
@end = @getPointAtLength(@length)

getPointAtLength: (length) ->
@node.getPointAtLength(length)

getTotalLength: ->
@node.getTotalLength()
Loading