On this article, Iโm going to clarify how one can convert animations created utilizing GSAP into animated GIFs utilizing modern-gif.
Right hereโs a sneak peek of 1 I made earlier. ๐
On the hyperlinks beneath, youโll discover a dwell preview and all of the code Iโll be referencing all through this text:
There are two โpagesโ within the repo. index accommodates all of the code for the GIF seen above, and simple is a place to begin for the steps coated on this put up.
The right way to convert GSAP Animations into GIFs
The strategy Iโm utilizing to transform a GSAP animation right into a GIF entails capturing SVG information on every โreplaceโ of the Tween and writing it to an HTML canvas. After the Tween completes Iโm then capable of convert SVG information into Rasterized picture information which can be utilized by modern-gif to create every body of an animated GIF.
Getting Began
Right hereโs the code Iโve used within the easy instance, and itโs what Iโll be utilizing to clarify every of the steps required to create an animated GIF from a GSAP animation:
<html lang='en'>
<head>
<meta charset='utf-8' />
<title>Easy</title>
<script>
const canvas = doc.getElementById('canvas');
const ctx = canvas.getContext('2nd');
let animationFrames = [];
let canvasFrames = [];
gsap.timeline({
onUpdate: () => {},
onComplete: () => {},
})
.fromTo('#rect', { x: -50 }, { length: 2, x: 350, ease: 'energy.ease2' });
</script>
</head>
<physique>
<important>
<part>
<svg
id='svg'
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 400 200'
width={400}
top={200}
model={{ border: '1px strong purple' }}
>
<rect id='rect' x='0' y='75' width='50' top='50' fill='purple'></rect>
</svg>
<canvas id='canvas' model={{ border: '1px strong blue' }} width={400} top={200}></canvas>
<img id='picture' width={400} top={200} model={{ border: '1px strong inexperienced' }} />
<a id='hyperlink' obtain='easy.gif'>Obtain</a>
</part>
</important>
<script src='https://unpkg.com/modern-gif'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js'></script>
</physique>
</html>
There are a few issues Iโd like to clarify in regards to the above code.
Inline Script
On the high of the file I create a reference to the canvas
component within the HTML (beneath) and outline a brand new reference to the canvas context referred to as ctx
. This may enable me to reference the canvas component and write information to it.
There are two arrays outlined to carry the captured information (Iโll clarify the place every is utilized in a later step):
animationFrames
canvasFrames
And final, however not least, an occasion of a GSAP Timeline and Tween that animates an SVG rect
component within the HTML (beneath).
HTML
- The HTML accommodates an
svg
component with an ID ofsvg
with a purplerect
component with an ID ofrect
. Therect
is the component Iโll be animating. - Under the
svg
component is acanvas
component. That is the place Iโll write the captured SVG information to be used afterward. - Under the
canvas
component is animg
component. That is the place the ultimate animated GIF might be displayed. - Lastly, thereโs an a component which can be utilized to โobtainโ the GIF.
Script parts
The 2 script parts on the backside are for the modern-gif library and GSAP library. Each should be included within the web page so you should utilize them.
Capturing SVG Knowledge
Find the GSAP Timeline and make the next modifications:
gsap.timeline({
onUpdate: () => {
+ const xml = new XMLSerializer().serializeToString(svg);
+ const src = `information:picture/svg+xml;base64,${btoa(xml)}`;
+ animationFrames.push(src);
},
onComplete: () => {
+ console.log(animationFrames);
},
})
.fromTo('#rect', { x: -50 }, { length: 2, x: 350, ease: 'energy.ease2' });
The above code serializes the HTML svg
component and converts the info to an svg+xml;base64
string. At this level, the โpicture informationโ isnโt fairly what I would like, however by changing it to a string I can retailer it within the animationFrame
array to be used afterward.
In case youโve added the console.log
within the onComplete
perform, it is best to see one thing much like the picture beneath within the console of your browser.
Convert SVG Knowledge to Rasterized Knowledge
gsap.timeline({
onUpdate: () => {
const xml = new XMLSerializer().serializeToString(svg);
const src = `information:picture/svg+xml;base64,${btoa(xml)}`;
animationFrames.push(src);
},
onComplete: () => {
- console.log(animationFrames);
+ let inc = 0;
+ const renderSvgDataToCanvas = () => {
+ const virtualImage = new Picture();
+ virtualImage.src = animationFrames[inc];
+ virtualImage.onload = () => {
+ ctx.clearRect(0, 0, 400, 200);
+ ctx.drawImage(virtualImage, 0, 0, 400, 200);
+ canvasFrames.push(canvas.toDataURL('picture/jpeg'));
+ inc++;
+ if (inc < animationFrames.size - 1) {
+ renderSvgDataToCanvas();
+ } else {
+ console.log(canvasFrames);
+ }
+ };
+ };
+ renderSvgDataToCanvas();
},
})
.fromTo('#rect', { x: -50 }, { length: 2, x: 350, ease: 'energy.ease2' });
This step is barely extra concerned and requires that I carry out an motion for every index of the animationFrames
array.
By utilizing a recursive perform, renderSvgDataToCanvas
, I can use the picture information from the animationFrames
array, write it to the canvas. Then, by utilizing canvas.toDataURL('picture/jpeg')
I can retailer rasterized information of every body of the animation within the canvasFrames
array.
In case youโve added the console.log
within the onComplete
perform, it is best to see one thing much like the beneath within the console of your browser. This time, nonetheless, be aware the MIME sort of the info: as an alternative of svg+xml
, itโs picture/jpeg
. That is essential for what I have to do subsequent.
Convert Rasterized Knowledge to GIF
That is the final step and entails passing every index of the canvasFrames
array onto modern-gif.
gsap.timeline({
onUpdate: () => {
const xml = new XMLSerializer().serializeToString(svg);
const src = `information:picture/svg+xml;base64,${btoa(xml)}`;
animationFrames.push(src);
},
onComplete: () => {
let inc = 0;
const renderSvgDataToCanvas = () => {
const virtualImage = new Picture();
virtualImage.src = animationFrames[inc];
virtualImage.onload = () => {
ctx.clearRect(0, 0, 400, 200);
ctx.drawImage(virtualImage, 0, 0, 400, 200);
canvasFrames.push(canvas.toDataURL('picture/jpeg'));
inc++;
if (inc < animationFrames.size - 1) {
renderSvgDataToCanvas();
} else {
- console.log(canvasFrames);
+ generateGif();
}
};
};
+ const generateGif = async () => {
+ const gif = await modernGif.encode({
+ width: 400,
+ top: 200,
+ frames: canvasFrames.map((body) => {
+ return { imageData: body, delay: 0 };
+ }),
+ });
+ const frames = await gif;
+ const blob = new Blob([frames], { sort: 'picture/gif' });
+ const src = URL.createObjectURL(blob);
+ const picture = doc.getElementById('picture');
+ const hyperlink = doc.getElementById('hyperlink');
+ picture.src = src;
+ hyperlink.href = src;
+ };
renderSvgDataToCanvas();
},
})
.fromTo('#rect', { x: -50 }, { length: 2, x: 350, ease: 'energy.ease2' });
Utilizing modernGif.encode you’ll be able to go an array of knowledge onto frames and outline a delay for every body, Iโve chosen so as to add a delay of 0 seconds.
The subsequent a part of the code offers with changing the modernGif.ecode
information and changing it to โone moreโ MIME sort, this time picture/gif
.
As soon as I’ve a ultimate โblobโ of knowledge that represents my animated GIF I convert it to a URL after which set the src
and href
of the picture and hyperlink parts so I can see and obtain the GIF within the browser.
Body Charge
You may discover the ultimate GIF runs fairly slowly, it is because animations that run within the browser will sometimes play again at 60 frames per second (fps), whereas GIFs sometimes run at a a lot slower body fee, 12 or 24fps.
To โdropโ some frames of the animation I take advantage of an array filter and JavaScript remainder operator to find out if the index is divisible by a sure quantity, in my case, I selected 6. Indexes that arenโt divisible by 6 are filtered out of the array. The ensuing animated GIF, whereas just a little clunky, will play again a lot quicker.
const generateGif = async () => {
const gif = await modernGif.encode({
width: 400,
top: 200,
frames: canvasFrames
+ .filter((_, index) => index % 6 === 0)
.map((body) => {
return { imageData: body, delay: 0 };
}),
});
const frames = await gif;
const blob = new Blob([frames], { sort: 'picture/gif' });
const src = URL.createObjectURL(blob);
const picture = doc.getElementById('picture');
const hyperlink = doc.getElementById('hyperlink');
picture.src = src;
hyperlink.href = src;
};
And thatโs how one can go from GSAP SVG animation to animated GIF by way of the HTML canvas!
When you’ve got any questions on something Iโve described on this put up be at liberty to seek out me on Twitter/X: @PaulieScanlon.