I have a GSAP animation like this:
window.addEventListener('keydown', ()=>{
GSAP.to(this.box, {
x: this.box.position.x + 4,
duration: 1,
}
});
I want to increment the box's x position by multiples of 4, and my current code above works, but only if the user waits until 1 second has passed before pressing another key. Then it goes 0 -> 4 -> 8 etc.
If they don't wait a second, what happens is something like 0 -> 3.76 -> 6.45. These numbers change depending on how long a user waits before they press another key. This makes sense because I'm effectively cancelling the animation and using the current position (since 1 second hasn't passed yet, it wouldn't get to 4 yet) and then just rerunning the animation with the current position (that wasn't 4).
Essentially, I want to prevent the 'keydown' event listener from triggered until the 1 second animation has been fully completed.
How would I do that?
This should work:
class Test {
constructor(el) {
this.box = el
window.addEventListener('keydown', () => {
if (!this.tl || !this.tl.isActive()) {
this.tl = gsap.to(this.box, {
x: "+=4",
duration: 1,
})
console.log("Moving to:", Math.floor(this.box.getBoundingClientRect().x))
}
});
}
}
const test = new Test(box)
#box {
width: 25px;
height: 25px;
background: blue;
}
<div id="box"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/CSSRulePlugin.min.js"></script>
Probably not the cleanest solution, gsap is a great tool and surely has some better way to handle this scenario, but this is the first brutal approach that came to my mind.
EDIT:
Using isActive() method is already a tiny bit better.
Related
I´m spinning a circle till 180° after that animation the circle hops back to 0° from where it started.
I´m sure it is because I called it in an function which is triggered by an onclickevent and there is no state of the circle-object(#XOWheel) saved.(because I don´t know how to do that)
function showWhoIs() {
console.log(main.stats.currentPlayerIdx); // 0 = O | 1 = X
document.querySelector('#XOWheel').animate([
{ transform: 'rotate(0deg)' },
{ transform: 'rotate(180deg)' }
], {
duration: 1000,
iterations: 1
});
}
Is there a better way to do that what I did here?
And what am I doing wrong?
How an animation behaves after it ended, can be specified using the animation-fill-mode property. https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode
And you can set this using the Web Animations API as well, via the EffectTiming dictionary. https://developer.mozilla.org/en-US/docs/Web/API/EffectTiming/fill
I'm using react js.
I try to run the simple code:
setTimeout(() => {
setTimeout(() => {
const element = document.getElementById(‘circle’)
element.style.backgroundColor = ‘red’
}, 3000)
}, 3000)
The CSS of 'circle' is just:
width: 100px;
height: 100px;
border-radius: 50%;
background-color: blue;
transition: background-color 2s;
I run the code and immediate change tab or minimize the screen, waiting more then 6 seconds and go back to the page and then the transition start. For some reason the transition not run if screen not in focus.
Any help guys???
The behavior you describe depending on the browser because inactive tabs have low priority execution.
While you performing a "JS Animation" you may want to use requestAnimation.
A better approach may use a "CSS Animation" with transition-delay.
Use this code why are using Apostrophe mark in code.
<script>
setTimeout(() => {
setTimeout(() => {
const element = document.getElementById('circle')
element.style.backgroundColor = 'red'
}, 3000)
}, 3000)
</script>
Actually it is not really help me.
The solution i made is to listen to 'focus' and 'blur' and stop any animations process and timers on blur and continue from last point at focus.
I am trying to control a GSAP animation using a forward & backward button. The expected behaviour I am aiming for is:
When the user hovers over the forward button the animation moves forward (increments in px).
When the user mouseleaves the button then the animation pauses.
When the user hovers over the backwards button the animation moves the other way.
The issue I have encountered is that I have tried to add a dynamic positioning variable in place which increments when the user hovers over the 'forward' button. This is not working as expected - instead it just moves once instead of waiting until the user's mouse leaves to stop.
I tried to add a setInterval to the button event listener to increment the positioning so that when the user hovered over the button it would move px at a time, which did work, but it would not stop causing the browser to crash. I also added a mouseleave to clear the setInterval but I don't think it was good practise.
var masterTimeline = new TimelineMax();
var mousedown = false;
var forwardBTN = document.getElementById("forward");
var backwardBTN = document.getElementById("backward");
var pauseBTN = document.getElementById("pause");
var blueboxElement = document.getElementById("blueBox");
var direction = '+';
var counter = 0;
var distance = 0;
var value1 = direction + '=' + distance;
var tween;
forwardBTN.addEventListener("mouseenter", function(){
// setInterval(function() {
directionMove('moveForward');
// }, 500);
});
backwardBTN.addEventListener("mouseenter", function(){
directionMove('moveBackward')
});
pauseBTN.addEventListener("click", function(){
directionMove('pause');
});
function directionMove(playk) {
if (playk == 'moveBackward') {
var direction = '-';
value1 = direction + '=' + distance; // how to update value
masterTimeline.to(blueboxElement, 0.1, {css: {x: value1}, ease: Linear.easeNone}); // no need move by default
}
else if (playk == 'moveForward') {
var direction = '+';
value1 = direction + '=' + distance; //how to update value
masterTimeline.to(blueboxElement, 0.1, {css: {x: value1}, ease: Linear.easeNone}); // no need move by default
}
else if (playk == 'pause') {
masterTimeline.kill();
console.log("killed");
//
}
}```
The expected behaviour is to move forward incrementally without stopping until the user moves off the forward button but at present it is just moving a single amount one time.
Here is a CodePen link if this helps:
https://codepen.io/nolimit966/pen/pXBeZv?editors=1111
I think you're overcomplicating this a bit (at least in the demo). The entire point of GSAP is moving things along a timeline. What you're trying to do is essentially forcibly use a timeline to work in a non-timeline way, which is why I think you're having trouble.
If you step back and just think about your three requirements, I think it becomes a lot more simple. It's always good to think about it in words like you did because it can help you simplify it and understand it better.
The key steps are to:
Create a timeline for the movement of the box.
Play the timeline forward when the forward button is hovered.
2b. Pause it when it's no longer hovered.
Play the timeline backward when the reverse button is hovered.
3b. Pause it when it's no longer hovered.
In code that looks like this:
var tl = new TimelineMax({paused: true});
var forwardBTN = document.getElementById("forward");
var backwardBTN = document.getElementById("backward");
var pauseBTN = document.getElementById("pause");
var blueboxElement = document.getElementById("blueBox");
tl.to(blueboxElement, 10, {x: window.innerWidth - blueboxElement.clientWidth, ease: Linear.easeNone});
function pause() {
tl.pause();
}
forwardBTN.addEventListener("mouseenter", function() {
tl.play();
});
forwardBTN.addEventListener("mouseleave", pause);
backwardBTN.addEventListener("mouseenter", function() {
tl.reverse();
});
backwardBTN.addEventListener("mouseleave", pause);
Demo
By the way, you're much more likely to get a faster response over on the official GreenSock forums :)
Do you mean something like this?
var tl = new TimelineMax({paused: true});
tl.to('.element', 3, {
x: 800,
});
$(document).on({
mouseenter: function () {
if($(this).hasClass('forward')){
tl.play();
}
else if($(this).hasClass('backwards')){
tl.reverse();
}
},
mouseleave: function () {
tl.pause();
}
}, ".btn");
.wrapper{
width: 100%;
padding: 40px;
}
.element{
width: 100px;
height: 100px;
background: red;
}
.btn{
display: inline-block;
padding: 15px;
background: #bbb;
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="element_wrapper">
<div class="element"></div>
</div>
<div class="forward btn">Forward</div>
<div class="backwards btn">Backwards</div>
</div>
I have an array of 8 cubes, all 1px tall. When a button is hit, they are meant to tween to a new target height, using a while loop, like this:
if (buttonHit) {
console.log('button hit')
for (i in meshArray) {
while (Math.abs(meshArray[i].scale.y - newList[i].cost)>5) {
console.log(meshArray[3].scale.y)
meshArray[i].scale.y += .3
}
}
buttonHit = false;
}
What the console log shows me is that meshArray[3] holds at 1px 1215 times in a row, then adds .3, then stops, but the mesh just pops to it's target height once this is all done.
Does a while loop not work in the render()?
The problem is that render is called once for each frame that needs to be rendered. You are doing all your changes in one frame. threejs never has a chance redraw anything.
What you need to do is change the height by .3 only once per call to render. Ie not have it in a loop.
something a bit like this:
function render() {
if (buttonHit) {
new_height = 2;
buttonHit = false;
}
if (meshArray[i].scale.y < new_height) {
meshArray[i].scale.y +=.3
}
}
How would I go about adjusting the time manually based on the scroll position? What might that look like? To basically 'scroll' the tween? So that the tween reacts to the scrolling mouse's Y position rather than just trigger and execute based on a preset time?
IMHO, here is what you'll need to do:
You will need TimelineMax for sequencing your animations. Place
your animations in TimelineMax as you like them to be.
You'll need to figure out the maximum scroll position your window can scroll up to, beforehand. (This can also be re-calculated on browser resize as well but I haven't taken this into account in my example below). You can figure out with the
help of this answer. Also read the comments on that answer.
Upon scroll, you'll need to convert the current scroll position of
your window object into percentage that is: var currentScrollProgress=window.scrollY/maxScroll; such that your currentScrollProgress should always be between 0 and 1.
TimelineMax has a progress() method which takes values ranging
from 0 and 1 where 0 being the initial state of the animations
and 1 being the final state. Feed this currentScrollProgress
into it and you're done.
OR, you can tween the timeline itself that is: TweenMax.to(timeline,scrollTweenDuration,{progress:currentScrollProgress,ease:ease});.
Code used in my example is as follows:
HTML:
<div> </div>
<div> </div>
...
CSS:
html, body { margin: 0; padding: 0; }
div { width: 100%; height: 60px; margin: 2px 0; }
div:nth-child(odd) { background: #cc0; }
div:nth-child(even) { background: #0cc; }
JavaScript:
/*global TweenMax, TimelineMax,Power2*/
var myDIVs=document.querySelectorAll('div'),numDIVs=myDIVs.length;
var timeline=new TimelineMax({paused:true}),duration=.4,ease=Power2.easeOut,staggerFactor=.1,scrollTweenDuration=.4;
var scrollTimeout=null,scrollTimeoutDelay=20,currentScrollProgress=0;
var maxScroll=Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)-window.innerHeight; //see [https://stackoverflow.com/a/17698713/3344111]
function init(){
initTimeline();
listenToScrollEvent();
onScroll();
}
function initTimeline(){
for(var i=0; i<numDIVs; i+=1){ timeline.fromTo(myDIVs[i],duration,{opacity:0},{opacity:1,ease:ease},i*staggerFactor); }
}
function listenToScrollEvent(){
(window.addEventListener)?window.addEventListener('scroll',debounceScroll,false):window.attachEvent('onscroll',debounceScroll);
}
function debounceScroll(){
clearTimeout(scrollTimeout);
scrollTimeout=setTimeout(onScroll,scrollTimeoutDelay);
}
function onScroll(){
currentScrollProgress=roundDecimal(window.scrollY/maxScroll,4);
//timeline.progress(currentScrollProgress); // either directly set the [progress] of the timeline which may produce a rather jumpy result
TweenMax.to(timeline,scrollTweenDuration,{progress:currentScrollProgress,ease:ease}); // or tween the [timeline] itself to produce a transition from one state to another i.e. it looks smooth
}
function roundDecimal(value,place){ return Math.round(value*Math.pow(10,place))/Math.pow(10,place); }
//
init();
Here is the resulting jsFiddle. Hope it helps.
T
While Tahir's answer is correct and sufficient, there's a lot of unnecessary code to show the example.
A more concise snippet is:
var max_scroll = document.body.offsetHeight - window.innerHeight;
win.addEventListener('scroll', function(){
var scroll_perc = parseFloat(Math.min(window.pageYOffset / max_scroll, 1).toFixed(2));
TweenMax.to(tl, 0, {
progress: scroll_perc
});
});
var tl = new TimelineMax({paused: true});
// the rest of your timeline....