Repeating, infinite animation loop using framer.js - framerjs

I'm trying to create a pulsating, looping animation effect using framer.js
I've loaded an image into a layer and I'd like to scale it up and down continuously. I can't figure out how to scale it down and loop through the animation indefinitely. This is what I currently have:
imageLayer = new Layer({x:0, y:0, width:128, height:128, image:"images/icon.png"})
imageLayer.center()
animationA = new Animation({
layer: imageLayer,
properties: {scale: 2.0},
curve: "ease-in-out"
})
animationA.start()

solved it like this:
imageLayer = new Layer({x:0, y:0, width:128, height:128, image:"images/icon.png"})
imageLayer.x = 100
imageLayer.y = 100
animationA = new Animation({
layer: imageLayer,
properties: {scale: 2.0},
curve: "linear",
time: 0.4
})
animationB = new Animation({
layer: imageLayer,
properties: {scale: 1.0},
curve: "linear",
time: 0.2
})
animationA.start()
animationA.on(Events.AnimationEnd, function() {
animationB.start()
});
animationB.on(Events.AnimationEnd, function() {
animationA.start()
});

Awesome.. thanks for this. I fixed your code so it's all in coffeescript.
imageLayer = new Layer({x:0, y:0, width:128, height:128})
imageLayer.x = 100
imageLayer.y = 100
animationA = new Animation({
layer: imageLayer,
properties: {scale: 2},
curve: "linear",
time: 0.2
})
animationB = new Animation({
layer: imageLayer,
properties: {scale: 1.0},
curve: "linear",
time: 0.6
})
animationA.start()
animationA.on Events.AnimationEnd, ->
animationB.start()
animationB.on Events.AnimationEnd, ->
animationA.start()

Here's how I did it. It uses a single animation variable instead of 2.
animationTime = 1.8
animation = new Animation
layer: sketch.spinner
properties:
rotation: 360
curve: "linear"
time: animationTime
animation.start()
Utils.interval animationTime, ->
#reset the animation
sketch.spinner.rotation = 0
animation.start()

You can use Animation.reverse() for this:
layerA = new Layer
point: Align.center
scaling = new Animation
layer: layerA
properties:
scale: 2
curve: "ease-in-out"
pulse = ->
scaling.start()
scaling.onAnimationEnd ->
#Reverse the scaling animation
scaling = scaling.reverse()
#Sart the animation again
pulse()
# Start of the pulse animation
pulse()
However, if you have a animation that ends at the same point as it begins, you can use the recently introduced looping: true parameter of Animation:
layerA.animate
properties:
rotation: 360
curve: "linear"
time: 5
looping: true
Full prototype here: http://share.framerjs.com/hvex1plbkiqt/

Simple scaling example.
Two key points:
1) Use reverse() to create the reverse animation.
2) After each animation ends, trigger the other animation to start.
animation = new Animation
layer: bookmark
properties:
scale: 1.08
time: 1
curve: Bezier.easeInOut
animation.start()
animation.onAnimationEnd ->
reversedAnimation = animation.reverse()
reversedAnimation.start()
reversedAnimation.onAnimationEnd ->
animation.start()
And here it is in action.

You could use States too.
https://framer.com/getstarted/guides/prototype#states
LayerA.states.flashGreen =
color: "#00F082"
shadowColor: "#00F082"
shadowBlur: 10
shadowSpread: 3
animationOptions:
time: 1
curve: Bezier.ease
layerA.stateCycle()
layerA.onAnimationStop -> layerA.stateCycle()

Related

UIViewPropertyAnimator with short animation segments in the beginning

