30 FPS Does Not Exist in GIF
Ask any GIF encoder for 30 frames per second and it will say yes. It is lying, and the reason is four bytes deep in the file format: a GIF frame’s delay is a whole number of hundredths of a second, and 100/30 is not a whole number.
Every frame in a GIF carries its own delay, stored in a Graphic Control Extension block that looks like this:
0x21 0xF9 0x04 <packed> <delay lo> <delay hi> <transparent> 0x00
The two delay bytes are a 16-bit little-endian integer, and its unit is hundredths of a second. Not milliseconds. Not a rational number. A whole count of centiseconds, from 0 to 65,535.
That single fact fixes the entire set of frame rates a GIF can express. If the delay must be an integer n, the rate must be 100/n:
100, 50, 33.33, 25, 20, 16.67, 14.29, 12.5, 11.11, 10, 9.09, 8.33, 7.69,
7.14, 6.67, 6.25, 5.88, 5.56, 5.26, 5.00 …
Thirty is not in that list. Neither is 60, nor 24, nor 15 — three of the four rates most video is shot at.
What an encoder actually does with your 30
It computes 100/30 = 3.33 centiseconds, discovers it cannot store that, and rounds to 3. Your GIF now runs at 33.33 fps, 11.11 % fast. The error is not the interesting part; the duration is:
| You ask for | Delay stored | Plays at | Error | Ten seconds becomes |
|---|---|---|---|---|
| 60 fps | 2 cs | 50.00 fps | −16.67 % | 12.00 s |
| 50 fps | 2 cs | 50.00 fps | exact | 10.00 s |
| 30 fps | 3 cs | 33.33 fps | +11.11 % | 9.00 s |
| 25 fps | 4 cs | 25.00 fps | exact | 10.00 s |
| 24 fps | 4 cs | 25.00 fps | +4.17 % | 9.60 s |
| 20 fps | 5 cs | 20.00 fps | exact | 10.00 s |
| 15 fps | 7 cs | 14.29 fps | −4.76 % | 10.50 s |
| 12 fps | 8 cs | 12.50 fps | +4.17 % | 9.60 s |
| 10 fps | 10 cs | 10.00 fps | exact | 10.00 s |
| 8 fps | 13 cs | 7.69 fps | −3.85 % | 10.40 s |
| 5 fps | 20 cs | 5.00 fps | exact | 10.00 s |
Ten seconds of video captured at 30 fps is 300 frames. At 3 centiseconds each that is 900 centiseconds — nine seconds. The GIF ends a full second before the clip it was made from. Ask for 60 and it runs long instead: 600 frames at 2 cs is 12.00 seconds.
Of the rates anyone actually asks for, only 50, 25, 20, 10 and 5 come back exactly. Everything else drifts, and it drifts by a fixed percentage that grows with the length of the clip. Cut a GIF against music and this is the whole explanation for why the loop walks away from the beat.
Encoders hide it by alternating two delays
Here is where it stops being arithmetic and starts being a thing you can read out of a file on your disk. A well-made encoder does not round every frame the same way — it dithers, so the average comes out right.
We took a GIF produced by ffmpeg, nominally at 12 fps, and walked its blocks to read the delay of every frame:
8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8, 8, 9, 8
Twenty-four frames. 200 centiseconds. Exactly 2.00 seconds, which is exactly 12.0000 frames per second on average. The average is perfect — and no frame in the file is 12 fps. Consecutive frames are 80 ms and then 90 ms apart, a 12.5 % jump between neighbours that never resolves, because the encoder is buying an impossible rate with two possible ones.
The consequence is worth saying in one sentence: “the frame rate of a GIF” is usually an average that no individual frame has. Any tool that reports a single number for a GIF is reporting something that is not in the file. If you want to know what your GIF really does, you have to look at every delay.
Changing the speed should not touch a pixel
Since the timing lives entirely in those two bytes per frame, retiming a GIF is a byte edit. The palette, the LZW-compressed pixel data, the frame positions, the transparency index — none of it needs to be read, let alone rewritten.
Most GIF speed changers do it the other way round: decode to frames, re-encode, re-quantise the palette. That costs a visible generation of colour every time you touch the file, and it is why a GIF that has been through two or three tools looks worse than the original for no reason anybody can point at.
We measured the byte edit on the 24-frame file above, which is 54,596 bytes:
| Retimed to | Bytes changed | Per frame | Share of file |
|---|---|---|---|
| 20 cs (5 fps) | 24 | 1 | 0.044 % |
| 4 cs (25 fps) | 24 | 1 | 0.044 % |
| 3 cs (33.33 fps) | 24 | 1 | 0.044 % |
| 2× speed (4, 5, 4, 4, 5 …) | 24 | 1 | 0.044 % |
| 300 cs (0.33 fps) | 48 | 2 | 0.088 % |
One byte per frame, not two, for everything except the last row — because the delay’s high byte is zero on both sides of the edit unless the delay crosses 256 centiseconds. Two bytes are written per frame; usually only one of them differs. Either way the file comes back the same length, with the pixel data bit-for-bit identical, which is the property that matters.
Why your browser cannot tell you any of this
The obvious way to check a GIF’s timing is to load it and watch it. It does not work, and the failure is absolute rather than approximate.
We built six copies of the same 12-frame GIF — identical pixels, only the delay bytes rewritten — with delays of 0, 1, 2, 3, 5 and 10 centiseconds. Each was displayed on screen and drawn to a canvas every 4 ms for four seconds, hashing the pixels each time to count how often the frame changed.
Zero advances. On all six files. Drawing an animated GIF to a canvas returns a static frame: the browser will animate the image on screen but will not hand the current frame to your code. That is not a bug we worked around — it is the reason a tool that wants to tell you the truth about a GIF has to parse the file itself.
It also means there is one number we are not going to give you. Browsers enforce their own minimum frame delay, and very small values are treated as something larger — but we could not measure that here, for exactly the reason above, so no figure for it appears anywhere on our pages. Everything else on this page came off a real file.
What to do about it
If you can choose your rate, choose one that exists: 25, 20 or 10 fps are exact, and 25 is close enough to 24 that nobody will see the difference. If you need 30 or 60 exactly, you do not need a GIF — you need a container with a finer timebase, which is the honest reason to convert it to MP4. An MP4 at 30 fps is 30 fps, and it will usually be a fraction of the size.
And if you just want to see what your own GIF is doing, the GIF speed changer draws every frame’s delay as a bar, tells you the exact rate you will get for the rate you asked for, and reports how many bytes it changed to get there. It runs in your browser; nothing is uploaded.
FAQ
Why can a GIF not do 30 fps when video can?
Because the GIF format stores each frame’s delay as a whole number of hundredths of a second, and 100/30 is 3.33 — a delay that does not exist. Video containers store a timebase as a fraction, so 30 fps is exactly 30/1 and 29.97 is exactly 30000/1001. It is a limitation of the file format from 1989, not of your encoder.
My GIF says 12 fps but the frames look uneven. Is it broken?
Almost certainly not — it is dithered on purpose. We read a real ffmpeg GIF nominally at 12 fps and found delays of 8, 9, 8, 8, 9 repeating: 24 frames totalling 200 centiseconds, which averages to exactly 12.0000 fps while consecutive frames sit 80 ms and 90 ms apart. The average is right and no single frame has it.
Does changing a GIF’s speed reduce its quality?
It does not have to. The timing lives in two bytes per frame, so retiming can be a pure byte edit that never decodes a pixel. On our 24-frame test file, retiming changed 24 bytes out of 54,596 and the file came back the same length. Tools that decode and re-encode do lose quality, because they re-quantise the palette every time.
What does a frame delay of 0 mean?
That the file declines to specify one, and every viewer decides for itself. It is the one case where playback is genuinely unpredictable across browsers and apps, so it is worth writing a real delay rather than leaving it at zero.
Which frame rates are exact in GIF?
Any rate of the form 100/n. Among the ones people ask for, that is 100, 50, 25, 20, 12.5, 10, 5, 4, 2 and 1 fps. The practical short list is 25, 20 and 10 — and 25 fps is a close enough stand-in for 24 that the difference is invisible.