To understand how 2D animation works, you should understand how textures are mapped to a surface. When you pass in vertex data along with a texture to the graphics card, UV coordinates are passed in with it, to specify a (usually) normalized position on the texture that the vertex is located at. For instance, when rendering a sprite to a single billboard, you would usually pass in uv coordintes like so.
Vertex01: (0,0), UV: (0,0)
Vertex02: (0,1), UV: (0,1)
Vertex03: (1,0), UV: (1,0)
Vertex04: (1,1), UV: (1,1)
This would map the entire texture to the billboard, which is fine for a single image, but if you want to use a sprite sheet for animation, then you would want to map to a portion of it only.
So lets say you have 4 sprites of even size on a sprite sheet, and you wanted to map the first frame of the sheet to a billboard, these UV coordinates would allow you to do map only 1/4 of the texture to the billboard.
Vertex01: (0,0), UV: (0,0)
Vertex02: (0,1), UV: (0,0.25)
Vertex03: (1,0), UV: (0.25,0)
Vertex04: (1,1), UV: (0.25,0.25)
So if you wanted to change the frame on this billboard, you would update the UV coordinates based on the dimensions of the frame.
On top of this system, you would need to implement the animation framework that updates the animation, at the frame rate you want. Keeping track of the time since the previous frame update, you can ensure you are only changing frames every n milliseconds to achieve the framerate you want.