---
title: "Render a CSS animation frame to PNG in C#"
url: https://recursiveai.org/onerender/cookbook/render-css-animation-frame/
description: "Capture a CSS keyframe animation at a chosen time, without waiting for a browser. JavaScript is not executed. Complete .NET example with source and output."
version: "1.4.0"
---

# Render a CSS animation frame to PNG in C#

**OneRender** is a free, open-source HTML and CSS rendering library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneRender.Image) · [GitHub](https://github.com/recursive-ai-oss/onerender-csharp) · [Docs](https://recursiveai.org/onerender/docs/) · [Download example (.zip)](https://recursiveai.org/onerender/cookbook/render-css-animation-frame/render-css-animation-frame.zip)

Capture a CSS keyframe animation at a chosen time, without waiting for a browser. JavaScript is not executed.

```shell
dotnet add package RecursiveAI.OneRender.Image
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using OneRender.Image;

var image = HtmlRender.ToImage(
    File.ReadAllText("scene.html"),
    new HtmlRenderOptions { Width = 240,
        Height = 80, AnimationTime = 500 });
File.WriteAllBytes("frame.png", image.ToPng());
Console.WriteLine(
    $"frame.png: {image.Width}x{image.Height}");
```

```text
frame.png: 240x80
```

`scene.html` is included in the download and shown below. `AnimationTime` is in milliseconds.

Unzip [the example](https://recursiveai.org/onerender/cookbook/render-css-animation-frame/render-css-animation-frame.zip), then `dotnet run`.

```html
<!doctype html>
<style>
body { margin: 0; background: white; }
div { width: 40px; height: 40px; background: #e03040;
      animation: slide 1s linear forwards; }
@keyframes slide { from { transform: translateX(0); }
                   to { transform: translateX(160px); } }
</style>
<div></div>
```

## The full program

The program writes frames at zero and 500 milliseconds. The red square is at the start in the first image and halfway through its horizontal motion in the second.

```csharp
using OneRender.Image;

string html = File.ReadAllText("scene.html");
foreach (int time in new[] { 0, 500 })
{
    var image = HtmlRender.ToImage(html, new HtmlRenderOptions
    {
        Width = 240, Height = 80, AnimationTime = time
    });
    File.WriteAllBytes($"frame-{time}.png", image.ToPng());
    Console.WriteLine($"frame-{time}.png: {image.Width}x{image.Height}");
}
```

`dotnet run` prints:

```text
frame-0.png: 240x80
frame-500.png: 240x80
```

`frame-0.png` (0 ms):

![Red square at 0 milliseconds](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAABQAQMAAAAdh2o/AAAABlBMVEXgMED///+JqGTkAAAAJUlEQVR4nO3JMQ0AAAgDsPk3DUEAApa0b5Mzn2itdV2/o7XWnb3EyI/h5O3+rgAAAABJRU5ErkJggg==)

`frame-500.png` (500 ms):

![Red square at 500 milliseconds](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAABQAQMAAAAdh2o/AAAABlBMVEX////gMEBVunS9AAAAJElEQVR4nGNggIP/IMCAE4xKj0qPSo9KDznpUTAKRsEoGBYAAMODxzl94lSOAAAAAElFTkSuQmCC)

A missing `scene.html` raises an I/O exception; unsupported CSS animation features do not become supported by setting a timestamp.

## Variations

Capture the end of the same animation. Run this in the example folder; it prints `end.png: 240x80`.

```csharp
using OneRender.Image;

var image = HtmlRender.ToImage(File.ReadAllText("scene.html"),
    new HtmlRenderOptions { Width = 240, Height = 80, AnimationTime = 1000 });
File.WriteAllBytes("end.png", image.ToPng());
Console.WriteLine($"end.png: {image.Width}x{image.Height}");
```

## Related recipes

- [Convert HTML to PNG in C#](https://recursiveai.org/onerender/cookbook/html-to-png/)
- [Change HTML text, classes and attributes in C#](https://recursiveai.org/onerender/cookbook/update-page/)

## The short answer

Set `HtmlRenderOptions.AnimationTime` in milliseconds and save `HtmlRender.ToImage(...).ToPng()`. An explicit timestamp samples CSS keyframes; it does not record a live browser.

## Can you do animations with CSS?

Yes. For this task, define a CSS `@keyframes` rule and apply it with an `animation` declaration. The input above moves a square from zero to 160 CSS pixels over one second, using linear timing. Setting `AnimationTime` to 500 samples the middle of that movement. The PNG itself is static: it holds that one sampled frame, not a playable animation.

OneRender’s image API applies the document’s CSS animations at an explicit time. It does not need a delay in the C# program. Waiting half a second before calling the renderer is not how you select the half-second frame. Pass the timestamp as an option instead. This also lets the full program request the start and midpoint independently, using the same HTML and viewport for each.

This recipe uses CSS keyframes only. Scripts are not executed, so an animation created by JavaScript is not an input this path can replay. If your visual depends on script-generated markup, provide that markup as HTML before rendering.

## How do I render a CSS animation frame in C#?

Use `HtmlRender.ToImage` with `HtmlRenderOptions.AnimationTime`, then write the result’s PNG bytes. The option is a nullable number of milliseconds on the host’s clock. With an explicit value, animations start at zero and the renderer shows their state at that value. Negative CSS animation delays are also accounted for. See the [animation behavior notes](https://recursiveai.org/onerender/docs/animations/).

Keep three choices separate: the viewport width and height, the animation’s CSS duration, and the timestamp being sampled. A larger viewport does not change the duration. A later timestamp does not enlarge the PNG. In this example both files remain 240 by 80 pixels while the square changes position. Use fixed input assets and options when producing a reproducible sequence of sampled frames.

The full program writes two PNGs rather than an animated image or video. To request more frames, repeat the same render operation with the timestamps you need and distinct output names. File order alone does not specify playback timing; any later video or animation assembly must choose that separately.

## Why is my CSS animation static?

In the [page API](https://recursiveai.org/onerender/docs/overview/), `HtmlPageOptions.AnimationTime` sets the sampled time and `InteractivePage` manages a live page. A page parsed without that time option and not wrapped in `InteractivePage` is static. On the image convenience API, omitting `AnimationTime` likewise leaves it null and renders the cascaded styles with no animation applied. Zero is different from null: zero explicitly samples the start of an animation.

An animation can also look unchanged when you sample outside its active interval or when its own CSS produces the same values at the chosen points. Check the name, duration, delay, timing function and fill mode before changing the C# code. Here `forwards` retains the final value after the animation finishes.

Not every animation feature is implemented. The pinned release does not implement `animation-composition`, `animation-timeline`, `animation-range` or `var()` inside keyframes. Pseudo-elements, `::marker` and `::first-line` do not animate. Colors interpolate premultiplied in sRGB, including colors originally written in another space. These are renderer behaviors, not errors that a longer wait or a different screenshot timestamp can fix.
