シンプルなオンラインツール

画像処理

画像リサイズの最適化テクニック:Web表示速度を劇的に改善する方法

画像のリサイズとフォーマット選択で、Webサイトの表示速度を最大80%改善。実践的な最適化手法を徹底解説。

i4uパフォーマンスチーム
6分で読む

なぜ画像リサイズが重要なのか

Webページの平均的な重量の60%以上を画像が占めており、適切な最適化により読み込み速度を大幅に改善できます。Googleの調査によると、ページ読み込みが3秒を超えると53%のユーザーが離脱します。

最適な画像サイズの決定方法

デバイス別の推奨サイズ

用途デスクトップタブレットモバイル
ヒーローイメージ1920×10801024×768640×360
サムネイル400×300300×225200×150
記事内画像800×600600×450400×300
アイコン64×6448×4832×32

レスポンシブ画像の実装

<!-- srcsetを使った最適化 -->
<img 
  src="image-800.jpg"
  srcset="
    image-400.jpg 400w,
    image-800.jpg 800w,
    image-1200.jpg 1200w,
    image-1600.jpg 1600w"
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    800px"
  alt="最適化された画像"
  loading="lazy"
>

画像フォーマットの選択基準

モダンフォーマットの活用

// 画像フォーマット自動選択システム
class ImageOptimizer {
  constructor() {
    this.formats = {
      avif: { support: this.checkAVIF(), quality: 85, size: 0.5 },
      webp: { support: this.checkWebP(), quality: 90, size: 0.65 },
      jpeg: { support: true, quality: 85, size: 1.0 }
    };
  }
  
  selectOptimalFormat(imageType, transparency = false) {
    // 透過が必要な場合
    if (transparency) {
      if (this.formats.avif.support) return 'avif';
      if (this.formats.webp.support) return 'webp';
      return 'png';
    }
    
    // 写真の場合
    if (imageType === 'photo') {
      if (this.formats.avif.support) return 'avif';
      if (this.formats.webp.support) return 'webp';
      return 'jpeg';
    }
    
    // イラストやロゴの場合
    return 'svg';
  }
  
  checkWebP() {
    const canvas = document.createElement('canvas');
    canvas.width = canvas.height = 1;
    return canvas.toDataURL('image/webp').indexOf('image/webp') === 5;
  }
  
  checkAVIF() {
    // AVIF対応チェック
    return CSS.supports('image-rendering', 'pixelated');
  }
}

自動リサイズシステムの構築

Node.js による画像処理パイプライン

const sharp = require('sharp');
const fs = require('fs').promises;
const path = require('path');

class ImageResizer {
  constructor(config) {
    this.config = {
      sizes: [400, 800, 1200, 1600],
      formats: ['webp', 'avif', 'jpg'],
      quality: { webp: 90, avif: 85, jpg: 85 },
      ...config
    };
  }
  
  async processImage(inputPath, outputDir) {
    const filename = path.basename(inputPath, path.extname(inputPath));
    const results = [];
    
    for (const size of this.config.sizes) {
      for (const format of this.config.formats) {
        const output = path.join(
          outputDir,
          `${filename}-${size}.${format}`
        );
        
        await sharp(inputPath)
          .resize(size, null, {
            withoutEnlargement: true,
            fit: 'inside'
          })
          [format]({ quality: this.config.quality[format] })
          .toFile(output);
          
        const stats = await fs.stat(output);
        results.push({
          path: output,
          size: stats.size,
          dimensions: `${size}px`,
          format
        });
      }
    }
    
    return results;
  }
}

CDNと画像配信の最適化

Cloudflare Polish との連携

interface CloudflareConfig {
  polish: 'lossy' | 'lossless' | 'off';
  webp: boolean;
  mirage: boolean;
}

class CDNOptimizer {
  private config: CloudflareConfig = {
    polish: 'lossy',
    webp: true,
    mirage: true  // モバイル向け自動最適化
  };
  
  generateImageURL(
    originalURL: string,
    options: {
      width?: number;
      quality?: number;
      format?: string;
    }
  ): string {
    const params = new URLSearchParams();
    
    if (options.width) {
      params.append('width', options.width.toString());
    }
    
    if (options.quality) {
      params.append('quality', options.quality.toString());
    }
    
    if (options.format) {
      params.append('format', options.format);
    }
    
    return `${originalURL}?${params.toString()}`;
  }
}

