I set every frame of my test animation to 66 ms. When I read the file back, every frame said 60 ms.
That is not a rounding bug in my code. GIF stores frame delay in hundredths of a second, so 66 ms has no representation in the file at all — the nearest it can hold is 6 units. Frame intervals in a GIF are always multiples of 10 ms. That was the point where I stopped blaming my screen recorder for the file size and started reading the format instead.
I'm a CS senior and I needed a short demo clip for my thesis mid-term review. 4.5 seconds of a back-office UI, exported as a GIF, came out at 1.5 MB — too big for the group chat, too big to drop into the document. Lowering the resolution and the frame rate bought me about a third after an evening of fiddling.
I couldn't publish the real recording, so I drew a fictional one frame by frame with Pillow: 960×600, 75 frames, 60 ms each, 4.5 seconds — a table filling in row by row, a cursor moving to a filter, a confirmation dialog. I drew it at 2× and scaled it down, the way a retina capture would be exported; without that step the frames are flat color blocks and the GIF comes out unrealistically small. On disk: 1,585,112 bytes. Self-made and fictional, not a screenshot of any real product.
Every frame carries its own color table
A frame is one still image in the animation; a color table (palette) is the list of colors that frame may use, and each pixel stores an index into that list rather than a color. GIF caps the list at 256 entries, which made sense in 1987 when displays couldn't show more anyway.
The expensive part is the second rule: the table isn't shared across the file, so every frame may carry its own. In my sample, 74 of the 75 frames each carry a 256-entry palette. At 3 bytes per entry that's roughly 57 KB spent on "which colors is this frame allowed to use" before a single pixel of picture is counted.
One rectangle per frame, and that's it
GIF does have interframe optimization. It's called partial refresh: a frame repaints one rectangular region and inherits everything outside it from the previous frame. That region is usually called the dirty rect. The catch is in the singular — one rectangle per frame.
My recording has three things moving at once: the pointer on the left, a clock ticking in the table header on the right, a blinking caret in the search box. Three corners of the screen, one rectangle to cover all of them, so the rectangle swells to most of the canvas. The numbers: 68 of the 75 frames did use partial refresh, and the average dirty rect still covered 59% of the canvas. On a full-screen gradient sample, 0 of 30 frames could use it at all — 100% repainted every frame.
A video encoder splits the picture into blocks and asks where each block moved from. GIF can only say "repaint this rectangle". Encoding the same 75 frames locally with ffmpeg at H.264 default settings gives 82,628 bytes, which makes the GIF 19.2× larger — one sample, one setting, and a sample that is mostly static, so expect that ratio to drop hard on fast-moving footage.
Where the savings actually are
For the format comparison I used the animation workshop on ImgIng (https://imging.ai/), mostly because all four animated formats export from the same page with everything else untouched, which keeps the variables clean. It also runs in the browser — I kept the Network panel open through every run and never saw a file upload request.
I expected WebP to be the answer. It wasn't, at least not here: 1,045,924 bytes, 34% off the original, about the same as re-encoding the GIF. APNG is lossless — its frames are pixel-identical to the source — so it stays at 1.33 MB. Animated AVIF is the one that moves the needle: 157,490 bytes, 9.94% of the original. On a gradient sample the ranking flips again, with WebP down 76% and GIF down only 14%. Why WebP does so well on gradients and so little on a screen recording is the part I still haven't worked out.
One more result worth knowing. I took a GIF I had already squeezed once — scaled to 70%, every second frame dropped, palette cut to 64 colors, 357,648 bytes — and re-encoded it as GIF: 356,964 bytes. 684 bytes saved, frames pixel-identical. Re-encoded as AVIF instead, it still dropped another 62%. "Already optimized, can't compress further" is only true inside the same format.
I'm not going to tell you to switch everything to AVIF. I verified decode and playback in three desktop browser engines and nothing else — no messaging apps, no old phones, no in-app webviews — and the tool's own result card notes that its animated AVIF carries no transparency. So I decide by where the file is going: if I control the player, AVIF; if someone else does, WebP; and if it has to stay a GIF, drop frames, the cheapest change at 28.8% off, remembering that it also makes the animation play twice as fast.

Top comments (0)