DA
Tüm yazılara dön
Görsel Optimizasyon Araçları: Web Performance İçin Resim Sıkıştırma Rehberi

Görsel Optimizasyon Araçları: Web Performance İçin Resim Sıkıştırma Rehberi

görsel optimizasyonweb performancecore web vitalsimage compressionwebpresponsive images

Görsel Optimizasyon Araçları: Web Performance İçin Resim Sıkıştırma Rehberi

Web sitelerinde görsel içerik, kullanıcı deneyiminin vazgeçilmez bir parçasıdır. Ancak optimize edilmemiş görseller, sayfa yükleme hızını dramatik şekilde yavaşlatabilir ve Core Web Vitals skorlarını olumsuz etkileyebilir. Bu kapsamlı rehberde, modern görsel optimizasyon tekniklerini, format seçimini ve performans iyileştirme stratejilerini detaylı olarak inceleyeceğiz.

Görsel Optimizasyonun Önemi

Web Performance Üzerindeki Etkisi

Görsellerin web performansı üzerindeki kritik etkileri:

İstatistikler:

  • Web sayfalarının ortalama %60'ı görsellerden oluşur
  • 1 saniye gecikme %7 dönüşüm kaybına neden olur
  • Mobil kullanıcıların %53'ü 3 saniyeden fazla beklemez
  • Görseller sayfa boyutunun ortalama %21'ini oluşturur

Core Web Vitals ve Görseller

Largest Contentful Paint (LCP):

Optimizasyon hedefleri:
- LCP < 2.5 saniye (İyi)
- 2.5s < LCP < 4.0s (İyileştirme gerekli)
- LCP > 4.0s (Kötü)

Görsel optimizasyonu ile %40-60 iyileştirme mümkün

Cumulative Layout Shift (CLS):

Layout kayması nedenleri:
- Boyutsuz görsel elementler
- Web fontları
- Dinamik içerik ekleme

Hedef CLS < 0.1

Görsel Format Karşılaştırması

Modern Görsel Formatları

JPEG (Joint Photographic Experts Group)

Özellikler:

  • Lossy compression
  • Fotoğraflar için ideal
  • Geniş tarayıcı desteği
  • 24-bit renk desteği

Kullanım alanları:

✅ Fotoğraflar
✅ Karmaşık renkli görseller
❌ Logolar ve ikonlar
❌ Şeffaflık gerektiren görseller

PNG (Portable Network Graphics)

Özellikler:

  • Lossless compression
  • Alpha channel (şeffaflık) desteği
  • Keskin kenarlar için ideal
  • Daha büyük dosya boyutu

Kullanım alanları:

✅ Logolar ve ikonlar
✅ Şeffaf görseller
✅ Screenshot'lar
❌ Büyük fotoğraflar

WebP

Özellikler:

  • Google tarafından geliştirildi
  • %25-35 daha küçük dosya boyutu
  • Hem lossy hem lossless
  • Animation desteği

Tarayıcı desteği:

Chrome: 23+ (2012)
Firefox: 65+ (2019)
Safari: 14+ (2020)
Edge: 18+ (2018)

Global destek: %95+

AVIF (AV1 Image File Format)

Özellikler:

  • En yeni format
  • %50 daha küçük dosya boyutu
  • Üstün compression
  • HDR desteği

Tarayıcı desteği:

Chrome: 85+ (2020)
Firefox: 93+ (2021)
Safari: Henüz desteklemiyor
Edge: 121+ (2024)

Global destek: %73+

Görsel Optimizasyon Aracımız

🖼️ Görsel Optimizasyon

Profesyonel web geliştiricileri için tasarlanmış kapsamlı görsel optimizasyon araçları:

Özellik 1: Akıllı Sıkıştırma

Kalite/Boyut Dengesi:

// Optimizasyon algoritması
class ImageOptimizer {
  static async optimizeImage(file, options = {}) {
    const {
      quality = 0.8,
      format = "auto",
      maxWidth = 1920,
      maxHeight = 1080,
      progressive = true,
    } = options;

    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");

    return new Promise((resolve) => {
      const img = new Image();
      img.onload = () => {
        // Calculate optimal dimensions
        const { width, height } = this.calculateDimensions(
          img.width,
          img.height,
          maxWidth,
          maxHeight
        );

        canvas.width = width;
        canvas.height = height;

        // Apply optimization settings
        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = "high";
        ctx.drawImage(img, 0, 0, width, height);

        // Convert to optimized format
        const outputFormat = this.selectFormat(format, file.type);
        const optimizedDataURL = canvas.toDataURL(outputFormat, quality);

        resolve({
          dataURL: optimizedDataURL,
          originalSize: file.size,
          optimizedSize: this.calculateDataURLSize(optimizedDataURL),
          compressionRatio: this.calculateCompressionRatio(
            file.size,
            optimizedDataURL
          ),
        });
      };

      img.src = URL.createObjectURL(file);
    });
  }

  static calculateDimensions(
    originalWidth,
    originalHeight,
    maxWidth,
    maxHeight
  ) {
    const aspectRatio = originalWidth / originalHeight;

    let width = originalWidth;
    let height = originalHeight;

    if (width > maxWidth) {
      width = maxWidth;
      height = width / aspectRatio;
    }

    if (height > maxHeight) {
      height = maxHeight;
      width = height * aspectRatio;
    }

    return {
      width: Math.round(width),
      height: Math.round(height),
    };
  }
}

Özellik 2: Toplu İşlem Desteği

Batch Processing:

class BatchImageProcessor {
  constructor() {
    this.queue = [];
    this.processing = false;
    this.onProgress = null;
    this.onComplete = null;
  }

  addImages(files, options = {}) {
    files.forEach((file) => {
      this.queue.push({ file, options });
    });
  }

  async processAll() {
    if (this.processing) return;

    this.processing = true;
    const results = [];
    const total = this.queue.length;

    for (let i = 0; i < this.queue.length; i++) {
      const { file, options } = this.queue[i];

      try {
        const result = await ImageOptimizer.optimizeImage(file, options);
        results.push({
          filename: file.name,
          success: true,
          ...result,
        });
      } catch (error) {
        results.push({
          filename: file.name,
          success: false,
          error: error.message,
        });
      }

      // Progress callback
      if (this.onProgress) {
        this.onProgress((i + 1) / total, results[i]);
      }
    }

    this.processing = false;
    this.queue = [];

    if (this.onComplete) {
      this.onComplete(results);
    }

    return results;
  }
}

Özellik 3: Format Dönüştürme

Intelligent Format Selection:

class FormatConverter {
  static selectOptimalFormat(imageType, hasTransparency, isPhoto) {
    // Feature detection
    const supportsWebP = this.supportsFormat("webp");
    const supportsAVIF = this.supportsFormat("avif");

    if (hasTransparency) {
      if (supportsAVIF) return "image/avif";
      if (supportsWebP) return "image/webp";
      return "image/png";
    }

    if (isPhoto) {
      if (supportsAVIF) return "image/avif";
      if (supportsWebP) return "image/webp";
      return "image/jpeg";
    }

    // For graphics/icons
    if (supportsWebP) return "image/webp";
    return "image/png";
  }

  static supportsFormat(format) {
    const canvas = document.createElement("canvas");
    canvas.width = 1;
    canvas.height = 1;

    try {
      return (
        canvas.toDataURL(`image/${format}`).indexOf(`data:image/${format}`) ===
        0
      );
    } catch {
      return false;
    }
  }

  static async convertToWebP(file, quality = 0.8) {
    return new Promise((resolve, reject) => {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      const img = new Image();

      img.onload = () => {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.drawImage(img, 0, 0);

        try {
          const webpDataURL = canvas.toDataURL("image/webp", quality);
          resolve(webpDataURL);
        } catch (error) {
          reject(new Error("WebP dönüştürme desteklenmiyor"));
        }
      };

      img.onerror = () => reject(new Error("Görsel yüklenemedi"));
      img.src = URL.createObjectURL(file);
    });
  }
}

Özellik 4: Responsive Image Generation

Multi-Resolution Output:

