name: 优化图像 description: 设计响应式图像管道,使用srcset、现代格式和懒加载。特定框架实现。 argument-hint: [–framework next|nuxt|blazor|vanilla] [–format webp|avif|both] allowed-tools: Read, Glob, Grep, Task, Skill, AskUserQuestion
优化图像命令
设计用于Web交付的响应式图像优化管道。
用法
/cms:optimize-images --framework blazor
/cms:optimize-images --framework next --format avif
/cms:optimize-images --framework vanilla --format both
框架选项
- next: Next.js Image组件
- nuxt: Nuxt Image模块
- blazor: 使用srcset的Blazor组件
- vanilla: 纯HTML/CSS实现
工作流程
步骤1:解析参数
从命令中提取框架和格式偏好。
步骤2:调用技能
调用相关技能:
image-optimization- 响应式模式cdn-media-delivery- CDN集成
步骤3:设计响应式策略
断点配置:
responsive:
breakpoints:
mobile: 320
mobile_lg: 480
tablet: 768
desktop: 1024
desktop_lg: 1280
wide: 1920
device_pixel_ratios: [1, 2, 3]
sizes_presets:
thumbnail:
default: 150px
card:
mobile: 100vw
tablet: 50vw
desktop: 33vw
hero:
default: 100vw
content:
mobile: 100vw
tablet: 80vw
desktop: 800px
formats:
priority: [avif, webp, jpg]
fallback: jpg
步骤4:生成实现
Blazor组件:
@* ResponsiveImage.razor *@
<picture>
@foreach (var format in Formats)
{
<source type="@GetMimeType(format)"
srcset="@GenerateSrcset(format)"
sizes="@Sizes" />
}
<img src="@FallbackSrc"
alt="@Alt"
loading="@(Lazy ? "lazy" : "eager")"
decoding="async"
width="@Width"
height="@Height"
class="@CssClass"
@attributes="AdditionalAttributes" />
</picture>
@code {
[Parameter] public string Src { get; set; }
[Parameter] public string Alt { get; set; }
[Parameter] public int Width { get; set; }
[Parameter] public int Height { get; set; }
[Parameter] public string Sizes { get; set; } = "100vw";
[Parameter] public bool Lazy { get; set; } = true;
[Parameter] public string CssClass { get; set; }
[Parameter] public string[] Formats { get; set; } = ["avif", "webp"];
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> AdditionalAttributes { get; set; }
private int[] Widths => [320, 640, 768, 1024, 1280, 1920];
private string FallbackSrc => GetTransformUrl(Src, Width, "jpg");
private string GenerateSrcset(string format)
{
return string.Join(", ",
Widths.Select(w => $"{GetTransformUrl(Src, w, format)} {w}w"));
}
private string GetTransformUrl(string src, int width, string format)
{
return $"/media/transform/{GetAssetId(src)}?w={width}&format={format}";
}
private string GetMimeType(string format) => format switch
{
"avif" => "image/avif",
"webp" => "image/webp",
"jpg" => "image/jpeg",
_ => "image/jpeg"
};
}
Next.js实现:
// components/ResponsiveImage.tsx
import Image from 'next/image';
interface ResponsiveImageProps {
src: string;
alt: string;
width: number;
height: number;
sizes?: string;
priority?: boolean;
className?: string;
}
export function ResponsiveImage({
src,
alt,
width,
height,
sizes = '100vw',
priority = false,
className,
}: ResponsiveImageProps) {
return (
<Image
src={src}
alt={alt}
width={width}
height={height}
sizes={sizes}
priority={priority}
className={className}
placeholder="blur"
blurDataURL={`/api/placeholder?w=10&h=${Math.round(10 * height / width)}`}
/>
);
}
// next.config.js
module.exports = {
images: {
domains: ['cdn.example.com'],
deviceSizes: [640, 768, 1024, 1280, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60 * 60 * 24 * 7, // 7天
loader: 'custom',
loaderFile: './lib/imageLoader.ts',
},
};
// lib/imageLoader.ts
export default function cmsImageLoader({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) {
const q = quality || 85;
return `${process.env.CDN_URL}/transform/${src}?w=${width}&q=${q}`;
}
Vanilla HTML:
<!-- 响应式图像与艺术指导 -->
<picture>
<!-- 艺术指导:移动端不同裁剪 -->
<source
media="(max-width: 767px)"
type="image/avif"
srcset="
/media/transform/hero-mobile?w=320&format=avif 320w,
/media/transform/hero-mobile?w=640&format=avif 640w
"
sizes="100vw"
/>
<source
media="(max-width: 767px)"
type="image/webp"
srcset="
/media/transform/hero-mobile?w=320&format=webp 320w,
/media/transform/hero-mobile?w=640&format=webp 640w
"
sizes="100vw"
/>
<!-- 桌面源 -->
<source
type="image/avif"
srcset="
/media/transform/hero?w=768&format=avif 768w,
/media/transform/hero?w=1024&format=avif 1024w,
/media/transform/hero?w=1920&format=avif 1920w
"
sizes="100vw"
/>
<source
type="image/webp"
srcset="
/media/transform/hero?w=768&format=webp 768w,
/media/transform/hero?w=1024&format=webp 1024w,
/media/transform/hero?w=1920&format=webp 1920w
"
sizes="100vw"
/>
<!-- 回退 -->
<img
src="/media/transform/hero?w=1024&format=jpg"
alt="英雄图像描述"
loading="lazy"
decoding="async"
width="1920"
height="1080"
/>
</picture>
步骤5:图像转换API
转换服务:
public class ImageTransformService
{
public async Task<Stream> TransformAsync(
Guid assetId,
ImageTransformOptions options)
{
var asset = await _mediaRepository.GetAsync(assetId);
var cacheKey = GenerateCacheKey(assetId, options);
// 首先检查缓存
if (await _cache.ExistsAsync(cacheKey))
{
return await _cache.GetStreamAsync(cacheKey);
}
// 获取原始图像
var original = await _storage.GetAsync(asset.StoragePath);
// 使用ImageSharp转换
using var image = await Image.LoadAsync(original);
// 应用焦点感知调整大小
if (options.FocalPoint != null)
{
image.Mutate(x => x.Resize(new ResizeOptions
{
Size = new Size(options.Width, options.Height),
Mode = ResizeMode.Crop,
CenterCoordinates = new PointF(
options.FocalPoint.X * image.Width,
options.FocalPoint.Y * image.Height)
}));
}
else
{
image.Mutate(x => x.Resize(options.Width, options.Height));
}
// 以请求的格式编码
var output = new MemoryStream();
await EncodeAsync(image, output, options.Format, options.Quality);
// 缓存结果
await _cache.SetAsync(cacheKey, output, TimeSpan.FromDays(7));
output.Position = 0;
return output;
}
}
public record ImageTransformOptions
{
public int Width { get; init; }
public int? Height { get; init; }
public string Format { get; init; } = "webp";
public int Quality { get; init; } = 85;
public string Fit { get; init; } = "contain";
public FocalPoint FocalPoint { get; init; }
}
步骤6:性能优化
加载策略:
loading:
# 首屏
critical:
loading: eager
fetchpriority: high
decoding: sync
# 非首屏
lazy:
loading: lazy
fetchpriority: auto
decoding: async
# 低优先级
deferred:
loading: lazy
fetchpriority: low
decoding: async
# 占位符策略
placeholders:
blur: true # LQIP(低质量图像占位符)
dominant_color: true
skeleton: false
缓存配置:
caching:
# 浏览器缓存
browser:
max_age: 31536000 # 1年
immutable: true
stale_while_revalidate: 86400
# CDN缓存
cdn:
ttl: 604800 # 7天
vary: [Accept] # 格式协商
# 转换缓存
transform:
storage: redis
ttl: 604800
max_size: 10GB
eviction: lru
指标
跟踪图像性能:
metrics:
- largest_contentful_paint
- cumulative_layout_shift
- time_to_first_byte
- cache_hit_ratio
- format_distribution
- bandwidth_saved
相关技能
image-optimization- 响应式模式cdn-media-delivery- CDN配置media-asset-management- 资产存储