Imagine UIViewPropertyAnimator is setup as follows:
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
myView.center = newCenterPoint
}
For this, I can add another animation that starts with a delay, ends with the main:
animator.addAnimation ({
myView.alpha = 0.5
}, withDelayFactor: 0.5)
So above starts at 1 second and continues for 1 second to finish with the main.
Question
Is there a way to add an animation to UIViewPropertyAnimator that starts at the beginning, but continues for a fraction of the time of the main animator? Starts at 0 and continues only for 1 second?
Why not just use another animator? It's fine for two animators to act on the same view simultaneously as long as their animations don't conflict:
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
self.myView.center.y += 100
}
let animator2 = UIViewPropertyAnimator(duration: 1, curve: .easeInOut) {
self.myView.backgroundColor = .red
}
animator.startAnimation(); animator2.startAnimation()
The animation moves the view downward for two seconds, while turning the view red during the first one second.
(Having said all that, in real life I'd probably use a CAAnimationGroup for this, rather than a property animator.)

optimal approach to animate radar in iOS

I'm trying to animate the heading line of a naval-style radar (the spinning part below) in iOS, like this:
My current slow, laggy, and high-overhead solution (pseudocode because the real Swift code is a little lengthy):
create NSTimer to call animate() every 1/60 sec
currentAngle = 0
animate():
create UIBezierPath from center of circle to outside edge at currentAngle
add path to new CAShapeLayer, add layer to views layer
add fade out CABasicAnimation to layer (reduces opacity over time)
increase currentAngle
What's the best way to do this without using a .gif? I have implemented the solution above, but the performance (frame rate and CPU use) is awful. How would you approach this problem?
Your approach is too complex. Don't try to re-draw your image during the animation.
Separate out the part that rotates from the part that fades in and out and animate them separately. Make each animation draw partly transparent so the other animation shows through.
Either create a static image of the part that rotates, or build a layer that creates that image.
If you create an image, you can use UIView animation to animate the rotation on the image view.
If you create a layer that draws the heading line and gradient around it, then use a CABasicAnimation that animates the z rotation of the layer's transform.
I've come to a working Swift 3 solution, thanks to Animations in Swift / iOS 8 article :
let scanInProgress = UIImageView()
scanInProgress.image = UIImage(named: "scanInProgress.png")
scanInProgress.frame = CGRect(x: 150, y: 200, width: 80, height: 200)
view.addSubview(scanInProgress)
let fullRotation = CGFloat(Double.pi * 2)
let duration = 2.0
let delay = 0.0
UIView.animateKeyframes(withDuration: duration, delay: delay, options: [.repeat, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1/3, animations: {
scanInProgress.transform = CGAffineTransform(rotationAngle: 1/3 * fullRotation)
})
UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 1/3, animations: {
scanInProgress.transform = CGAffineTransform(rotationAngle: 2/3 * fullRotation)
})
UIView.addKeyframe(withRelativeStartTime: 2/3, relativeDuration: 1/3, animations: {
scanInProgress.transform = CGAffineTransform(rotationAngle: 3/3 * fullRotation)
})
}, completion: {
})

Save CoreAnimation sequence frame by frame

How can I save each frame of a CoreAnimation based animation (as image files)?
Here's my little playground scene. The animation lasts 2.4 seconds.
let stage = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 300, height: 300))
stage.backgroundColor = UIColor.blueColor();
var dot = UIView(frame: CGRectMake(0, 0, 10, 10))
dot.backgroundColor = UIColor.redColor()
dot.center = stage.center
UIView.animateWithDuration(2.4, animations: { () -> Void in
dot.center.y = dot.center.y + 50
})
I know how to save a static UIView frame as PDF but I am not sure how to hook into the animation sequence while it happens and capture/save the view frame by frame.
Option 1
As far as I see it I need to hook into the animation block and save the stage view for each frame (at a given frame rate?). How can I do this? Is there some sort of callback I can use?
Option 2
While looking for a solution I came across another option that looks even more promising (based on this question and this awesome blog post). Using a CBAnimation, setting a timingFunction and then setting the timeOffset to the progress states I want to render.
Here's an example for the frame at 50% progress (using a different example here).
var drawAnimation = CABasicAnimation(keyPath: "strokeEnd")
…
drawAnimation.fromValue = NSNumber(float: 0.0)
drawAnimation.toValue = NSNumber(float: 1.0)
drawAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
circle.addAnimation(drawAnimation, forKey: "drawCircleAnimation")
circle.speed = 0.0;
circle.timeOffset = 0.5
The problem is that I cannot capture this timeOffset based animation state. When I render the view I only get the final state instead of the frozen animation frame at 0.5.
Any help to resolve this is appreciated.

