framer motion is not transitioning when switched between keyframe animations - javascript

I have 2 keyframe animation variants and when I switch between them I want it to happen with the transition. How can I make it transition all the props when switched from one keyframe animation to another (currently it happens instantly)?
const variants = {
...,
horizontalWiggle: {
x: ["40px", "80px"],
y: [0, 0],
transition: { duration: 0.5, yoyo: Infinity }
},
verticalWiggle: {
x: [0, 0],
y: ["8px", "40px"],
transition: { duration: 0.5, yoyo: Infinity }
}
}
Using with framer motion as so:
<motion.div
className="wiggle"
variants={variations}
initial="init"
animate={vertical ? "verticalWiggle" : "horizontalWiggle"}
/>
Link to the sandbox: https://codesandbox.io/s/long-surf-405lho?file=/src/App.js

You have two options:
You should not specify 0 for axis which you do not want to animate. That way there will be no instant jump and will be smooth animation between variants.
The idea is start new animation (new variant) from last position of previous variant. Another words: you need to stop old animation and start new animation from the same value (the same keyframe). That way there also will be no instant jump.
Full example here:
NOTE: if you need to animate between variants - you could create one more variant with animation from STATE-1 to STATE-2.
Like here:
Short Example:
const variations = {
...
horizontalWiggle: {
x: [0, 40, 80, 0],
transition: { duration: 1, repeat: Infinity }
},
verticalWiggle: {
y: ["0px", "20px", "40px", "0px"],
transition: { duration: 1, repeat: Infinity }
}
};

Related

Stop animation at specific frame?

I'm making a game with Phaser, and I need to have an animation for the health bar going down dynamically stop on a frame, and I couldn't find any clear documentation on the stopOnFrame() method, or the this.anims.getFrame(index) method, both of which woulden't work. I believe that stopOnFrame accepts a frame object, not a frame number but I coulden't figure out how to get that specific frame, as the getFrame() method returned undefined. If there's something I'm missing, my ideal solution looks something like this:
this.hpBar.play({key: 'damageAnimation', startFrame: this.hp})
this.hpBar.stopOnFrame(this.hpBar.getFrame(this.hp - amountOfDamage))
Thanks for any suggestions, cheers!
PS: I know there's some more nuance to how I would use the animations in forwards and reverse to properly create this effect, the example is purely for demonstration.
Two points, you would have to pass a Phaser.Textures.Frame to the stopOnFrame function, and as far as I know the function getFrame doesn't exist (atleast on the phaser sprite GameObject, as shown in your code).
You can use the function getFrameAt on the Animation object, the index has to be here only a valid integer, in the range from 0 - to max frame of the animation.
(btw.: here is the link to the documenation of the stopOnFrame function)
Info: the frame index-count, is based on the frames of the specific animation, not the frame in the spritesheet. (In this example the sprite frame index of the kick is 12, but in the kick animation the index for that specific frame is 2)
Here a Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
preload,
create
}
};
function preload(){
this.load.spritesheet('brawler',
'http://labs.phaser.io/assets/animations/brawler48x48.png',
{ frameWidth: 48, frameHeight: 48 }
);
this.load.image('brawler-sheet',
'http://labs.phaser.io/assets/animations/brawler48x48.png');
}
function create () {
this.add.text(10,10, 'Click, to Stop on kick (third frame)')
.setScale(1.5)
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
this.anims.create({
key: 'kick',
frames: this.anims.generateFrameNumbers('brawler',
{ frames: [ 10, 11, 12, 13, 10 ] }),
frameRate: 8,
repeat: -1,
repeatDelay: 0
});
this.add.image(140, 20, 'brawler-sheet')
.setOrigin(0)
const cody = this.add.sprite(100, 90);
cody.setScale(3);
cody.play('kick');
this.input.on('pointerdown', () => {
cody.stopOnFrame(cody.anims.currentAnim.getFrameAt(2))
});
}
new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
But for a simple healt-bar, I would use a phaser tween, not an animation, like this you can set the value more precise, you can easily update the max-health and "animation" is easy.
Here a demo how I would do it (two visula alternatives):
(I would opt for the second version, if you have some nice heart images or so, I could find a better image for the demo)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create,
preload
},
banner: false
};
function preload ()
{
this.load.image('mushroom',
'https://labs.phaser.io/assets/sprites/mushroom16x16.png');
}
let healthMax = 32*5
function create () {
this.add.text(10,10, 'Click, to animate healthbar')
.setScale(1.5)
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
this.healthbarBG = this.add.rectangle(10, 50, 32*5, 10, 0xffffff)
.setOrigin(0);
this.healthbar = this.add.rectangle(12, 52, healthMax - 4, 10 - 4, 0xff0000)
.setOrigin(0);
this.add.text(10, 75, 'Or abit more flashy')
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
let tilespriteBG = this.add.tileSprite(10, 100, 16*5, 16, 'mushroom')
.setOrigin(0)
.setScale(2);
tilespriteBG.setTint(0x2a2a2a)
let tilesprite = this.add.tileSprite(10, 100, 16*5, 16, 'mushroom')
.setOrigin(0)
.setScale(2);
this.input.on('pointerdown', () => {
// remove 50% health
let newHealthWidth = healthMax * .5;
this.tweens.add({
targets: this.healthbar,
width: newHealthWidth,
duration: 750,
ease: 'Power2',
/* this two following properties must be remove, just for demo
yoyo: true,
repeat:-1*/
});
this.tweens.add({
targets: tilesprite,
width: { to: 16 * 5 * .5, from: 16 * 5},
duration: 750,
/* this two following properties must be remove, just for demo
yoyo:true,
repeat:-1*/
});
})
}
new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
modifying the ease and/or the duration property (and others), you can make the animation suitable (and spicy) for the most usecases.

