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
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:
- Akıllı sıkıştırma - Kalite/boyut dengesi
- Format dönüştürme - WebP, AVIF desteği
- Toplu işlem - Batch optimization
- 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.