遅延読み込みの実装

Intersection Observer を使った高度な遅延読み込み

class LazyImageLoader {
  constructor(options = {}) {
    this.config = {
      rootMargin: '50px',
      threshold: 0.01,
      placeholder: 'data:image/svg+xml;base64,...',
      ...options
    };
    
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      {
        rootMargin: this.config.rootMargin,
        threshold: this.config.threshold
      }
    );
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        const src = img.dataset.src;
        const srcset = img.dataset.srcset;
        
        // プログレッシブ読み込み
        this.loadImage(src).then(() => {
          img.src = src;
          if (srcset) img.srcset = srcset;
          img.classList.add('loaded');
          this.observer.unobserve(img);
        });
      }
    });
  }
  
  loadImage(src) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = resolve;
      img.onerror = reject;
      img.src = src;
    });
  }
  
  observe() {
    document.querySelectorAll('img[data-src]').forEach(img => {
      this.observer.observe(img);
    });
  }
}

アート指向の画像配信

Art Direction による最適化

<picture>
  <!-- モバイル向け(縦長・クロップ版) -->
  <source
    media="(max-width: 640px)"
    srcset="
      hero-mobile-400.avif 400w,
      hero-mobile-600.avif 600w"
    type="image/avif"
  >
  
  <!-- タブレット向け(正方形版) -->
  <source
    media="(max-width: 1024px)"
    srcset="
      hero-tablet-800.webp 800w,
      hero-tablet-1200.webp 1200w"
    type="image/webp"
  >
  
  <!-- デスクトップ向け(横長版) -->
  <source
    srcset="
      hero-desktop-1600.webp 1600w,
      hero-desktop-2400.webp 2400w"
    type="image/webp"
  >
  
  <!-- フォールバック -->
  <img
    src="hero-fallback.jpg"
    alt="レスポンシブヒーローイメージ"
    loading="eager"
    fetchpriority="high"
  >
</picture>

パフォーマンス測定とモニタリング

Core Web Vitals への影響測定

// 画像パフォーマンスモニター
class ImagePerformanceMonitor {
  constructor() {
    this.metrics = {
      lcp: [],
      cls: [],
      loadTime: []
    };
  }
  
  measureLCP() {
    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      const lastEntry = entries[entries.length - 1];
      
      // LCPが画像の場合
      if (lastEntry.element?.tagName === 'IMG') {
        this.metrics.lcp.push({
          time: lastEntry.renderTime || lastEntry.loadTime,
          url: lastEntry.element.src,
          size: lastEntry.size
        });
        
        this.analyzeImageImpact(lastEntry);
      }
    }).observe({ type: 'largest-contentful-paint', buffered: true });
  }
  
  analyzeImageImpact(entry) {
    const recommendations = [];
    
    // サイズチェック
    if (entry.size > 100000) {
      recommendations.push('画像サイズが100KB超。圧縮を検討');
    }
    
    // フォーマットチェック
    if (!entry.element.src.includes('.webp') && 
        !entry.element.src.includes('.avif')) {
      recommendations.push('モダンフォーマットの使用を推奨');
    }
    
    return recommendations;
  }
}

実装チェックリスト

画像最適化の必須項目

  • 適切なサイズでのリサイズ実装
  • モダンフォーマット(WebP/AVIF)対応
  • レスポンシブ画像(srcset/sizes)実装
  • 遅延読み込み(loading="lazy")設定
  • CDN経由での配信
  • 画像圧縮の自動化
  • Art Directionの検討
  • Core Web Vitalsのモニタリング

ツールとリソース

推奨ツール

まとめ

画像の最適化は、Webサイトのパフォーマンス向上において最も効果的な施策の一つです。適切なサイズ、フォーマット、配信方法を選択することで、ページ読み込み速度を劇的に改善できます。

本記事で紹介した技術を段階的に実装することで、Core Web Vitalsのスコア向上と、より良いユーザー体験の提供が可能になります。