React Native - animation with parallel translation and scale - javascript

I want to make an animation where an image should scale and move to the center at the same time. However, the final position is not correct. If I don't have scale, the translation works perfectly. I want to center the image, so (screenW / 2 - IMAGE_RADIUS * SCALE) should be correct.
(1) before: https://ibb.co/efjAW8, (2): after: https://ibb.co/bUL64T
code:
pressedImage = () => {
const deltaX = (screenW / 2 - IMAGE_RADIUS * SCALE) - origX;
const deltaY = (screenH / 2 - IMAGE_RADIUS * SCALE - 30) - origY;
Animated.parallel([
Animated.timing(this.state.scaleAnimatedValue, {
toValue: SCALE,
easing: Easing.ease,
duration: 1000,
}),
Animated.timing(this.state.animTransX,{
toValue: deltaX,
easing: Easing.ease,
duration: 1000,
}),
Animated.timing(this.state.animTransY,{
toValue: deltaY,
easing: Easing.ease,
duration: 1000,
}),
]).start();
}
render() {
...
<ImageView style={
{
transform: [
{
translateX: this.state.animTransX,
},
{
translateY: this.state.animTransY,
},
{
scale: this.state.scaleAnimatedValue,
},
],
}} source={{ uri: gist.author.avatarUrl }} />
}
How can I achieve this? I've tried with matrix transformations and setNativeProps but failed miserably. This should be a simple thing...

Related

AnimeJS animation takes config from initial click

I implemented a Staggered Grid in React using AnimeJS, with 2 variants, defined as
export const SimpleAnimationConfig: AnimeParams = {
backgroundColor: COLORS[Math.floor(Math.random() * COLORS.length)],
scale: [
{ value: 0.1, easing: "easeOutSine", duration: 500 },
{ value: 1, easing: "easeInOutQuad", duration: 1200 },
],
};
export const CoolerAnimationConfig: AnimeParams = {
scale: [
{ value: 0.1, easing: "easeOutSine", duration: 500 },
{ value: 1, easing: "easeInOutQuad", duration: 1200 },
],
};
(and some other properties defined in CSS)
and using them as
async function onClickTile(index: number) {
const animationConfig = getAnimationConfig(isCooler);
animation.current = anime({
targets: ".tile",
delay: anime.stagger(50, { grid: [columns, rows], from: index }),
...animationConfig,
});
}
where animation is
const animation = useRef<AnimeInstance>();
and getAnimationConfig is
export function getAnimationConfig(isCooler: boolean): AnimeParams {
if (isCooler) {
return CoolerAnimationConfig;
}
return SimpleAnimationConfig;
}
Problem
Let say I clicked on a tile with index 50, it will start a staggered animation from the div with index 50 , Every subsequent animation I trigger from any other div located at any other index will however start from 50.
I console logged index, it shows the right value ,
I console logged the whole object that I am passing inside anime , it shows the correct value for fromIndex in the scope of function delay.
I did resolve the issue with
function onClickTile(index: number) {
animation.current = anime({
targets: ".tile",
delay: anime.stagger(50, { grid: [columns, rows], from: index }),
scale: [
{ value: 0.1, easing: "easeOutSine", duration: 500 },
{ value: 1, easing: "easeInOutQuad", duration: 1200 },
],
...(!isCooler && {
backgroundColor: COLORS[Math.floor(Math.random() * COLORS.length)],
}),
});
}
but still curious what was happening with separate objects, since using separate constants and helpers seems a much cleaner way than this.
DEMO

How to exit a method in Phaser3 only when the played timeline is finished?

I'm quite new in Phaser, but I've already done a bunch of ionic/angular app.
One of my object has a method that will be called by the main scene:
powerOff() {
let easing = 'Cubic'
let overallDuration = 500
let visiblePauseDuration = 100
let flashDuration = overallDuration - visiblePauseDuration / 2
this.scene.tweens.timeline({
tweens: [
{
targets: this,
duration: 0,
tint: 0xff0000,
ease: easing,
},
{
targets: this,
duration: flashDuration,
tint: 0xffffff,
ease: easing,
},
{
targets: this,
duration: visiblePauseDuration,
tint: 0xff0000,
ease: easing,
},
{
targets: this,
duration: flashDuration,
tint: 0xffffff,
ease: easing,
},
{
targets: this,
duration: visiblePauseDuration,
tint: 0xff0000,
ease: easing,
onComplete: () => {
this.scene.sound.play(MainScene.POWER_DOWN)
},
},
{
targets: this,
duration: flashDuration,
tint: 0xf54242,
ease: easing,
},
],
})
this.poweredUp = false
}
The thing is: I need to exit this method only when the timeline has completed.
Is there some await/async support? Or at least promises ?
The scene will call this method on a lot of different objects, and I need that they are not done in parallel but sequentially.
Thanks a lot!!!
var timeline = this.scene.tweens.timeline({
/*TWEENS*/
})
//Timeline complete event listener
timeline.addListener("complete", function(){
//Do something when tweens are complete
}, this);

