-
Notifications
You must be signed in to change notification settings - Fork 1
Image
The Image component is a high-performance alternative to the standard HTML <img> tag, heavily inspired by Next.js. It helps optimize images for speed and search engine ranking.
- Lazy Loading: Images are only loaded when they enter the viewport by default.
- CLS Prevention: Automatically sets width and height to prevent layout shifts.
-
Layout Modes: Supports standard fixed sizing or a
Fillmode for responsive containers. - Blur-up Placeholders: Support for low-resolution placeholders while the full image loads.
-
LCP Optimization: Use the
Priorityflag for critical above-the-fold images. -
Server-Side Optimization: Auto resize, format conversion (WebP/AVIF), and quality control via
eQuantic.UI.Images.
new Image
{
Src = "/path/to/image.jpg",
Alt = "Description",
Width = 800,
Height = 600
}Requires Width and Height. The image maintains these dimensions.
The image will fill its parent container. The parent MUST have relative positioning.
new Container
{
ClassName = "relative h-64",
Children =
{
new Image { Src = "/hero.webp", Fill = true }
}
}Use Blur placeholders for a smoother loading experience.
new Image
{
Src = "/large.jpg",
Width = 800,
Height = 600,
Placeholder = ImagePlaceholder.Blur,
BlurDataURL = "data:image/webp;base64,..."
}The eQuantic.UI.Images package enables Next.js-style server-side image optimization. When enabled, local images are automatically resized, converted to WebP, and served via an optimized endpoint.
Image Component (Optimize = true)
│
▼ Generates srcset URLs
/_equantic/image?url=/images/hero.jpg&w=640&q=80
│
▼ Image Optimization Endpoint
├── Validates parameters (width in allowed sizes, quality 1-100)
├── Checks filesystem cache (obj/eQuantic/image-cache/)
├── On miss: resize → format convert → cache → serve
└── Content negotiation: Accept header → WebP or JPEG fallback
<PackageReference Include="eQuantic.UI.Images" />// Program.cs
builder.Services.AddImageOptimization(opts =>
{
opts.DefaultQuality = 80; // 1-100, default: 75
opts.Formats = ["image/webp"]; // Preferred output formats
opts.CacheTtlSeconds = 14400; // Cache TTL (default: 4 hours)
opts.MaxSourceSize = 10 * 1024 * 1024; // Max source size (default: 10MB)
});app.UseStaticFiles();
app.UseImageOptimization(); // Maps /_equantic/image endpoint
app.MapUI();When Width is set and Fill is false, the component generates 1x and 2x density descriptors for retina displays:
new Image
{
Src = "/images/hero.jpg",
Alt = "Hero",
Width = 800,
Height = 600
}Generated HTML:
<img
src="/_equantic/image?url=%2Fimages%2Fhero.jpg&w=828&q=80"
srcset="/_equantic/image?url=%2Fimages%2Fhero.jpg&w=828&q=80 1x,
/_equantic/image?url=%2Fimages%2Fhero.jpg&w=1920&q=80 2x"
width="800" height="600"
loading="lazy" decoding="async" />When using Fill mode or without Width, the component generates a full srcset with all configured sizes:
new Image
{
Src = "/images/hero.jpg",
Alt = "Full-width hero",
Fill = true
}Generated HTML:
<img
src="/_equantic/image?url=%2Fimages%2Fhero.jpg&w=3840&q=80"
srcset="/_equantic/image?url=%2Fimages%2Fhero.jpg&w=32&q=80 32w,
/_equantic/image?url=%2Fimages%2Fhero.jpg&w=48&q=80 48w,
... (all configured sizes) ...
/_equantic/image?url=%2Fimages%2Fhero.jpg&w=3840&q=80 3840w"
sizes="100vw"
loading="lazy" decoding="async" />Override the global quality setting per image:
new Image
{
Src = "/images/background.jpg",
Alt = "Background",
Width = 1200,
Height = 800,
Quality = 50 // Lower quality for background images
}Optimization is controlled at two levels:
Global (UseImageOptimization()) |
Per-Image (Optimize) |
Result |
|---|---|---|
| Enabled |
null (default) |
Optimized |
| Enabled | true |
Optimized |
| Enabled | false |
Not optimized |
| Not called |
null (default) |
Not optimized |
| Not called | true |
Optimized* |
*Requires the endpoint to be mapped; otherwise, URLs will be generated but won't resolve.
// Explicit opt-out (e.g., SVGs don't need optimization)
new Image
{
Src = "/images/logo.svg",
Alt = "Logo",
Optimize = false
}- Only local images (paths starting with
/) are optimized. External URLs are never proxied. - Path traversal attacks (
..) are blocked. - Width must be in the configured allowed sizes (
DeviceSizes+ImageSizes). - Quality must be 1-100.
- Source file size is limited by
MaxSourceSize(default 10MB).
The endpoint reads the browser's Accept header and serves the best supported format:
- Checks configured
Formats(e.g.,["image/avif", "image/webp"]) - Returns the first format the browser supports
- Falls back to JPEG if no match
-
Filesystem cache: Optimized images are cached in
obj/eQuantic/image-cache/(configurable) -
Cache key: SHA256 of
(url, width, quality, format) -
TTL: Configurable via
CacheTtlSeconds(default: 4 hours) - Thread-safe: Concurrent requests for the same image are deduplicated
-
Response headers:
Cache-Control: public, max-age={ttl},Vary: Accept
The BlurPlaceholderGenerator service creates tiny 8px-wide JPEG placeholders as base64 data URLs:
// Inject from DI
var generator = app.Services.GetRequiredService<BlurPlaceholderGenerator>();
// Generate from file
var dataUrl = await generator.GenerateFromFileAsync("wwwroot/images/hero.jpg");
// Returns: "data:image/jpeg;base64,/9j/4AAQ..."| Property | Type | Default | Description |
|---|---|---|---|
| DeviceSizes | int[] |
[640, 750, 828, 1080, 1200, 1920, 2048, 3840] |
Allowed widths for viewport-sized images. |
| ImageSizes | int[] |
[32, 48, 64, 96, 128, 256, 384] |
Allowed widths for smaller images. |
| Formats | string[] |
["image/webp"] |
Preferred output formats (ordered by priority). |
| DefaultQuality | int |
75 |
Default quality (1-100). |
| CacheTtlSeconds | int |
14400 |
Cache TTL in seconds (4 hours). |
| CacheDirectory | string |
"obj/eQuantic/image-cache" |
Filesystem cache directory. |
| MaxSourceSize | long |
10485760 |
Max source image size in bytes (10MB). |
| Property | Type | Default | Description |
|---|---|---|---|
| Src | string |
- | Image source URL. |
| Alt | string |
- | Alternative text for accessibility. |
| Width | int? |
- | Width in pixels. |
| Height | int? |
- | Height in pixels. |
| Fill | bool |
false |
If true, fills the parent container. |
| Loading | ImageLoading |
Lazy |
Lazy or Eager. |
| Priority | bool |
false |
High priority for preloading (LCP). |
| Placeholder | ImagePlaceholder |
None |
Blur or Empty. |
| BlurDataURL | string? |
- | Small Data URL for blurred background. |
| ObjectFit | string? |
"cover" |
CSS object-fit property (for Fill). |
| Optimize | bool? |
null |
Override global optimization setting. |
| Quality | int |
0 |
Image quality (1-100). 0 = use global default. |
| SrcSet | string? |
- | Manual srcset (ignored when optimized). |
| Sizes | string? |
- | Manual sizes attribute. |
The eQuantic.UI.Images package follows the self-contained package pattern:
eQuantic.UI.Core
└── ImageOptimizationState (static bridge)
↑ read by ↑ written by
eQuantic.UI.Components eQuantic.UI.Images
└── Image component ├── ImageOptimizer (SixLabors.ImageSharp)
├── ImageCache (filesystem + SemaphoreSlim)
├── ImageOptimizationMiddleware
├── BlurPlaceholderGenerator
└── ImageExtensions (DI + endpoint)
This avoids circular dependencies: Core defines the state, Components reads it, Images writes it.