class ResponsiveImageGenerator {
  static generateSrcSet(imageDataURL, breakpoints = [480, 768, 1200, 1920]) {
    const results = [];

    return new Promise((resolve) => {
      const img = new Image();
      img.onload = async () => {
        for (const width of breakpoints) {
          if (width <= img.width) {
            const resized = await this.resizeImage(imageDataURL, width);
            results.push({
              width,
              dataURL: resized,
              descriptor: `${width}w`,
            });
          }
        }
        resolve(results);
      };
      img.src = imageDataURL;
    });
  }

  static async resizeImage(dataURL, targetWidth) {
    return new Promise((resolve) => {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");
      const img = new Image();

      img.onload = () => {
        const aspectRatio = img.height / img.width;
        const targetHeight = Math.round(targetWidth * aspectRatio);

        canvas.width = targetWidth;
        canvas.height = targetHeight;

        ctx.imageSmoothingEnabled = true;
        ctx.imageSmoothingQuality = "high";
        ctx.drawImage(img, 0, 0, targetWidth, targetHeight);

        resolve(canvas.toDataURL("image/jpeg", 0.8));
      };

      img.src = dataURL;
    });
  }

  static generatePictureElement(imageSrcSet, alt = "") {
    const sources = imageSrcSet.map((item) => item.dataURL).join(", ");
    const sizes = imageSrcSet
      .map((item) => `(max-width: ${item.width}px) ${item.width}px`)
      .join(", ");

    return `
      <picture>
        <source 
          srcset="${sources}" 
          sizes="${sizes}, 100vw"
          type="image/webp"
        >
        <img 
          src="${imageSrcSet[imageSrcSet.length - 1].dataURL}" 
          alt="${alt}"
          loading="lazy"
          decoding="async"
        >
      </picture>
    `;
  }
}

HTML5 Picture Element ve Responsive Images

Modern Responsive Image Techniques

Picture Element Kullanımı

<!-- Art direction ve format fallback -->
<picture>
  <!-- AVIF format (en optimal) -->
  <source
    srcset="
      hero-480.avif   480w,
      hero-768.avif   768w,
      hero-1200.avif 1200w,
      hero-1920.avif 1920w
    "
    sizes="(max-width: 768px) 100vw,
           (max-width: 1200px) 50vw,
           25vw"
    type="image/avif"
  />

  <!-- WebP format (iyi destek) -->
  <source
    srcset="
      hero-480.webp   480w,
      hero-768.webp   768w,
      hero-1200.webp 1200w,
      hero-1920.webp 1920w
    "
    sizes="(max-width: 768px) 100vw,
           (max-width: 1200px) 50vw,
           25vw"
    type="image/webp"
  />

  <!-- JPEG fallback -->
  <img
    src="hero-768.jpg"
    srcset="
      hero-480.jpg   480w,
      hero-768.jpg   768w,
      hero-1200.jpg 1200w,
      hero-1920.jpg 1920w
    "
    sizes="(max-width: 768px) 100vw,
           (max-width: 1200px) 50vw,
           25vw"
    alt="Hero image description"
    loading="lazy"
    decoding="async"
  />
</picture>

Art Direction

<!-- Farklı ekran boyutları için farklı kırpımlar -->
<picture>
  <!-- Mobile: square crop -->
  <source
    media="(max-width: 768px)"
    srcset="mobile-hero-square.webp"
    type="image/webp"
  />

  <!-- Tablet: 16:9 crop -->
  <source
    media="(max-width: 1200px)"
    srcset="tablet-hero-wide.webp"
    type="image/webp"
  />

  <!-- Desktop: panoramic crop -->
  <source
    media="(min-width: 1201px)"
    srcset="desktop-hero-panoramic.webp"
    type="image/webp"
  />

  <!-- Fallback -->
  <img src="hero-default.jpg" alt="Adaptive hero image" />
</picture>

CSS ile Görsel Optimizasyonu

CSS Image Optimization

/* Modern CSS görsel optimizasyonu */
.optimized-image {
  /* Aspect ratio preservation */
  aspect-ratio: 16 / 9;
  object-fit: cover;
  object-position: center;

  /* Performance optimizations */
  content-visibility: auto;
  contain-intrinsic-size: 400px 225px;

  /* Quality hints */
  image-rendering: auto;
  image-resolution: from-image;
}

