Capture a CSS keyframe animation at a chosen time, without waiting for a browser. JavaScript is not executed.
dotnet add package RecursiveAI.OneRender.ImageRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
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}");
frame.png: 240x80scene.html is included in the download and shown below. AnimationTime is in milliseconds.
Unzip the example, then dotnet run.
<!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.
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:
frame-0.png: 240x80 frame-500.png: 240x80
frame-0.png (0 ms):
frame-500.png (500 ms):
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.
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
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.
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, 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.