I have a simple GSAP animation that I want to only run while the app is waiting for a request to finish. I can get the animation to run by default, but the issue I'm having now is that when I try to trigger it within an onClick function it seems like sometimes the animation starts, sometimes it doesn't, and I can't seem to track the rhyme or reason behind it. I have a hunch that it has something to do with the timing of when I'm trying to play the timeline and when I'm revealing the component, but I'm not totally certain of this and so far my tinkering and attempts at moving the timeline.play()
around haven't seen much success.
CODE:
App.js
import React, { useState, useRef, useLayoutEffect } from 'react';
import { gsap } from 'gsap';
import './App.css';
import DataInput from './Components/data-input';
import Footer from './Components/footer';
import Header from './Components/header';
import RedBall from './Components/red-ball';
const timeline = gsap.timeline({paused: true, repeat: -1, yoyo: true});
function App() {
const [messageData, setMessageData] = useState(null);
const [zipData, setZipData] = useState(null);
const [screenShotData, setScreenshotData] = useState(null);
const [statusMessage, showStatusMessage] = useState(false);
const tl = useRef(timeline);
const app = useRef(null);
let zipBlob;
let zipDownload;
let url;
useLayoutEffect(() => {
const ctx = gsap.context(() => {
tl.current.fromTo('.red-ball', .5, {autoAlpha: 0, x: 0}, {autoAlpha: 1, x: 20});
}, app.current);
return () => ctx.revert();
}, []);
const getScreenshotData = (screenShotData) => {
setScreenshotData(screenShotData);
showStatusMessage(true);
setMessageData('');
setZipData('');
timeline.play();
fetch(`/dcsgrab?tearsheetUrl=${screenShotData}`)
.then((response) => response.json())
.then((data) => {
zipBlob = new Blob([new Uint8Array(data.zipFile.data)], {type: "octet/stream"});
url = window.URL.createObjectURL(zipBlob);
zipDownload = document.createElement("a");
setMessageData(data.message);
setZipData(zipBlob);
zipDownload.href = url;
zipDownload.download = "screenshot-download.zip";
document.body.appendChild(zipDownload);
zipDownload.click();
console.log(zipBlob);
console.log([new Uint8Array(data.zipFile.data)]);
console.log(data);
});
};
return (
<div className="App" ref={app}>
<Header />
<DataInput getScreenshotData={getScreenshotData} />
{
!statusMessage ? '' : <p>{!messageData ? 'Taking screenshots...' : messageData}</p>
}
{
!statusMessage ? '' : <div className="waiting-anim-container">{!messageData ? <RedBall /> : ''}</div>
}
<Footer />
</div>
);
}
export default App;
red-ball.js
export default function RedBall() {
return <div className="red-ball"></div>;
}
data-input.js
import React from 'react';
import { useState } from 'react';
function DataInputForm({ getScreenshotData }) {
const [tearsheetUrl, setTearsheetUrl] = useState('');
return (
<div>
<form>
<input
id='tearsheetUrl'
name='tearsheetUrl'
placeholder='input tearsheet url'
type='text'
value={tearsheetUrl}
onChange={(event) => setTearsheetUrl(event.target.value)}
/>
</form>
<button id="submit-data" onClick={() => getScreenshotData(tearsheetUrl)}>SUBMIT</button>
</div>
);
}
export default DataInputForm;
Ok, so I was able to get this figured out. Ultimately what had to happen is I had to tie the desired animation behavior of the redball component to the actual state, which makes sense since this is React. I was initially able to get the animation to work on button click, but then had an issue getting it to go away once the specific action it was tied to had completed. The below code handles this by triggering a css class, soft-hide, depending on the hideClass state.
The particular lines of importance are:
const [hideClass, toggleHideClass] = useState(true);
let redBallContainerClasses = !hideClass ? 'waiting-anim-container' : 'waiting-anim-container soft-hide';
toggleHideClass(false);
toggleHideClass(true);
<div className={redBallContainerClasses}><RedBall /></div>
Full code:
import React, { useState, useRef, useLayoutEffect } from 'react';
import { gsap } from 'gsap';
import './App.css';
import DataInput from './Components/data-input';
import Footer from './Components/footer';
import Header from './Components/header';
import RedBall from './Components/red-ball';
const timeline = gsap.timeline({paused: true, repeat: -1, yoyo: true});
function App() {
const [messageData, setMessageData] = useState(null);
const [statusMessage, showStatusMessage] = useState(false);
const [hideClass, toggleHideClass] = useState(true);
const tl = useRef(timeline);
const app = useRef(null);
let zipBlob;
let zipDownload;
let url;
let redBallContainerClasses = !hideClass ? 'waiting-anim-container' : 'waiting-anim-container soft-hide';
useLayoutEffect(() => {
const ctx = gsap.context(() => {
tl.current.fromTo('.red-ball', .5, {autoAlpha: 0}, {autoAlpha: 1, scale: 2});
}, app.current);
return () => ctx.revert();
}, []);
const getScreenshotData = (screenShotData) => {
showStatusMessage(true);
setMessageData('');
toggleHideClass(false);
timeline.revert();
timeline.play();
fetch(`/dcsgrab?tearsheetUrl=${screenShotData}`)
.then((response) => response.json())
.then((data) => {
zipBlob = new Blob([new Uint8Array(data.zipFile.data)], {type: "octet/stream"});
url = window.URL.createObjectURL(zipBlob);
zipDownload = document.createElement("a");
toggleHideClass(true);
timeline.revert();
setMessageData(data.message);
zipDownload.href = url;
zipDownload.download = "screenshot-download.zip";
document.body.appendChild(zipDownload);
zipDownload.click();
console.log(zipBlob);
console.log([new Uint8Array(data.zipFile.data)]);
console.log(data);
});
};
return (
<div className="App" ref={app}>
<Header />
<DataInput getScreenshotData={getScreenshotData} />
{
!statusMessage ? '' : <p>{!messageData ? 'Taking screenshots...' : messageData}</p>
}
<div className={redBallContainerClasses}><RedBall /></div>
<Footer />
</div>
);
}
export default App;