/* Container queries ile responsive behavior */
@container (max-width: 480px) {
  .optimized-image {
    aspect-ratio: 1 / 1; /* Square on small screens */
  }
}

@container (min-width: 1200px) {
  .optimized-image {
    aspect-ratio: 21 / 9; /* Ultra-wide on large screens */
  }
}

/* Critical images için optimize loading */
.hero-image {
  /* No lazy loading for above-fold content */
  content-visibility: visible;

  /* Preload hint */
  background-image: image-set(
    "hero.avif" type("image/avif"),
    "hero.webp" type("image/webp"),
    "hero.jpg" type("image/jpeg")
  );
}

/* Image placeholder ve skeleton loading */
.image-placeholder {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}

@keyframes loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

JavaScript ile Lazy Loading

Intersection Observer API

class LazyImageLoader {
  constructor(options = {}) {
    this.options = {
      rootMargin: "50px",
      threshold: 0.01,
      ...options,
    };

    this.imageObserver = new IntersectionObserver(
      this.handleIntersection.bind(this),
      this.options
    );

    this.init();
  }

  init() {
    const lazyImages = document.querySelectorAll("img[data-src]");
    lazyImages.forEach((img) => this.imageObserver.observe(img));
  }

  handleIntersection(entries) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        this.loadImage(entry.target);
        this.imageObserver.unobserve(entry.target);
      }
    });
  }

  async loadImage(img) {
    try {
      // Show placeholder
      img.classList.add("loading");

      // Preload image
      const imageLoader = new Image();

      return new Promise((resolve, reject) => {
        imageLoader.onload = () => {
          // Apply loaded image
          img.src = img.dataset.src;
          img.srcset = img.dataset.srcset || "";

          // Clean up
          img.removeAttribute("data-src");
          img.removeAttribute("data-srcset");
          img.classList.remove("loading");
          img.classList.add("loaded");

          resolve();
        };

        imageLoader.onerror = reject;
        imageLoader.src = img.dataset.src;
      });
    } catch (error) {
      console.error("Image loading failed:", error);
      img.classList.add("error");
    }
  }

  // Dynamic image addition support
  addImage(imgElement) {
    this.imageObserver.observe(imgElement);
  }

  // Cleanup
  destroy() {
    this.imageObserver.disconnect();
  }
}

// Usage
const lazyLoader = new LazyImageLoader({
  rootMargin: "100px", // Load images 100px before they enter viewport
  threshold: 0.1,
});

Progressive Image Loading

class ProgressiveImageLoader {
  static async loadProgressive(img, lowQualitySrc, highQualitySrc) {
    // 1. Show low quality placeholder immediately
    img.src = lowQualitySrc;
    img.classList.add("low-quality");

    // 2. Preload high quality version
    const highQualityImage = new Image();

    return new Promise((resolve) => {
      highQualityImage.onload = () => {
        // 3. Fade to high quality
        img.classList.add("loading-high-quality");

        setTimeout(() => {
          img.src = highQualitySrc;
          img.classList.remove("low-quality", "loading-high-quality");
          img.classList.add("high-quality");
          resolve();
        }, 100);
      };

      highQualityImage.src = highQualitySrc;
    });
  }
}

// CSS for smooth transitions
/*
.progressive-image {
  transition: filter 0.3s ease;
}

.progressive-image.low-quality {
  filter: blur(5px);
}

.progressive-image.loading-high-quality {
  filter: blur(2px);
}

.progressive-image.high-quality {
  filter: none;
}
*/

Build Tool Entegrasyonları

Webpack Image Optimization

// webpack.config.js
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024, // 8kb
          },
        },
      },
    ],
  },
  plugins: [
    new ImageMinimizerPlugin({
      minimizer: {
        implementation: ImageMinimizerPlugin.sharpMinify,
        options: {
          encodeOptions: {
            jpeg: {
              quality: 80,
              progressive: true,
            },
            webp: {
              quality: 80,
            },
            avif: {
              quality: 50,
            },
            png: {
              quality: 80,
              compressionLevel: 9,
            },
          },
        },
      },
      generator: [
        // WebP versions
        {
          preset: "webp-custom-name",
          implementation: ImageMinimizerPlugin.sharpGenerate,
          options: {
            encodeOptions: {
              webp: {
                quality: 80,
              },
            },
          },
        },
        // AVIF versions
        {
          preset: "avif-custom-name",
          implementation: ImageMinimizerPlugin.sharpGenerate,
          options: {
            encodeOptions: {
              avif: {
                quality: 50,
              },
            },
          },
        },
      ],
    }),
  ],
};

