Permalink
Browse files

Merge pull request #555 from koenbok/feature/path-animation

Path animation
  • Loading branch information...
nvh committed Dec 11, 2017
2 parents c93bf72 + 8429254 commit 6f33b51f6bb8ca084cc335044478943e0cfc03e5
Showing with 123 additions and 5 deletions.
  1. +25 −5 framer/Animation.coffee
  2. +70 −0 framer/SVG.coffee
  3. +28 −0 framer/SVGLayer.coffee
View
@@ -7,6 +7,7 @@ Utils = require "./Utils"
{BaseClass} = require "./BaseClass"
{Animator} = require "./Animators/Animator"
{LinearAnimator} = require "./Animators/LinearAnimator"
{SVG, SVGPath} = require "./SVG"
Curves = require "./Animators/Curves"
numberRE = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/
@@ -260,6 +261,7 @@ class exports.Animation extends BaseClass
_start: =>
@_delayTimer = null
@emit(Events.AnimationStart)
@_previousValue = 0
Framer.Loop.on("update", @_update)
finish: =>
@@ -277,9 +279,22 @@ class exports.Animation extends BaseClass
_prepareUpdateValues: =>
@_valueUpdaters = {}
for k, v of @_stateB
if Color.isColorObject(v) or Color.isColorObject(@_stateA[k])
if SVGPath.isPath(v)
path = new SVGPath(v)
direction = null
start = null
end = null
switch k
when "x", "minX", "midX", "maxX", "width"
direction = "horizontal"
when "y", "minY", "midY", "maxY", "height"
direction = "vertical"
when "rotation", "rotationZ", "rotationX", "rotationY"
direction = "angle"
@_valueUpdaters[k] = path.valueUpdater(direction, @_target, @_target[k])
else if Color.isColorObject(v) or Color.isColorObject(@_stateA[k])
@_valueUpdaters[k] = @_updateColorValue
else if Gradient.isGradient(v) or Gradient.isGradient(@_stateA[k])
@_valueUpdaters[k] = @_updateGradientValue
@@ -297,7 +312,9 @@ class exports.Animation extends BaseClass
@_valueUpdaters[k] = @_updateNumberValue
_updateValues: (value) =>
@_valueUpdaters[k](k, value) for k, v of @_stateB
delta = value - @_previousValue
@_previousValue = value
@_valueUpdaters[k](k, value, delta) for k, v of @_stateB
return null
_updateNumberValue: (key, value) =>
@@ -400,7 +417,7 @@ class exports.Animation extends BaseClass
return _.pick(@layer, _.keys(@properties))
@isAnimatable = (v) ->
_.isNumber(v) or _.isFunction(v) or isRelativeProperty(v) or Color.isColorObject(v) or Gradient.isGradientObject(v)
_.isNumber(v) or _.isFunction(v) or isRelativeProperty(v) or Color.isColorObject(v) or Gradient.isGradientObject(v) or SVGPath.isPath(v)
# Special cases that animate with different types of objects
@isAnimatableKey = (k) ->
@@ -418,7 +435,10 @@ class exports.Animation extends BaseClass
when "size" then derivedKeys = ["width", "height"]
when "point" then derivedKeys = ["x", "y"]
else derivedKeys = []
if _.isObject(v)
if SVGPath.isPath(v)
for derivedKey in derivedKeys
animatableProperties[derivedKey] = v
else if _.isObject(v)
_.defaults(animatableProperties, _.pick(v, derivedKeys))
else if _.isNumber(v)
for derivedKey in derivedKeys
View
@@ -0,0 +1,70 @@
{BaseClass} = require "./BaseClass"
class SVG
@isSVG: (svg) ->
svg instanceof SVGElement
class SVGPath extends BaseClass
@define "length",
get: ->
@_length
@define "start",
get: ->
@pointAtFraction(0)
@define "end",
get: ->
@pointAtFraction(1)
@define "path", @simpleProperty("path", null)
constructor: (path) ->
return null if not SVGPath.isPath(path)
super
if path instanceof SVGPath
path = path.path
@path = path
@_length = path.getTotalLength()
pointAtFraction: (fraction) ->
@path.getPointAtLength(@length * fraction)
valueUpdater: (axis, target, offset) =>
switch axis
when "horizontal"
offset -= @start.x
return (key, value) =>
target[key] = offset + @pointAtFraction(value).x
when "vertical"
offset -= @start.y
return (key, value) =>
target[key] = offset + @pointAtFraction(value).y
when "angle"
return (key, value, delta = 0) =>
return if delta is 0
fromPoint = @pointAtFraction(Math.max(value - delta, 0))
toPoint = @pointAtFraction(Math.min(value + delta, 1))
angle = Math.atan2(fromPoint.y - toPoint.y, fromPoint.x - toPoint.x) * 180 / Math.PI - 90
target[key] = angle
@isPath: (path) ->
path instanceof SVGPathElement or path instanceof SVGPath
@getStart: (path) ->
@getPointAtFraction(path, 0)
@getPointAtFraction: (path, fraction) ->
return null if not @isPath(path)
length = path.getTotalLength() * fraction
path.getPointAtLength(length)
@getEnd: (path) ->
@getPointAtFraction(path, 1)
exports.SVG = SVG
exports.SVGPath = SVGPath
View
@@ -1,6 +1,7 @@
{_} = require "./Underscore"
{Color} = require "./Color"
{Layer, layerProperty, layerProxiedValue} = require "./Layer"
{SVG, SVGPath} = require "./SVG"
validFill = (value) ->
Color.validColorValue(value) or _.startsWith(value, "url(")
@@ -10,6 +11,7 @@ toFill = (value) ->
return value
else
return Color.toColor(value)
class exports.SVGLayer extends Layer
constructor: (options={}) ->
@@ -56,6 +58,32 @@ class exports.SVGLayer extends Layer
value = value.cloneNode(true)
@_elementHTML.appendChild(value)
@define "path",
get: ->
if @svg.children?.length isnt 1
error = "SVGLayer.path can only be used on SVG's that have a single child"
if Utils.isFramerStudio()
throw new Error(error)
else
console.error(error)
child = @svg.children[0]
if not SVGPath.isPath(child)
error = "SVGLayer.path can only be used on SVG's containing an SVGPathElement, not #{Utils.inspectObjectType(child)}"
if Utils.isFramerStudio()
throw new Error(error)
else
console.error(error)
return child
@define "pathStart",
get: ->
start = SVGPath.getStart(@path)
return null if not start?
point =
x: @x + start.x
y: @y + start.y
return point
updateGradientSVG: ->
return if @__constructor
if not Gradient.isGradient(@gradient)

0 comments on commit 6f33b51

Please sign in to comment.