ios animating already transformed view

I have a collection view with a collection of images (which are views). The layout causes transformations to occur on every visible item. The item at the center is both zoomed and translated while the others are just translated.
The point is, the layout is setting the layer's transform property for each item.
Later, when the user touches an item, I want to animate the item using a keyframe animation.
The behavior I am experiencing is that the item seems to revert back to its untransformed state, the animation occurs on its untransformed state and then the item's layout transformation returns.
Why?
I've tried using the view's transform property, the backing layer's affine transform property, and the backing layer's 3D transform property all with this same behavior. The keyframe animation uses 3D transforms.
I clearly have a gap in my understanding of transforms and animations, though I have seen this question posed for platforms other than iOS without answers.
Layout transformation code (in a flow layout subclass):
// Calculate the distance from the center of the visible rect to the center of the attributes.
// Then normalize it so we can compare them all. This way, all items further away than the
// active get the same transform.
let distanceFromVisibleRectToItem = CGRectGetMidX(visibleRect) - attributes.center.x
let normalizedDistance = distanceFromVisibleRectToItem / ACTIVE_DISTANCE
let isLeft = distanceFromVisibleRectToItem > 0
var transform = CATransform3DIdentity
var maskAlpha: CGFloat = 0.0
if (abs(distanceFromVisibleRectToItem) < ACTIVE_DISTANCE) {
// We're close enough to apply the transform in relation to how far away from the center we are.
transform = CATransform3DTranslate(transform,
(isLeft ? -FLOW_OFFSET : FLOW_OFFSET) * abs(distanceFromVisibleRectToItem/TRANSLATE_DISTANCE),
0, (1 - abs(normalizedDistance)) * 40000 + (isLeft ? 200 : 0))
// Set the zoom factor.
let zoom = 1 + ZOOM_FACTOR * (1 - abs(normalizedDistance))
transform = CATransform3DScale(transform, zoom, zoom, 1)
attributes.zIndex = 1
let ratioToCenter = (ACTIVE_DISTANCE - abs(distanceFromVisibleRectToItem)) / ACTIVE_DISTANCE
// Interpolate between 0.0f and INACTIVE_GREY_VALUE
maskAlpha = INACTIVE_GREY_VALUE + ratioToCenter * (-INACTIVE_GREY_VALUE)
} else {
// We're too far away - just apply a standard perspective transform.
transform = CATransform3DTranslate(transform, isLeft ? -FLOW_OFFSET : FLOW_OFFSET, 0, 0)
attributes.zIndex = 0
maskAlpha = INACTIVE_GREY_VALUE
}
attributes.transform3D = transform
Animation code (note this is a Swift extension to the UIView class):
func bounceView(completion: (() -> Void)? = nil) {
var animation = bounceAnimation(frame.height)
animation.delegate = AnimationDelegate(completion: completion)
layer.addAnimation(animation, forKey: "bounce")
}
func bounceAnimation(itemHeight: CGFloat) -> CAKeyframeAnimation {
let factors: [CGFloat] = [0, 32, 60, 83, 100, 114, 124, 128, 128, 124, 114, 100, 83, 60, 32,
0, 24, 42, 54, 62, 64, 62, 54, 42, 24, 0, 18, 28, 32, 28, 18, 0]
var transforms: [AnyObject] = [NSValue(CATransform3D: self.layer.transform)]
for factor in factors {
let positionOffset = factor/256.0 * itemHeight
let transform = CATransform3DMakeTranslation(0, -positionOffset, 0)
transforms.append(NSValue(CATransform3D: transform))
}
let animation = CAKeyframeAnimation(keyPath: "transform")
animation.repeatCount = 1
animation.duration = CFTimeInterval(factors.count)/30.0
animation.fillMode = kCAFillModeForwards
animation.values = transforms
animation.removedOnCompletion = true // final stage is equal to starting stage
animation.autoreverses = false
return animation;
}
Note: I should state more simply that I want this process to begin and end in the collection view layout transformed state. I want the animation to occur on the layout transformed state as well (i.e. no reverting to the untransformed state at all).
It seems that in your animations you are not syncing your model with your presentation layer.
When you animate with Core Animation (CABasicAnimation, for instance) it only changes the presentation layer (the thing you see on the screen) but in fact the state your layers have is different from the visible one.
(You can read more about that in this objc.io issue)
So basically to fix this you need to update your model in your CAAnimation. You need to set a from and toValue, and then, after you set those, you update your transform so it can match the final state of your presentation layer.
EDIT
Now that we have some code I can be concrete
To sync the model layer to the presentation layer you need to set your model to the final state before adding the animation:
var animation = bounceAnimation(frame.height)
animation.delegate = AnimationDelegate(completion: completion)
layer.transform = CATransform3DMakeTranslation(0, 0, 0)
layer.addAnimation(animation, forKey: "bounce")
David Rönnqvist has a good rant about this issue in this gist.
SOLUTION:
The help Tiago Almeida offered was very useful to get me thinking about what the animation was actually doing (how it was actually working). My basic assumption that the animation would begin with the current model layer transform was incorrect. As Tiago points out, the presentation layer is a separate and distinct layer from the model layer.
Once I got that through my thick head, I realized that I had to manually concatenate the current model transform with the transform being built for the animation frame and viola! Things work as expected.
The updated animation code:
var transforms: [AnyObject] = []
for factor in factors {
let positionOffset = factor/256.0 * itemHeight
var transform = CATransform3DMakeTranslation(0, -positionOffset, 0)
transform = CATransform3DConcat(self.layer.transform, transform)
transforms.append(NSValue(CATransform3D: transform))
}