Next.js Image Optimization

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    formats: ["image/avif", "image/webp"],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 31536000, // 1 year
    dangerouslyAllowSVG: false,
    contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
    remotePatterns: [
      {
        protocol: "https",
        hostname: "images.unsplash.com",
        port: "",
        pathname: "/**",
      },
    ],
  },
};

module.exports = nextConfig;
// Component usage
import Image from "next/image";

function OptimizedImage({ src, alt, ...props }) {
  return (
    <Image
      src={src}
      alt={alt}
      priority={false} // Set true for above-fold images
      quality={80}
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkqGx0f/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyG621jjVZFHeTzak0A1eqZEBA"
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      {...props}
    />
  );
}

CDN ve Delivery Optimization

Image CDN Services

Cloudinary Integration

class CloudinaryImageOptimizer {
  constructor(cloudName) {
    this.cloudName = cloudName;
    this.baseUrl = `https://res.cloudinary.com/${cloudName}/image/upload/`;
  }

  generateUrl(publicId, transformations = {}) {
    const {
      width,
      height,
      quality = "auto",
      format = "auto",
      crop = "fill",
      gravity = "auto",
      fetchFormat = "auto",
      dpr = "auto",
    } = transformations;

    const transforms = [];

    if (width) transforms.push(`w_${width}`);
    if (height) transforms.push(`h_${height}`);
    if (quality) transforms.push(`q_${quality}`);
    if (format) transforms.push(`f_${format}`);
    if (crop) transforms.push(`c_${crop}`);
    if (gravity) transforms.push(`g_${gravity}`);
    if (dpr) transforms.push(`dpr_${dpr}`);

    const transformString = transforms.join(",");
    return `${this.baseUrl}${transformString}/${publicId}`;
  }

  generateResponsiveUrls(publicId, breakpoints = [480, 768, 1200, 1920]) {
    return breakpoints.map((width) => ({
      width,
      url: this.generateUrl(publicId, {
        width,
        quality: "auto",
        format: "auto",
        dpr: "auto",
      }),
      descriptor: `${width}w`,
    }));
  }
}

// Usage
const cloudinary = new CloudinaryImageOptimizer("your-cloud-name");

const responsiveUrls = cloudinary.generateResponsiveUrls("sample-image");
const srcset = responsiveUrls
  .map((item) => `${item.url} ${item.descriptor}`)
  .join(", ");

ImageKit Integration

class ImageKitOptimizer {
  constructor(urlEndpoint) {
    this.urlEndpoint = urlEndpoint;
  }

  generateUrl(path, transformations = {}) {
    const {
      width,
      height,
      quality,
      format,
      crop = "maintain_ratio",
      focus = "auto",
      blur,
      sharpen,
    } = transformations;

    const params = new URLSearchParams();

    if (width) params.append("tr", `w-${width}`);
    if (height) params.append("tr", `h-${height}`);
    if (quality) params.append("tr", `q-${quality}`);
    if (format) params.append("tr", `f-${format}`);
    if (crop) params.append("tr", `c-${crop}`);
    if (focus) params.append("tr", `fo-${focus}`);
    if (blur) params.append("tr", `bl-${blur}`);
    if (sharpen) params.append("tr", `e-sharpen-${sharpen}`);

    return `${this.urlEndpoint}${path}?${params.toString()}`;
  }
}

Performance Monitoring

Core Web Vitals Tracking

class ImagePerformanceMonitor {
  constructor() {
    this.metrics = {
      lcp: null,
      cls: null,
      imageLoadTimes: [],
    };

    this.initLCPObserver();
    this.initCLSObserver();
    this.initImageLoadObserver();
  }

