Kebechet.Blazor.HtmlToImage 1.11.13

Prefix Reserved
There is a newer version of this package available.
See the version list below for details.
dotnet add package Kebechet.Blazor.HtmlToImage --version 1.11.13
                    
NuGet\Install-Package Kebechet.Blazor.HtmlToImage -Version 1.11.13
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kebechet.Blazor.HtmlToImage" Version="1.11.13" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kebechet.Blazor.HtmlToImage" Version="1.11.13" />
                    
Directory.Packages.props
<PackageReference Include="Kebechet.Blazor.HtmlToImage" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kebechet.Blazor.HtmlToImage --version 1.11.13
                    
#r "nuget: Kebechet.Blazor.HtmlToImage, 1.11.13"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kebechet.Blazor.HtmlToImage@1.11.13
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kebechet.Blazor.HtmlToImage&version=1.11.13
                    
Install as a Cake Addin
#tool nuget:?package=Kebechet.Blazor.HtmlToImage&version=1.11.13
                    
Install as a Cake Tool

"Buy Me A Coffee"

Blazor.HtmlToImage

NuGet Version NuGet Downloads Build codecov Storybook Last updated Twitter

Blazor wrapper for html-to-image: capture any DOM element as PNG, JPEG, SVG, raw bytes or pixel data.

The html-to-image build is vendored and pinned inside the package and injected by a Blazor JS initializer, so there is no npm step, no CDN, and no <script> tag to add. That matters beyond convenience: a CDN reference breaks a MAUI or hybrid app offline, lets a shipped store build change behaviour without a release, and counts as downloading executable code at runtime under App Store guideline 2.5.2.

Live storybook - interactive stories for every feature.

Installation

dotnet add package Kebechet.Blazor.HtmlToImage

Register the service:

using Kebechet.Blazor.HtmlToImage;

builder.Services.AddHtmlToImage();

Usage

@inject IHtmlToImageService _htmlToImage

<div @ref="_poster" class="poster">
    <h1>Bench press - 120 kg x 5</h1>
    <button class="capture-ignore" @onclick="Capture">Share</button>
</div>

@code {
    private ElementReference _poster;

    private async Task Capture()
    {
        var png = await _htmlToImage.ToPngBytesAsync(_poster, new HtmlToImageOptions
        {
            PixelRatio = 3,
            BackgroundColor = "#0b0b0b",
            ExcludeCssClasses = ["capture-ignore"],
        });

        // upload, save, or hand to a native share sheet
    }
}

Every method also takes a DOM id instead of an ElementReference:

var dataUrl = await _htmlToImage.ToPngAsync("poster");

Data URL vs. bytes

ToPngAsync returns a data:image/png;base64,... string, matching upstream. ToPngBytesAsync returns byte[] and streams the image over JS interop instead of marshalling base64. Prefer the bytes overload for anything you intend to upload or save: a poster at PixelRatio = 3 is several megabytes, and a data URL carries it as one JSON string - roughly 33% larger and fully buffered on both sides.

Excluding elements from the capture

Upstream's filter is a JavaScript predicate, which cannot cross JS interop as a delegate without a round-trip per DOM node. It is modelled instead as two declarative options that the interop layer compiles into a single filter:

new HtmlToImageOptions
{
    ExcludeCssClasses = ["capture-ignore", "debug-overlay"],
    ExcludeSelector = "[data-private]",
}

Excluding a node excludes its whole subtree, matching upstream semantics.

Repeated captures of the same subtree

Font resolution dominates the cost of a capture that uses web fonts. Resolve once and reuse:

var fontCss = await _htmlToImage.GetFontEmbedCssAsync(_poster);

foreach (var frame in frames)
{
    var png = await _htmlToImage.ToPngBytesAsync(_poster, new HtmlToImageOptions
    {
        FontEmbedCss = fontCss,
    });
}

Coverage vs. html-to-image 1.11.13

Complete. Every upstream entry point and every option is reachable.

Axis html-to-image This package
Entry points 7 7
Options 20 20

