Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4cfddaf
Changed version
FlorianRappl Sep 7, 2026
6eb4aa8
Added rounded corners
FlorianRappl Sep 7, 2026
3e3b595
Added text- and box-shadow
FlorianRappl Sep 7, 2026
636a2f9
Added support for ol/ul lists
FlorianRappl Sep 7, 2026
25ed434
Improved clipping support
FlorianRappl Sep 7, 2026
88a5378
Fixed scrolling of content
FlorianRappl Sep 7, 2026
2ed9d1e
Improved background image handling
FlorianRappl Sep 7, 2026
1a7efaf
Added and improved form drawing
FlorianRappl Sep 8, 2026
28ad942
Regenerate visual snapshot baselines for all platforms
github-actions[bot] Sep 8, 2026
a59671f
Added support for transform
FlorianRappl Sep 9, 2026
94811fa
Added filter declaration
FlorianRappl Sep 9, 2026
30f1c4c
Include opacity
FlorianRappl Sep 9, 2026
c01b706
Added support for transitions
FlorianRappl Sep 9, 2026
b2d316f
Implemented animation
FlorianRappl Sep 9, 2026
6fe4789
Documented
FlorianRappl Sep 9, 2026
85489d1
Bring AngleSharp.Css to 1.1.1 for 0.4 release
FlorianRappl Sep 10, 2026
c5b8fee
Merge branch 'devel' of github.com:AngleSharp/AngleSharp.Renderer int…
FlorianRappl Sep 10, 2026
8fe5658
Updated reference to AngleSharp.Css
FlorianRappl Sep 10, 2026
1aaa634
Regenerate visual snapshot baselines for all platforms
github-actions[bot] Sep 10, 2026
1c1cefc
Improved whitespace and form control rendering
FlorianRappl Sep 10, 2026
dc1441b
Updated min required version
FlorianRappl Sep 10, 2026
89db8b8
Regenerate visual snapshot baselines for all platforms
github-actions[bot] Sep 10, 2026
cbdd9de
Tolerance for non-matching pixels
FlorianRappl Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ env:
DOCS_BRANCH: ${{ secrets.DOCS_BRANCH }}
# Snapshot baselines are per-platform and committed; a missing one is a gap, never a pass.
ANGLESHARP_SNAPSHOT_STRICT: 1
ANGLESHARP_VERSION: 1.8.0
ANGLESHARP_CSS_VERSION: 1.1.0
ANGLESHARP_VERSION: 1.8.1
ANGLESHARP_CSS_VERSION: 1.1.1

jobs:
can_document:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/update-snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ permissions:
contents: write

env:
ANGLESHARP_VERSION: 1.8.0
ANGLESHARP_CSS_VERSION: 1.1.0
ANGLESHARP_VERSION: 1.8.1
ANGLESHARP_CSS_VERSION: 1.1.1

jobs:
# Keep this matrix identical to the `test` matrix in ci.yml, images included - a baseline
Expand Down
100 changes: 99 additions & 1 deletion AGENTS.md

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# 0.4.0

Released on Thursday, September 10 2026.

- Improved support for clipping and `overflow` declarations
- Improved whitespace handling for rendering
- Fixed painting of scrolled content
- Fixed handling of background images
- Added support for rounded corners
- Added support for transformations and filters
- Added transitions and animations with a virtual clock
- Added support for `text-shadow` declarations
- Added support for `box-shadow` declarations
- Added support for numeric sorted lists (e.g., using `ol`)
- Added support for bullet point lists (e.g., using `ul`)
- Added basic rendering of standard form controls

# 0.3.0

Released on Monday, September 7 2026.
Expand Down
4 changes: 2 additions & 2 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ class Build : FalloutBuild
[Fallout.Common.Parameter("ReleaseNotesFilePath - To determine the SemanticVersion")]
readonly AbsolutePath ReleaseNotesFilePath = RootDirectory / "CHANGELOG.md";

[Fallout.Common.Parameter("AngleSharp package version override (e.g. 1.8.0 for compatibility checks)")]
[Fallout.Common.Parameter("AngleSharp package version override (e.g. 1.8.1 for compatibility checks)")]
readonly string AngleSharpVersion;

[Fallout.Common.Parameter("AngleSharp.Css package version override (e.g. 1.1.0 for compatibility checks)")]
[Fallout.Common.Parameter("AngleSharp.Css package version override (e.g. 1.1.1 for compatibility checks)")]
readonly string AngleSharpCssVersion;

[Solution]
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ We have more detailed information regarding the following subjects:
## General

- [Getting Started](general/01-Basics.md)
- [Interactivity, Transitions & Animations](general/02-Interactivity.md)

## Tutorials

Expand Down
144 changes: 144 additions & 0 deletions docs/general/02-Interactivity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title: "Interactivity, Transitions & Animations"
section: "AngleSharp.Renderer"
---
# Interactivity, Transitions & Animations

`HtmlRenderer.RenderToPng`/`BuildDisplayList` render one static snapshot of a document by
themselves. To simulate mouse hover, drive CSS `transition`s and `animation`s, or read/set scroll
position, a document needs an **interactive DOM harness** - a small piece of state, bound to the
document's browsing context, that tracks things a real browser would (cursor position, scroll
offsets, a running clock) and feeds them back into rendering.

