CSS image-set() ile Format ve Yoğunluk Tabanlı Görsel Servisi
CSS'de bir arka plan görseli eklemek istediğinizde, background-image: url(...) yazmak başlangıçta yeterlidir. Ama modern tarayıcıların WebP ve AVIF gibi formatları desteklemesi, retina ekranların daha yüksek çözünürlük gerektirmesi bu basit satırı yetersiz kılmaya başladı. HTML tarafında bu sorunu srcset çözüyordu; CSS tarafında aynı işi image-set() üstleniyor.
image-set(), background-image ve content özelliklerinde kullanılabilen bir CSS fonksiyonu. Birden fazla görsel seçeneği listeleyip tarayıcının bunlar arasından en uygununu seçmesini sağlıyor. Seçim kriteri iki eksene bakıyor: piksel yoğunluğu ve görsel formatı. İki eksen birleşince, hem düşük hem yüksek DPI ekranlar için hem de farklı tarayıcı yetenekleri için tek CSS bloğuyla doğru görseli sunmak mümkün hale geliyor.
Özelliğin tarayıcı desteği birkaç yıl boyunca kısmi kaldı, vendor prefix gerektirdi ve spesifikasyon birden fazla kez değişti. Günümüzde durum önemli ölçüde düzelmiş olsa da bazı sözdizimi nüansları hâlâ dikkat istiyor; özellikle type() sözdiziminin prefixli sürümle uyumsuzluğu hâlâ tuzak kuruyor.
Yoğunluk tanımlayıcısı ve temel sözdizimi
image-set() içinde her görsel iki parçadan oluşur: URL ve tanımlayıcı. En yaygın kullanım yoğunluk tanımlayıcısıyla (density descriptor) başlar:
background-image: image-set(
url("hero.webp") 1x,
url("[email protected]") 2x
);
1x standart ekranları, 2x retina veya HiDPI ekranları hedefler. Tarayıcı, window.devicePixelRatio değerine bakarak hangisini kullanacağına karar verir. 1.5x, 3x gibi değerler de geçerlidir; Apple'ın bazı cihazları 3x değerine sahiptir ve 2x varyant burada hâlâ gönderilir, ancak piksel başına kalite düşer.
Tarayıcı bu seçimi sayfa yüklenirken yapar. Genellikle tek seferlik bir karardır. JavaScript ile devicePixelRatio değişimini izleyip CSS'i güncellemediğiniz sürece, ilk yükleme anındaki karar geçerli kalır; monitör değiştirme ya da ekran paylaşımı gibi nadir durumlar zaten başka stratejilerle ele alınır. Yoğunluk tanımlayıcısı tek başına format kararı vermez.
Sık yapılan bir hata, yoğunluk tanımlayıcısını tek format üzerinden kullanıp JPEG bırakmak. Böyle bir yapı ekran çözünürlüğünü çözüyor ama WebP veya AVIF avantajını CSS'e taşımıyor; format müzakeresi için ayrı bir mekanizma gerekiyor.
Format ipuçları: type() ile WebP ve AVIF sunmak
type() sözdizimi her görsel için MIME tipi belirtir. Tarayıcı listeyi sırayla okuyup desteklediği ilk formatı seçer:
background-image: image-set(
url("hero.avif") type("image/avif"),
url("hero.webp") type("image/webp"),
url("hero.jpg") type("image/jpeg")
);
AVIF desteği olan Chrome 85+ ve Firefox 93+ sürümleri ilk seçeneği alır; WebP bilen ama AVIF bilmeyen eski Chrome sürümleri ikincisini, Safari 13 ve öncesi ise JPEG'i alır. Sıralama önemli: en verimli format başa yazılır, en geniş desteklenen sona.
type() ve yoğunluk tanımlayıcısı aynı anda kullanılabilir:
background-image: image-set(
url("hero.avif") type("image/avif") 1x,
url("[email protected]") type("image/avif") 2x,
url("hero.webp") type("image/webp") 1x,
url("[email protected]") type("image/webp") 2x,
url("hero.jpg") type("image/jpeg") 1x
);
Beş satır korkutucu görünebilir, ama her satır belirli bir tarayıcı-ekran kombinasyonuna karşılık geliyor. Aynı görsel için beş ayrı dosya saklamak istemiyorsanız, AVIF ve WebP varyantlarını yalnızca 1x için sunup 2x fallback olarak JPEG bırakmak da makul bir tercih; bu karar performans bütçenize ve hedef kitlenize göre değişir. Retina ekranlarda büyük görünecek bir hero görseli için her iki ekseni de çözmek mantıklı, küçük arka plan desenleri için ise yoğunluk setini atlayabilirsiniz.
Bir öncelik kuralını da hatırlatmak gerekiyor: tarayıcı listedeki ilk uyumlu girdide durur, geri kalanı indirmez. Bu davranış hem format hem yoğunluk tanımlayıcıları için geçerlidir; dolayısıyla aynı format için 1x ve 2x her ikisini de listeye koyduğunuzda yalnızca bir tanesi indirilir.
Vendor prefix zorunluluğu ve mevcut destek durumu
image-set(), uzun süre -webkit-image-set() prefixi gerektirdi. Safari, prefiksiz sürümü ancak 2023'te tam olarak desteklemeye başladı. 2026 itibarıyla güncel Safari, Chrome, Firefox ve Edge prefiksiz sözdizimini anlıyor. Eski iOS ve macOS cihazları için destek gerekiyorsa prefixli ve prefiksiz sürümü birlikte yazmak mantıklıdır:
.hero {
background-image: -webkit-image-set(
url("hero.webp") 1x,
url("hero.jpg") 1x
);
background-image: image-set(
url("hero.webp") type("image/webp") 1x,
url("hero.jpg") type("image/jpeg") 1x
);
}
Aynı özellik iki kez yazıldığında tarayıcı son geçerli değeri kullanır. Prefixli sözdizimini anlayan eski tarayıcılar birinci satırda kalır, standart sözdizimini anlayan modern tarayıcılar ikincisine geçer; bu mekanizma CSS cascade'in doğal davranışıdır.
Buradaki önemli bir nüans: -webkit-image-set() içinde type() desteklenmiyordu. Prefix bloğundan type() çıkarılmalı, o blok yalnızca yoğunluk tanımlayıcısıyla sınırlı tutulmalı. Aksi takdirde eski Safari tüm bloğu geçersiz sayabilir ve görsel hiç yüklenmeyebilir. Prefiksiz blokta ise type() tam desteğe sahiptir.
CSS cascade ile fallback stratejisi
image-set() desteklemeyen tarayıcılar için doğru fallback, kuralı image-set() bloğundan önce yazmaktır:
.hero {
background-image: url("hero.jpg");
background-image: image-set(
url("hero.avif") type("image/avif"),
url("hero.webp") type("image/webp"),
url("hero.jpg") type("image/jpeg")
);
}
image-set() bilinmiyorsa tarayıcı ikinci satırı görmezden gelir, ilk url() kalır. Bilen tarayıcılar ikinci satırı kullanır. İki satırlık bu cascade stratejisi @supports gerektirmez ve temiz çalışır.
@supports (background-image: image-set(...)) kontrolü de yapılabilir, ancak özellik algılama sözdizimi burada karmaşıklaşır. Çoğu durumda iki satırlı cascade yeterlidir. İstisna: yalnızca type() destekleyen tarayıcılar için farklı mantık çalıştırılmak istendiğinde veya Sass/PostCSS gibi araçlarla derleme zamanında ayrı dallar üretmek gerektiğinde @supports devreye alınabilir.
Preload etiketi kullanıyorsanız ayrı bir güçlük çıkıyor. <link rel="preload">, imagesrcset ve imagesizes niteliklerini destekler; ancak CSS image-set() ile yüklenen görseller için preload yazmak mümkün değildir çünkü tarayıcı CSS'i parse edene kadar hangi dosyanın seçileceğini bilemez. Kritik arka plan görselleri için bu gecikme önemli hale geliyorsa, bir sonraki bölümde ele alınan mimari karara tekrar bakmak gerekebilir.
Ne zaman işe yaramaz ya da gereksizdir?
<img> etiketleri için image-set() doğru araç değil. HTML srcset ve <picture> orada çok daha güçlü çalışır; boyut bilgisini de işin içine katar (w tanımlayıcısı). CSS'deki image-set() yalnızca background-image, content, list-style-image gibi CSS özelliklerine uygulanıyor. HTML görsellerinde kullanmaya çalışmak anlamsız; farklı araçlar.
Çok küçük veya dekoratif görseller için format seti oluşturmak gereksiz karmaşıklık yaratabilir. Bir simge veya 5 KB'lık arka plan dokusu için üç formatta varyant üretmek net bir fayda sağlamaz; dosya boyutu farkı ihmal edilebilir kalır, ama derleme ve yönetim maliyeti artar. Bu karar genellikle basittir: görsel ne kadar büyükse ve ne kadar sık yükleniyorsa, format optimizasyonunun getirisi o kadar yüksek olur.
Görseli dinamik olarak yöneten bir CDN kullanıyorsanız, URL parametresiyle format ve boyut belirleyebilen bir servis, image-set() karmaşıklığı çoğu zaman gereksiz hale gelir. Tarayıcının Accept başlığına bakıp otomatik WebP ya da AVIF döndüren CDN'ler mevcuttur; bu durumda tek bir url() yeterlidir, format müzakeresi CDN tarafında gerçekleşir.
background-image ile büyük içerik görseli kullanmak başlı başına tartışmalıdır. SEO açısından bakıldığında, CSS arka planındaki görseller crawl edilmez; Google'ın görsel arama indeksine girmesi için görselin <img> etiketiyle işaretlenmesi gerekir. Erişilebilirlik tarafında, alt metni yazılamaz. LCP gibi performans metrikleri açısından ise <img> çoğu zaman daha iyi bir seçimdir. image-set() bu kararı değiştirmez; yalnızca CSS yolunu seçmişseniz ona format çeşitliliği ekler.
<picture> ile karşılaştırma: hangi durum hangisini gerektirir?
<picture> HTML katmanında çalışır ve birden fazla <source> içerebilir. srcset, sizes ve media niteliklerini aynı anda kullanabilirsiniz; tarayıcı boyutu, çözünürlüğü ve formatı hep birden değerlendirir. Ayrıca tarayıcı, <picture> için kaynak listesini HTML parse aşamasında görebilir; bu sayede erken keşif ve preload boru hattına katılım mümkündür.
LCP öğesinin <img> olması gereken durumlarda <picture> doğal seçimdir. fetchpriority="high" ve loading="eager" nitelikleri doğrudan <img> üzerine yazılır; ayrıca <link rel="preload" imagesrcset="..."> ile ön yükleme de desteklenir. CSS'deki image-set() bunların hiçbirini sunamaz.
image-set() CSS katmanında kalır. background-image olarak yüklenen büyük görseller LCP hesabına girebilir, ama CSS parse edilene kadar tarayıcı bu görseli keşfedemez; bu gecikme <picture>'a kıyasla dezavantaj yaratır. Küçük, dekoratif ya da JavaScript ile dinamik olarak değiştirilen arka plan görselleri ise bu kısıttan etkilenmez.
Pratik ayrım şu: içerik görseli için <picture>, dekoratif veya CSS'e bağlı arka plan için image-set(). İkisi aynı sorunu çözmüyor; biri görseli işaretlemeye taşır, diğeri CSS'de bırakır. Hangi yolun seçileceği, görselin sayfadaki rolüne bağlıdır; bazen bu karar performans ve SEO gereksinimlerini birlikte değerlendirerek verilir, bazen tasarım kısıtı seçim alanını zaten daraltır.
image-set() CSS'in uzun süre eksik olan bir parçasını tamamlıyor: arka plan görselleri için format ve yoğunluk bazlı seçim. Sözdizimini öğrenmek, vendor prefix geçmişini anlamak ve cascade fallback yazmak bir kere emek istiyor; sonrasında benzer şablonları her projede yeniden kullanabilirsiniz.
Özelliğin tüm gücünü görmek için type() sözdizimini ve yoğunluk tanımlayıcısını birleştirmek gerekiyor. Yalnızca 1x/2x yazmak yeterli görünebilir, ama format müzakeresi olmadan AVIF ve WebP avantajı CSS arka planlarına taşınmıyor; iki eksen birlikte yazıldığında tablo tamamlanıyor.
Vendor prefix gerektiren dönem geride kalıyor. Modern projelerde prefiksiz sözdizimi ve basit cascade fallback ile başlamak makul; eski iOS desteği gerekiyorsa -webkit- bloğunu öne, type() olmadan eklemek yeterli. Sonrasında projenin browser hedefine göre bu satırlar silinebilir.