Entry points: toPng, toJpeg, toSvg, toPixelData, getFontEmbedCSS and toCanvas map to ToPngAsync, ToJpegAsync, ToSvgAsync, ToPixelDataAsync, GetFontEmbedCssAsync and ToCanvasAsync. toBlob's role is served by ToPngBytesAsync / ToJpegBytesAsync, which route through toCanvas to work around an upstream bug - see the deviation note below.

Options: all 20, though three are reshaped because their upstream form is a JavaScript value that cannot cross interop as data:

Upstream Here Why
filter ExcludeCssClasses, ExcludeSelector A JS predicate would need an interop round-trip per DOM node; these compile into one filter function
onImageErrorHandler ImageLoadFailed event A JS callback cannot be an option value; wired through a DotNetObjectReference only while subscribed
fetchRequestInit FetchRequestInit class Models the data-carrying members; signal, body and window are live JS objects with no data form

⚠️ ImageLoadFailed and ImagePlaceholder are alternatives, not complements. Upstream resolves a failed URL to the placeholder before assigning it to the cloned image, so with a placeholder set the clone loads successfully and the error never fires. Pick reporting or papering over. Pinned by ImageLoadFailed_ReportsAnImageThatCouldNotResolve.

Coverage is measured against html-to-image's published type definitions - lib/index.d.ts for entry points, lib/types.d.ts for the Options interface - excluding underscore-prefixed internals. Re-checked on every upstream version bump.

One deliberate deviation from upstream

⚠️ ToJpegBytesAsync does not call upstream's toBlob.

Upstream's toBlob forwards its options to toCanvas but then calls its own internal canvasToBlob(canvas) with no options at all, so type and quality are silently dropped - asking it for a JPEG returns a PNG at quality 1. This wrapper calls toCanvas and does the canvas-to-blob step itself, which is what makes ToJpegBytesAsync return real JPEG bytes and Quality take effect. Pinned by the Capture_JpegStory_ReturnsBytesWithAJpegSignature browser test.

Captures stall in hidden tabs - by upstream design

⚠️ A capture started while the tab is hidden or fully occluded does not complete until the tab renders a frame again. Nothing errors and nothing times out - the Task just stays pending, then resolves when the tab becomes visible.

This is upstream's own resolution path, not wrapper behaviour: html-to-image's createImage resolves every capture inside img.decode().then(() => requestAnimationFrame(resolve)), and browsers do not fire requestAnimationFrame for hidden documents. The normal case - capturing in response to a user action in a visible tab - never sees this. It matters only if a capture races a tab switch, or if you drive the page from automation that keeps it backgrounded (CDP screenshots force a frame, which un-stalls it).

Vendored library provenance

Version html-to-image 1.11.13
File wwwroot/html-to-image.js
Source npm tarball html-to-image-1.11.13.tgz, package/dist/html-to-image.js
SHA-256 a90b42909d80964269ef6d5f3d1e4a5a7e2a4c263a5d2a76a9e7151901343262

The npm dist build is already minified - jsDelivr reports "skipped minification" for it - so it is vendored verbatim rather than re-minified, and the hash above verifies byte-for-byte against the tarball.

License

MIT. html-to-image is itself MIT licensed.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.11.13.1 143 8/6/2026
1.11.13 83 8/4/2026

Initial release. Wraps html-to-image 1.11.13 with complete coverage - all 7 entry points and all 20 options are reachable, over ElementReference or element id. ToPng/ToJpeg/ToSvg return data URLs; To*BytesAsync stream raw bytes over JS interop instead of base64; ToPixelDataAsync returns raw RGBA; ToCanvasAsync hands back the live HTMLCanvasElement; GetFontEmbedCssAsync enables font-CSS reuse across captures. Three options are reshaped because their upstream form is a JS value: filter becomes ExcludeCssClasses/ExcludeSelector, onImageErrorHandler becomes the ImageLoadFailed event, fetchRequestInit becomes a typed FetchRequestInit. JPEG bytes deliberately bypass upstream's toBlob, which drops type and quality. Verified by 13 real-browser Playwright tests plus unit tests.