Trigger anime.js animation when element enters viewport with waypoints

I'm triggering an animation on multiple elements when they are visible in the viewport with waypoints but I run into some problems with animations not working correctly.
What is happening here is that the animations look smooth and correct but are fired on all elements when I scroll a new .sequential element in the viewport. Even the ones that are already animated This is obviously because I target that class in the code. You can see that flickering.
This is how it looks: https://watch.screencastify.com/v/cIKae7lQKMtw4zCXlTlG
This is the code:
$('.sequential').waypoint(function() {
var CSStransforms = anime({
targets: '.sequential',
opacity: [0, 1],
translateY: [50, 0],
delay: anime.stagger(100),
easing: 'cubicBezier(.71,-0.77,.43,1.67)'
});
}, {
offset: '100%'
});
What I need to do is to instead target 'this.element' in the code instead of the class. It works but the animation is fired at the same time if multiple elements are visible and the animation is not looking as good.
This is how it looks: https://watch.screencastify.com/v/69SVdcXsTywYeYuC6LwQ
This is the code:
$('.sequential').waypoint(function() {
var CSStransforms = anime({
targets: this.element,
opacity: [0, 1],
translateY: [50, 0],
delay: anime.stagger(100),
easing: 'cubicBezier(.71,-0.77,.43,1.67)'
});
}, {
offset: '100%'
});
How can I make the animation be more sequential even if there are multiple elements visible?
If anyone is looking. This is how I finally solved it. turns out I had to target a wrapping element with waypints before I could call the animation.
$(".anime").waypoint(function() {
anime.timeline({
loop: false
}).add({
targets: this.element.querySelectorAll('.sequential'),
translateY: [50, 0],
opacity: [0, 1],
duration: 500,
delay: (el, i) => 100 * i,
easing: 'cubicBezier(.71,-0.77,.43,1.67)'
});
}, {
offset: '100%'
});

Framer Motion: Animation Will not Loop

I'm trying to loop an animation with the Framer Motion library after a small delay. The animation itself will play when it's mounted, on the webpage refresh, however does not repeat.
Iv'e looked through the docs and this seems to be similar syntax.
const AnimateTrial = {
initial: {
opacity: 0,
x: -100,
y: -35,
scale: 0.9,
},
animate: (i) => {
const delay = 5 + i * 0.5;
return {
opacity: 1,
x: -10,
y: -35,
transition: {
opacity: { delay, duration: 1.5 },
x: { delay, duration: 1.5},
repeatType: "Infinity",
repeatDelay: 5,
},
};
}
Does anyone have an idea? Everything works minus bottom two lines!
The properties inside your transition object are not right. Use repeat in place of repeatType, Write it something like this
transition={{ repeat: Infinity, repeatDelay: 5 }}
If you check the docs repeateType property accepts only "loop" "reverse" "mirror" and not "Infinity".

GSAP timeline with repeat is breaking the draggable ease flow

I see that using the "repeat:-1" in timeline is breaking the draggable behavior ease flow.
https://codepen.io/GreenSock/pen/WNedayo?editors=1111
If use "repeat:-1" to loop the animation in above code pen code. observe that drag ease is breaking.
.to("#Handle", {
duration: 100,
x: 1000,
repeat:-1,
ease: "none"
});
You need to put repeat in right place:
handle.t1 = gsap.timeline({
repeat: -1,
onStart() {
console.log('start');
},
onComplete() {
console.log('complete');
}
})
.to("#Handle", {
duration: 100,
x: 1000,
ease: "none"
});

CSS Transform 3D - get children if main SVG to face the camera

I am making an animation using the GreenSock JS animation library. In the animation there are 3d transformations on SVGs.
The current case is: I have a larger SVG (with paths, polylines, etc.) animated (3d transformations and others), and I want some SVG elements (children of the main SVG) to always face the "camera" (the screen), while they go with the main SVG (e.g. their location on the screen, their size / scale changes with the transformation, but the rotation does not)?
Here is an image:
how it should look like during the animation.
I have tried transforming them (the district numbers and highway signs) in the opposite direction as their parent (the main SVG) is transformed, but it only seems to work with the Z axis.
var tl_loc1 = new TimelineMax({
repeat: -1,
yoyo: true
});
tl_loc1
.fromTo($("#City_Glow"), 4, {
css: {
scale: 1.3,
transformPerspective: 200,
transformStyle: "preserve-3d",
rotationY: -90,
rotationX: 60,
rotationZ: 20,
opacity: 0,
top: "-300px",
}
}, {
css: {
scale: 1,
transformPerspective: 200,
transformStyle: "preserve-3d",
rotationY: 0,
rotationX: 0,
rotationZ: 0,
opacity: 1,
top: "0",
}
})
.fromTo($(".loc1-cls-7, .loc1-cls-6"), 4, {
css: {
transformStyle: "preserve-3d",
rotationY: 90,
rotationX: -60,
rotationZ: -20,
}
}, {
css: {
transformStyle: "preserve-3d",
rotationY: 0,
rotationX: 0,
rotationZ: 0,
}
}, 0);
Here is a codepen: http://codepen.io/mukagergely/pen/QEvyqR
I do not know if it is impossible to achieve with CSS transform3d, GreenSock has some issues, or it's just me not knowing the solution.

Categories

Resources