spritekit: trouble with particle keyframe sequence

This should be really straightforward. I'm trying to have my particles fade out using a keyframe sequence.. but when I use the keyframe sequence they dont fade out at all. Not sure what I could be doing wrong.
particle creation:
static func debris(size: Int) -> Array<SKEmitterNode> {
if size > 5 {
fatalError("we don't have that much debris")
}
var debrisArr: [SKEmitterNode] = []
for i in 1...size {
let debris: SKEmitterNode = SKEmitterNode(fileNamed: "debris")
debris.particleTexture = SKTexture(imageNamed: "debris\(i)")
convertNumRef(&debris.particleScale)
convertNumRef(&debris.particleScaleRange)
debris.particleRotationSpeed = debris.particleRotationSpeed * CGFloat.randomSign()
// THE PART WE CARE ABOUT
debris.particleAlphaSequence = SKKeyframeSequence(keyframeValues: [0.5, 1.0, 0.0], times: [0.0, 3.0, 4.0])
debrisArr.append(debris)
}
add particles to game scene here
func makeDebris(){
for debrisEmitter in self.debris {
debrisEmitter.resetSimulation()
debrisEmitter.position = self.position
self.gameScene.gameLayer.addChild(debrisEmitter)
debrisEmitter.runAction(SKAction.removeFromParentAfterDelay(10))
}
}
I've tried this using a simpler example too.
fire is the default spritekit "fire" particle
let fire = SKEmitterNode(fileNamed: "MyParticle")
fire.particleColorSequence = SKKeyframeSequence(keyframeValues: [SKColor.blueColor(), SKColor.blueColor(), SKColor.yellowColor()], times: [0.0, 1.0, 2.0])
fire.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(fire)
the emitter is only emitting blue particles. it just picks whichever color is first in the array. I must be missing something.
Ok I misunderstood. the times arent the times in seconds. they are fractions of your particle's lifespan
debris.particleLifetime = 8
debris.particleAlphaSequence = SKKeyframeSequence(keyframeValues: [1.0, 1.0, 0.0], times: [0.0, 0.7, 1.0])

Resources