jQuery to monitor links to see if they are hovered over, if they are go up 3 parents and add css/class

I have lots of links on my page with the HTML format of:
<div class="heading-column">
<div class="heading-container">
<h2 class="heading-item">
<a href="/find/">Find</h2>
</div>
</div>
</div>
Currently we have some animations happening when they hover over the heading-column element, but they should actually only happen when hovering over the a hyperlink.
I'm trying to figure out how to create some jQuery to monitor all hyperlinks, if one with the class of .heading-item a is hovered over for it to update its parent 3 higher (.heading-column) by adding the css of pointer-events: auto; but as soon as the mouse stops hovering, this css rule should be deleted so that the pointer-events: none; stops the .heading-column animation/hover from happening
I'd greatly appreciate some help
Have you tried:
$(".heading-column a").hover(function(){
// In
$(this).closest(".heading-column").css("pointer-events", "none");
}, function(){
// Out
$(this).closest(".heading-column").css("pointer-events", "auto");
});
Reference: https://api.jquery.com/hover/
Might consider adding and removing a Class too.
See if this helps -
https://codepen.io/VweMWoz
const tl = gsap.timeline({paused: true});
tl.from(
".gsap-swipe",
{
y: 20,
x: 20,
rotate: -40,
duration: 3,
transformOrigin: '30% 80%',
ease: Elastic.easeOut.config(1, 0.5),
}, 0
)
.fromTo(
".swipe",
{
xPercent: -100
},
{
duration: 1,
xPercent: 100,
ease: Sine.easeInOut,
stagger: {
each: 0.15
}
}, 0
)
.from(
".maskSwipe",
{
xPercent: -100,
ease: Sine.easeInOut
},
0.4
)
.from(
"#hello",
{
duration: 1.5,
drawSVG: "0%"
},
1
)
.from(
".swoop",
{
duration: 2,
drawSVG: "0%"
},
1
)
.from(
".line",
{
drawSVG: "0%",
duration: 0.5,
stagger: {
each: 0.2
}
},
1
)
.from(
".shape",
{
scale: 0,
duration: 1.3,
transformOrigin: "50% 50%",
rotate: '+=random(-60, 60)',
ease: Elastic.easeOut.config(1, 0.8),
stagger: {
each: 0.2
}
},
0.2
);
// ScrubGSAPTimeline(tl);
let hover = document.querySelector('.js-hover');
hover.addEventListener('mouseover', playTl);
hover.addEventListener('mouseout', resetTl);
function playTl(){
tl.timeScale(1.25).restart();
}
function resetTl(){
tl.progress(0).pause();
}

A function that execute once when scrollY is higher than 100vh

I want call my function once when the statement if (scrollY > 100vh), but on every scroll when I get more than 100vh is calling func. I understand the reason why is that, but don't know how to fix that.
Perhaps it is simple but I can't do that.
I want a simple animation on scrolling.
code :
import anime from 'animejs';
var txt = document.querySelector('.textanimation');
txt.innerHTML = txt.textContent.replace(
/\S/g,
"<span class='letter'>$&</span>"
);
window.addEventListener('scroll', () => {
const vh = window.innerHeight;
const scrolled = window.scrollTop;
if (scrolled > vh) {
const scrollTextAnimation = anime.timeline();
scrollTextAnimation
.add({
targets: '.textanimation .letter',
translateY: [-100, 0],
easing: 'easeOutExpo',
duration: 1400,
delay: (el, i) => 30 * i
})
.add({
targets: '.textanimation',
duration: 1000,
easing: 'easeOutExpo',
delay: 1000
});
}
});
just add once option to the event listener to execute it one time.
window.addEventListener('scroll', () => {
const vh = window.innerHeight;
const scrolled = window.scrollTop;
if (scrolled > vh) {
const scrollTextAnimation = anime.timeline();
scrollTextAnimation
.add({
targets: '.textanimation .letter',
translateY: [-100, 0],
easing: 'easeOutExpo',
duration: 1400,
delay: (el, i) => 30 * i
})
.add({
targets: '.textanimation',
duration: 1000,
easing: 'easeOutExpo',
delay: 1000
});
}
}, {once: true});
I just added a bool that change the statement to false when I make a scrollY more than innerHeight/2. "Once" option in addEventListener was not working, don't know why. Is there a better solution how to fix that?
The code below is working:
let myBool = true;
window.addEventListener('scroll', () => {
if (window.scrollY > window.innerHeight / 2 && myBool) {
anime.timeline()
.add({
targets: '.textanimation .letter',
translateY: [-100, 0],
easing: 'easeOutExpo',
duration: 1400,
delay: (el, i) => 30 * i
})
.add({
targets: '.textanimation',
duration: 1000,
easing: 'easeOutExpo',
delay: 1000
});
myBool = false;
}
});
You can remove the event listener after your logic executes in order to stop it from ever firing again. You can do this in two ways:
1) Use the once option as defined in the parameters for addEventListener here. This option automatically removes the event listener after the event happens once. The OP has already stated this isn't working for them (possibly because the scroll needs to reach a certain height before the logic works so it is being removed too early).
2) Call removeEventListener after the logic executes:
import anime from 'animejs';
var txt = document.querySelector('.textanimation');
txt.innerHTML = txt.textContent.replace(
/\S/g,
"<span class='letter'>$&</span>"
);
var animateOnce = function() {
const vh = window.innerHeight;
const scrolled = window.scrollTop;
if (scrolled > vh) {
const scrollTextAnimation = anime.timeline();
scrollTextAnimation
.add({
targets: '.textanimation .letter',
translateY: [-100, 0],
easing: 'easeOutExpo',
duration: 1400,
delay: (el, i) => 30 * i
})
.add({
targets: '.textanimation',
duration: 1000,
easing: 'easeOutExpo',
delay: 1000
});
// remove the event listener after the animation happens on the first time
window.removeEventListener('scroll', animateOnce);
}
}
window.addEventListener('scroll', animateOnce);
3) Here are a couple of other solutions in a post on sitepoint.