  initLCPObserver() {
    if ("PerformanceObserver" in window) {
      const observer = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lastEntry = entries[entries.length - 1];

        this.metrics.lcp = lastEntry.startTime;

        // Check if LCP element is an image
        if (lastEntry.element && lastEntry.element.tagName === "IMG") {
          console.log("LCP element is an image:", lastEntry.element.src);
          this.trackImageOptimization(lastEntry.element);
        }
      });

      observer.observe({ entryTypes: ["largest-contentful-paint"] });
    }
  }

  initCLSObserver() {
    if ("PerformanceObserver" in window) {
      const observer = new PerformanceObserver((list) => {
        let cls = 0;

        list.getEntries().forEach((entry) => {
          if (!entry.hadRecentInput) {
            cls += entry.value;
          }
        });

        this.metrics.cls = cls;
      });

      observer.observe({ entryTypes: ["layout-shift"] });
    }
  }

  initImageLoadObserver() {
    const images = document.querySelectorAll("img");

    images.forEach((img) => {
      if (img.complete) {
        this.trackImageLoad(img, 0);
      } else {
        const startTime = performance.now();

        img.addEventListener("load", () => {
          const loadTime = performance.now() - startTime;
          this.trackImageLoad(img, loadTime);
        });

        img.addEventListener("error", () => {
          console.error("Image failed to load:", img.src);
        });
      }
    });
  }

  trackImageLoad(img, loadTime) {
    const imageData = {
      src: img.src,
      loadTime,
      naturalWidth: img.naturalWidth,
      naturalHeight: img.naturalHeight,
      displayWidth: img.width,
      displayHeight: img.height,
      isLazy: img.hasAttribute("loading"),
      format: this.getImageFormat(img.src),
    };

    this.metrics.imageLoadTimes.push(imageData);

    // Check for optimization opportunities
    if (img.naturalWidth > img.width * 1.5) {
      console.warn("Over-sized image detected:", img.src);
    }
  }

  trackImageOptimization(img) {
    const metrics = {
      isResponsive: img.hasAttribute("srcset"),
      hasAltText: img.hasAttribute("alt"),
      isLazy: img.hasAttribute("loading"),
      format: this.getImageFormat(img.src),
      sizeRatio: img.naturalWidth / img.width,
    };

    return metrics;
  }

  getImageFormat(src) {
    const extension = src.split(".").pop().toLowerCase().split("?")[0];
    return extension;
  }

  generateReport() {
    return {
      coreWebVitals: {
        lcp: this.metrics.lcp,
        cls: this.metrics.cls,
      },
      imageMetrics: {
        totalImages: this.metrics.imageLoadTimes.length,
        averageLoadTime: this.calculateAverageLoadTime(),
        oversizedImages: this.findOversizedImages(),
        formatDistribution: this.getFormatDistribution(),
      },
      recommendations: this.generateRecommendations(),
    };
  }

  calculateAverageLoadTime() {
    const times = this.metrics.imageLoadTimes.map((img) => img.loadTime);
    return times.reduce((sum, time) => sum + time, 0) / times.length;
  }

  findOversizedImages() {
    return this.metrics.imageLoadTimes.filter(
      (img) => img.naturalWidth > img.displayWidth * 1.5
    );
  }

  getFormatDistribution() {
    const formats = {};
    this.metrics.imageLoadTimes.forEach((img) => {
      formats[img.format] = (formats[img.format] || 0) + 1;
    });
    return formats;
  }

  generateRecommendations() {
    const recommendations = [];

    if (this.metrics.lcp > 2500) {
      recommendations.push(
        "LCP skorunu iyileştirmek için above-fold görselleri optimize edin"
      );
    }

    if (this.metrics.cls > 0.1) {
      recommendations.push(
        "CLS skorunu iyileştirmek için görsel boyutlarını belirtin"
      );
    }

    const oversized = this.findOversizedImages();
    if (oversized.length > 0) {
      recommendations.push(
        `${oversized.length} görsel gereksiz yere büyük boyutta`
      );
    }

    return recommendations;
  }
}

// Initialize monitoring
const monitor = new ImagePerformanceMonitor();

// Generate report after page load
window.addEventListener("load", () => {
  setTimeout(() => {
    const report = monitor.generateReport();
    console.log("Image Performance Report:", report);
  }, 3000);
});