## Getting A Harness

A harness needs an `IRenderDevice` registered on the browsing context, then is created (or
retrieved, if one already exists) via `GetDomHarness()`:

```cs
using AngleSharp;
using AngleSharp.Css;
using AngleSharp.Renderer;

var renderDevice = new DefaultRenderDevice { ViewPortWidth = 320, ViewPortHeight = 200 };
var context = BrowsingContext.New(Configuration.Default.WithCss().WithRenderDevice(renderDevice));
var document = await context.OpenAsync(request => request.Content(html));

var harness = document.Context.GetDomHarness();
```

Every document not wired up this way renders exactly as before - the interactive machinery is a
no-op until a harness exists for that browsing context.

## Simulating `:hover`

Set `MousePosition` in viewport coordinates. The harness hit-tests the layout, forces the
`:hover` pseudo-class on the topmost element under the cursor *and its whole ancestor chain*
(matching how a real pointer makes `.card:hover .title` work, not just the innermost element), and
raises `PaintInvalidated`:

```cs
harness.MousePosition = (50, 40);
var image = harness.PaintToPng();
```

`harness.HoveredElement` exposes the currently hit-tested element if you need it directly.

## The Virtual Clock

There is no real-time timer anywhere in this renderer. CSS `transition`s and `animation`s are both
measured against a virtual clock that only moves when you tell it to, via `AdvanceTime`:

```cs
harness.AdvanceTime(TimeSpan.FromMilliseconds(500));
var midFrame = harness.PaintToPng();
```

Call `AdvanceTime` with however much time you want a frame to represent, then paint - repeating
that pair of calls is how you produce a sequence of frames (for example, to assemble a GIF or
video of an animation) instead of only ever the resting/end states.

## CSS `transition`

A `transition` animates a property when its own natural value changes for some other reason - in
this renderer, that means a `:hover` state change:

```cs
var html = """
<html>
<head>
<style>
#box { width: 40px; height: 40px; background-color: blue; transition: background-color 1s ease; }
#box:hover { background-color: red; }
</style>
</head>
<body><div id="box"></div></body>
</html>
""";

var harness = document.Context.GetDomHarness();
harness.MousePosition = (20, 20); // hovers #box, starts the transition
harness.AdvanceTime(TimeSpan.FromMilliseconds(500)); // halfway through the 1s duration

var midway = harness.PaintToPng(); // background is purple, not yet fully red
```

Moving the pointer away mid-transition reverses it smoothly from whatever is currently on screen,
with its own full declared duration - not a jump back to blue, and not just the remaining
fraction of the original 1s.

## CSS `animation`/`@keyframes`

An `animation` needs no trigger at all - it starts the moment the harness first becomes aware of
it (pinned to virtual-clock zero, matching a real browser's "starts playing at page load") and
keeps looping per `animation-iteration-count`:

```cs
var html = """
<html>
<head>
<style>
@keyframes fade {
0% { opacity: 0; }
100% { opacity: 1; }
}
#box { width: 40px; height: 40px; background-color: blue; animation: fade 2s linear; }
</style>
</head>
<body><div id="box"></div></body>
</html>
""";

var harness = document.Context.GetDomHarness();
harness.AdvanceTime(TimeSpan.FromMilliseconds(1000)); // halfway through the 2s duration

var midway = harness.PaintToPng(); // #box is 50% opaque
```

`animation-name` (including multiple, comma-separated animations on one element),
`-duration`, `-delay`, `-timing-function` (`ease`/`linear`/`cubic-bezier()`/`steps()`),
`-iteration-count` (a number, or `infinite`), `-direction` (`normal`/`reverse`/`alternate`/
`alternate-reverse`), and `-fill-mode` (`none`/`forwards`/`backwards`/`both`) are all supported.
`animation-play-state` (pausing) is not - every animation the harness knows about is always
running.

## What Can Be Animated

Both `transition` and `animation` only ever interpolate a fixed set of properties:

- **Colors** (channel-wise): `background-color`, `color`, `border-top-color`,
`border-right-color`, `border-bottom-color`, `border-left-color`
- **Numeric lengths/`opacity`** (plain numeric lerp): `opacity`, `width`, `height`, `font-size`,
`margin-top`/`-right`/`-bottom`/`-left`, `padding-top`/`-right`/`-bottom`/`-left`,
`border-top-width`/`-right-width`/`-bottom-width`/`-left-width`, `top`, `left`, `right`, `bottom`

`transition-property: all` and any other property named in a `@keyframes` block are limited to
this same list - properties this renderer does not otherwise resolve component-wise, such as
`transform` and `filter`, are not interpolated.

## Scrolling

The same harness also tracks per-element scroll position, read and set through the standard
CSSOM-view properties/methods (`element.scrollTop`, `element.scrollTo(...)`,
`element.scrollIntoView()`, ...) - `RenderToPng`/`BuildDisplayList` honor whatever scroll position
is currently set once a harness exists for the document.
2 changes: 1 addition & 1 deletion src/AngleSharp.Renderer.Docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@anglesharp/renderer",
"version": "0.3.0",
"version": "0.4.0",
"preview": true,
"description": "The doclet for the AngleSharp.Renderer documentation.",
"keywords": [
Expand Down
Loading
Loading