How to translate view offset based on the current drag position of parent

I have a super specific case here that is kind of tripping me up on how the math needs to work out to get this to work
I have this card rendered on the page
Which is rendered with these styles...
-------- main card --------
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height,
shadowColor: "#455B63",
shadowOffset: {
width: 0,
height: 0,
},
shadowOpacity: 0.33,
shadowRadius: 16,
borderBottomLeftRadius: 12,
borderBottomRightRadius: 12
-------- content view --------
backgroundColor: '#fff',
paddingVertical: 14,
paddingHorizontal: 20,
height: height * 2
With this animation attached to it...
Animated.spring(this.state.placeCard,{
toValue: {x: 0, y: deviceHeight - placeCardClosedHeight},
duration: 350,
}).start();
The content view has this animation state set in the state
new Animated.ValueXY({x: 0, y: -(Math.round(deviceHeight / 2) - 160)})
And I'm trying to get it to animate to this layout
The panhandler I have set up for handle the overall card styles is this
this._placeCardPanResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: (evt, gestureState) => gestureState.dx != 0 && gestureState.dy != 0,
onPanResponderGrant: (evt, gestureState) => {
this.setState({
placeCardTopOffset: this.state.placeCard.__getValue().y
})
},
onPanResponderMove: (evt, gestureState) => {
let y = gestureState.dy;
const { placeCardTopOffset } = this.state;
if (y + placeCardTopOffset <= 0) {
y = 0
} else if (y + placeCardTopOffset >= deviceHeight - placeCardClosedHeight) {
y = deviceHeight - placeCardClosedHeight
} else {
y = y + placeCardTopOffset;
}
this.state.placeCard.setValue({x: 0, y});
},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.dy < 0) {
if (this.state.placeCard.__getValue().y > 0) {
Animated.timing(
this.state.placeCard,
{
toValue: {x: 0, y: 0},
duration: 175,
}
).start();
}
}
if (gestureState.dy > 0) {
if (this.state.placeCard.__getValue().y < deviceHeight - placeCardClosedHeight) {
Animated.timing(
this.state.placeCard,
{
toValue: {x: 0, y: deviceHeight - placeCardClosedHeight},
duration: 175,
}
).start();
}
}
}
});
When the card is full height on the page the content view would be animated something like this
Animated.timing(
this.state.placeCardContent,
{
toValue: {x: 0, y: 0},
duration: 175,
}
).start();
I have a snack setup to show this off
If you click on the image you will see the intended animation but if you drag you will see how the dragging is being handled. Where I am having issues is with trying to figure out the math related to animating that content view while the main view is being dragged.
https://snack.expo.io/B1lhq5Rb4
I was neglecting to remember the fact that react-native's Animated API has a .interpolate() function which was designed for this vary task.
Here is the code to make this work...
<Animated.View style={{...style.placeCardContent, transform: [{
translateX: 0
},{
translateY: this.state.placeCard.y.interpolate({
inputRange: [0, deviceHeight - placeCardClosedHeight],
outputRange: [0, -(Math.round(deviceHeight / 2) - 160)]
})
}]}}>
You can see this working in this snack example
https://snack.expo.io/#jordanr/carddrag

Categories

Resources