Otomatik Optimizasyon Workflow

GitHub Actions ile Otomatik Optimizasyon

# .github/workflows/image-optimization.yml
name: Image Optimization

on:
  push:
    paths:
      - "assets/images/**"
      - "public/images/**"

jobs:
  optimize-images:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"

      - name: Install dependencies
        run: |
          npm install -g @squoosh/cli
          npm install sharp imagemin imagemin-webp imagemin-avif

      - name: Optimize Images
        run: |
          # Convert to WebP
          find ./public/images -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" | \
          while read image; do
            echo "Converting $image to WebP..."
            squoosh-cli --webp '{"quality":80}' "$image"
          done

          # Convert to AVIF
          find ./public/images -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" | \
          while read image; do
            echo "Converting $image to AVIF..."
            squoosh-cli --avif '{"quality":50}' "$image"
          done

      - name: Generate responsive variants
        run: node scripts/generate-responsive-images.js

      - name: Commit optimized images
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add .
          git diff --staged --quiet || git commit -m "Optimize images [skip ci]"
          git push

Node.js Optimizasyon Scripti

// scripts/generate-responsive-images.js
const sharp = require("sharp");
const fs = require("fs").promises;
const path = require("path");

class ResponsiveImageGenerator {
  constructor(inputDir, outputDir) {
    this.inputDir = inputDir;
    this.outputDir = outputDir;
    this.breakpoints = [480, 768, 1200, 1920];
    this.formats = ["webp", "avif"];
  }

  async processDirectory() {
    const files = await fs.readdir(this.inputDir);
    const imageFiles = files.filter((file) => /\.(jpg|jpeg|png)$/i.test(file));

    for (const file of imageFiles) {
      await this.processImage(file);
    }
  }

  async processImage(filename) {
    const inputPath = path.join(this.inputDir, filename);
    const name = path.parse(filename).name;

    const image = sharp(inputPath);
    const metadata = await image.metadata();

    console.log(`Processing ${filename}...`);

    // Generate responsive variants
    for (const width of this.breakpoints) {
      if (width <= metadata.width) {
        await this.generateVariant(image, name, width);
      }
    }
  }

  async generateVariant(image, name, width) {
    const resized = image.resize(width, null, {
      withoutEnlargement: true,
      kernel: sharp.kernel.lanczos3,
    });

    // Generate different formats
    for (const format of this.formats) {
      const outputPath = path.join(
        this.outputDir,
        `${name}-${width}.${format}`
      );

      let pipeline = resized.clone();

      if (format === "webp") {
        pipeline = pipeline.webp({
          quality: 80,
          effort: 6,
        });
      } else if (format === "avif") {
        pipeline = pipeline.avif({
          quality: 50,
          effort: 9,
        });
      }

      await pipeline.toFile(outputPath);
      console.log(`Generated: ${outputPath}`);
    }
  }
}

// Usage
async function main() {
  const generator = new ResponsiveImageGenerator(
    "./public/images/original",
    "./public/images/optimized"
  );

  await generator.processDirectory();
  console.log("Image optimization completed!");
}

main().catch(console.error);

Praktik Kullanım Rehberi

Görsel Optimizasyon Checklist

Before Upload:

  • [ ] Doğru format seçimi (JPEG/PNG/WebP/AVIF)
  • [ ] Uygun çözünürlük (responsive variants)
  • [ ] Compression kalite ayarı
  • [ ] Metadata temizleme

Implementation:

  • [ ] Picture element kullanımı
  • [ ] Lazy loading implementation
  • [ ] Progressive enhancement
  • [ ] Error handling

Performance:

  • [ ] Core Web Vitals monitoring
  • [ ] CDN configuration
  • [ ] Cache headers
  • [ ] Preload critical images

Hemen Başlayın

Görsel Optimizasyon aracımızla web performansınızı artırın:

  1. Akıllı sıkıştırma - Kalite/boyut dengesi
  2. Format dönüştürme - WebP, AVIF desteği
  3. Toplu işlem - Batch optimization
  4. Responsive variants - Multi-resolution output

Tüm görsel işlemleri tarayıcınızda gerçekleştirilir, dosyalarınız sunucularımıza yüklenmez.