图像优化Skill optimize-images

该技能用于设计和实现响应式图像优化管道,支持多种现代图像格式(如AVIF和WebP),集成懒加载、srcset和CDN交付,适用于不同前端框架(如Next.js、Blazor),以提升网页性能、用户体验和SEO效果。关键词:图像优化、响应式设计、懒加载、srcset、WebP、AVIF、前端开发、性能优化。

前端开发 0 次安装 0 次浏览 更新于 3/11/2026

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 - 资产存储