Mert Özen Her Satırda Daha İleri
Frontend CSS Property'leri: Uygulamalı Rehber — #04

Background ve Gradient'ler: background-image, size, position, Çoklu Katman, linear/radial/conic-gradient ve image-set()

Mert Özen 15 Kas 2026 13 dk 6 görüntülenme
Background ve Gradient'ler: background-image, size, position, Çoklu Katman, linear/radial/conic-gradient ve image-set()

Bir görselin üstündeki metni okunur kılmak için background-image'a görselden önce yarı saydam bir gradient katmanı yazılır, görsel image-set() ile 1x/2x ve AVIF olarak seçilir, background shorthand'i ise background-color'ı da sıfırladığı için dikkatli kullanılır. Bookstore'un kampanya banner'ında contrast oranını piksel okuyarak ölçüyor, sRGB'deki gri orta noktayı in oklch ile düzeltiyor, conic-gradient ile puan halkası ve reduced-motion'a uyan bir skeleton yazıyoruz.

Sürüm notu: Bu yazı CSS Backgrounds and Borders Level 3, CSS Images Level 3/4 ve CSS Color Level 4 spesifikasyonlarına ve Baseline'ın Kasım 2026 durumuna (web-features 3.46.0) göre yazıldı; örnekler Chromium 158 ve WebKit 27.2 (Playwright 1.65) ile denenmiştir, Firefox desteği web-features ve MDN'in (Mozilla'nın web dokümantasyon projesi) browser-compat-data 8.1.9 verisinden alınmıştır.

Önce Tahmin Et

Üç yazı boyunca metinle uğraştık; şimdi kutunun kendisine geçiyoruz. Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için ana sayfaya bir kampanya banner'ı ekliyoruz. Önce iki küçük deneme:

.box { inline-size: 400px; block-size: 200px; }
.banner { background-color: #1e3a8a; }
.banner.sale { background: url("../img/square.png") center / cover no-repeat; }

.mix { block-size: 40px; background: linear-gradient(to right, #0000ff, #ffff00); }

Birincisi: .banner lacivert bir arka plan rengi veriyor, .banner.sale ise üstüne background shorthand'i ile bir görsel ekliyor. .sale kutusunun background-color'ı ne olur? İkincisi: maviden sarıya giden bir gradient'in tam ortasındaki piksel hangi renktir? Mavi ile sarının ortası yeşil mi olur, başka bir şey mi?

background Longhand'leri ve Shorthand'in Sıfırladıkları

background sekiz longhand'in shorthand'i: background-color, background-image, background-position, background-size, background-repeat, background-attachment, background-origin ve background-clip. Lab'deki ölçüm:

node scripts/04-background.mjs
--- chromium
.banner.sale background-color: rgba(0, 0, 0, 0)
--- webkit
.banner.sale background-color: rgba(0, 0, 0, 0)

Lacivert kayboldu: rgba(0, 0, 0, 0), yani transparent. Shorthand'de background-color yazmadık, ama shorthand yazmadığın her longhand'i başlangıç değerine çeker ve background-color'ın başlangıç değeri transparent. Görsel kutuyu tam kapladığı için bu ekranda fark edilmez; görsel yüklenemediğinde ya da yavaş geldiğinde ise metin beyaz zeminde beyaz kalır. Bu serinin font yazısında font shorthand'inin line-height'ı sıfırladığını görmüştük; background da aynı şekilde davranıyor.

Shorthand'in sırası esnek, ama iki kuralı var. background-size yalnız background-position'dan sonra ve / ile yazılabilir (center / cover). Bir değerde iki kutu anahtar kelimesi varsa (padding-box border-box) ilki background-origin, ikincisi background-clip olur; tek kutu anahtar kelimesi ikisine de uygulanır. Virgülle birden fazla katman yazabilirsin, ama background-color yalnız son katmanda yer alabilir. Benim kuralım: component'te rengi ve görseli değiştiren kurallarda longhand yaz; shorthand'i yalnız bir kutunun arka planını sıfırdan tanımlarken kullan.

background-size, background-position ve background-repeat

cover görseli oranını koruyarak kutuyu tamamen kaplayacak kadar büyütür ve taşan kısmı keser; contain görselin tamamı sığacak kadar küçültür ve boşluk bırakır. 300×300'lük bir test görselini (kırmızı zemin, ortada mavi kare) 400×200'lük bir kutuya koyup sol kenardaki ve ortadaki pikseli okudum:

/* 2) cover ve contain: 400x200 kutuda 300x300 bir görsel */
.fill { background: #1e3a8a url("../img/square.png") center / cover no-repeat; }
.fill.contain { background-size: contain; }
cover    left edge rgb(220, 38, 38)  center rgb(37, 99, 235)
contain  left edge rgb(30, 58, 138)  center rgb(37, 99, 235)

cover'da sol kenar görselin kırmızısı: görsel 400×400'e büyüdü ve kutunun eninin tamamını kapladı, üstten ve alttan 100'er piksel kesildi. contain'de sol kenar lacivert, yani background-color: görsel 200×200'e küçüldü ve iki yanda 100'er piksel boşluk kaldı. Bu fark, piksel okuyan küçük bir yardımcı ile ölçüldü: script ekran görüntüsünü alıyor ve sayfadaki bir canvas'a çizip getImageData ile rengi okuyor. Gradient'leri ve ileride gölgeleri de bu yardımcı ile ölçeceğiz. İki motorun sonuçları burada birebir aynı.

background-position anahtar kelime, uzunluk ya da yüzde alır ve dört değerli yazımla kenara göre offset verilebilir: right 1rem bottom 1rem görseli sağ alt köşeden 1rem içeride konumlar. Yüzdeler ise görselin ve kutunun aynı oranlı noktasını üst üste getirir: 25% 75%, görselin %25/%75 noktasını kutunun %25/%75 noktasına koyar. background-repeat'te repeat ve no-repeat dışında iki az bilinen değer var: space görseli kesmeden tekrarlar ve aradaki boşluğu eşit dağıtır, round görseli tam sayıda sığacak şekilde hafifçe ölçekler.

background-origin konumlandırmanın hangi kutuya göre yapılacağını (padding-box varsayılan), background-clip ise boyamanın nerede kesileceğini (border-box varsayılan) belirler. İkisi bir sonraki yazıda gradient kenarlık yaparken işe yarayacak. background-attachment: fixed görseli viewport'a sabitler ve parallax benzeri bir etki verir; ama iOS Safari'de çalışmıyor ve bu yüzden özellik Baseline değil. Mobilde bu etkiye dayanan bir tasarım yapma.

Gradient'ler: Geometri, Color Stop ve Color Hint

Gradient'ler CSS'te birer görseldir: background-image'a yazılır, background-size ve background-repeat onlara da uygulanır. Üç türün renkleri farklı bir geometriye göre yerleştirdiği aşağıdaki diyagramda görülüyor:

04-gradient-geometry-tr.png

linear-gradient'te renkler bir "gradient line" boyunca dizilir. Açı yukarıdan saat yönünde ölçülür (0deg yukarı, 90deg sağa) ve çizginin uzunluğu, 0% ve 100% noktalarından geçen dik çizgiler kutunun köşelerine değecek şekilde hesaplanır. Bu yüzden 135deg'lik bir gradient'te köşeler tam olarak ilk ve son renge sahip olur. to right ve to bottom right gibi yazımlar ise köşeye yönelir ve açı kutunun oranına göre hesaplanır.

radial-gradient'in bir merkezi (at 30% 50%) ve bir ending shape'i (circle ya da ellipse) var; ending shape'in boyutu closest-side, farthest-corner (varsayılan) gibi anahtar kelimelerle ya da uzunlukla verilir. conic-gradient ise renkleri merkezin etrafında bir açı boyunca dizer: 0deg yukarıda, yön saat yönünde, yüzdeler tam turun oranı.

Color stop'lar bu çizgi ya da açı üzerindeki konumlardır. İki stop arasına konan tek bir yüzde ise color hint'tir ve iki rengin tam ortasının nerede olacağını söyler:

/* 4) color hint orta noktayı kaydırır, eşit stop'lar keskin bir sınır çizer */
.hint { block-size: 40px; background: linear-gradient(to right, #b91c1c, 20%, #1d4ed8); }
.hard { block-size: 40px; background: linear-gradient(to right, #b91c1c 50%, #1d4ed8 50%); }
hint     at 20%: rgb(106, 53, 122)  at 50%: rgb(69, 65, 167)
hard     at 49%: rgb(185, 28, 28)  at 51%: rgb(29, 78, 216)

Hint'li gradient'te %20'deki renk rgb(106, 53, 122), yani kırmızı ile mavinin tam ortalaması: geçişin yarısı ilk %20'de tamamlanıyor. İkinci satırda aynı konuma yazılan iki stop keskin bir sınır çiziyor; %49 saf kırmızı, %51 saf mavi. Bu "hard stop" tekniği şerit, bayrak ve ilerleme çubuğu gibi pattern'lerin temeli. #b91c1c 0 8px gibi tek renge iki konum veren yazım (double position) aynı şeyi daha kısa yazmayı sağlar. repeating-linear-gradient ve diğer repeating-* function'ları da son stop'tan sonra pattern'i tekrar eder.

Gradient Interpolation: sRGB'deki Grileşme ve in oklch

Giriş sorusunun ikinci yarısı: mavi (#0000ff) ile sarı (#ffff00) arasındaki gradient'in ortası. Aynı gradient'i üç renk uzayında çizdim:

.mix { block-size: 40px; background: linear-gradient(to right, #0000ff, #ffff00); }
.mix.oklab { background: linear-gradient(to right in oklab, #0000ff, #ffff00); }
.mix.oklch { background: linear-gradient(to right in oklch, #0000ff, #ffff00); }
--- chromium
srgb     middle of the gradient: rgb(127, 127, 127)
oklab    middle of the gradient: rgb(108, 171, 198)
oklch    middle of the gradient: rgb(0, 207, 188)
--- webkit
srgb     middle of the gradient: rgb(128, 128, 128)
oklab    middle of the gradient: rgb(109, 172, 199)
oklch    middle of the gradient: rgb(0, 208, 189)

Varsayılan interpolation sRGB'de yapılıyor ve her kanal ayrı ayrı ortalanıyor: (0+255)/2, (0+255)/2, (255+0)/2. Sonuç rgb(127, 127, 127), yani gri. Renkli bir gradient'in ortasında oluşan bu soluk bant sRGB'de iki tamamlayıcı renk arasında hep görülür. in oklab algısal olarak düzgün bir uzayda ortaladığı için açık bir mavi-gri veriyor; in oklch ise ton (hue) açısını da ortaladığı için ortada parlak bir camgöbeği (rgb(0, 207, 188)) çıkıyor. Hangisinin doğru olduğu tasarıma bağlı: iki rengin arasında doygunluğu korumak istiyorsan oklch, tonu kaydırmadan yumuşak bir geçiş istiyorsan oklab. WebKit'te değerler 1 birim farklı; bu fark piksel yuvarlamasından geliyor. Renk uzaylarının kendisi ve oklch() sitedeki CSS serisinin (CSS3: Flexbox, Grid ve Modern CSS) 11. yazısında anlatıldı. Gradient'te renk uzayı seçmek Haziran 2024'ten beri Baseline newly available.

Çoklu Katman ve image-set(): Kampanya Banner'ı

Banner'ın görseli bir kitap rafı; üstüne beyaz bir başlık ve kısa bir metin geliyor. Raf görselinin açık renkli bölgeleri beyaz metni okunmaz hâle getiriyor, bu yüzden görselin üstüne yarı saydam koyu bir gradient katmanı koyuyoruz:

.promo-banner {
    display: grid;
    align-content: end;
    min-block-size: 16rem;
    padding: 2rem;
    border-radius: 0.75rem;
    color: #ffffff;
    /* görsel gelene kadar ve hiç gelmezse görünür */
    background-color: #1e293b;
    /* ilk katman en üsttedir: overlay metni okunur tutar */
    background-image:
        linear-gradient(to top, rgb(15 23 42 / 0.9), rgb(15 23 42 / 0.3) 70%),
        image-set(
            url("../../img/banner-1x.avif") 1x type("image/avif"),
            url("../../img/banner-2x.avif") 2x type("image/avif"),
            url("../../img/banner-1x.webp") 1x type("image/webp"),
            url("../../img/banner-2x.webp") 2x type("image/webp")
        );
    background-size: cover;
    background-position: center;
}

Virgülle ayrılan katmanlarda ilk yazılan en üstte çizilir. Gradient'in alt kısmı daha koyu (%90 opaklık), üst kısmı daha açık (%30); metin alta yaslandığı için en koyu bölgeye düşüyor. Görsel ise image-set() ile veriliyor: tarayıcı desteklediği türler (type()) arasından ekranın piksel yoğunluğuna (1x, 2x) en uygun olanı seçiyor. image-set() Mart 2026'dan beri Baseline widely available. Ölçüm, hangi dosyanın istendiğini ve metnin arkasındaki en açık piksele göre contrast oranını gösteriyor:

node scripts/04-pages.mjs
--- chromium
dpr 1: requested /img/banner-1x.avif
banner layers: linear-gradient + image-set
worst contrast with overlay:    h2 4.28:1, p 7.21:1
worst contrast without overlay: h2 1.03:1, p 1.09:1
dpr 2: requested /img/banner-2x.avif
skeleton animation (no-preference): shimmer
skeleton animation (reduce): none
--- webkit
dpr 1: requested /img/banner-1x.avif
banner layers: linear-gradient + image-set
worst contrast with overlay:    h2 4.35:1, p 7.13:1
worst contrast without overlay: h2 1.03:1, p 1.09:1
dpr 2: requested /img/banner-2x.avif
skeleton animation (no-preference): shimmer
skeleton animation (reduce): none

Normal ekranda (device pixel ratio 1) banner-1x.avif, 2x ekranda banner-2x.avif isteniyor ve WebP dosyalarına hiç istek gitmiyor; iki motor da AVIF (AV1 Image File Format; AV1 video codec'ine dayanan, eski formatlara göre çok daha küçük dosya üreten görsel formatı) destekliyor. Overlay ile en kötü contrast Chromium'da başlıkta 4.28:1, paragrafta 7.21:1 (WebKit'te 4.35:1 ve 7.13:1). WCAG (Web Content Accessibility Guidelines; web erişilebilirlik kuralları) AA (yasal düzenlemelerin çoğunun istediği orta uygunluk seviyesi) seviyesi normal metin için 4.5:1, 24px ve üstü büyük metin için 3:1 istiyor; 32px'lik başlık 3:1'i, paragraf 4.5:1'i geçiyor. Overlay'i kaldırdığımda (script bunu katmanı silerek yapıyor) oranlar 1.03:1 ve 1.09:1'e düşüyor: metin raftaki beyaz etiketlerin üstünde neredeyse görünmez. Contrast eşiklerinin ayrıntısı UI/UX Tasarım İlkeleri serisinin Color ve Contrast yazısında.

Gradient overlay ile text-shadow arasındaki fark burada netleşiyor: overlay arka planın kendisini koyulaştırıyor ve ölçülebilir bir contrast sağlıyor. background-color'ı da ayrıca yazdık: görsel gelmezse ya da kullanıcı görselleri kapattıysa metin yine koyu zemin üzerinde kalıyor.

Şeritli Arka Plan, Puan Halkası ve Skeleton

Ana sayfadaki kategori şeridi, kategori renginin üstüne 45 derecelik ince şeritler çiziyor. Pattern tek bir repeating-linear-gradient ve double position'lı stop'lar; rengi ise HTML'den gelen --cat-color custom property'si veriyor:

.category-strip {
    display: flex;
    gap: 1rem;
    padding: 0.75rem 1rem;
    margin-block: 1.5rem;
    border-radius: 0.5rem;
    color: #ffffff;
    /* kategori renginin üstünde 45 derecelik şeritler */
    background:
        repeating-linear-gradient(45deg, rgb(255 255 255 / 0.12) 0 8px, transparent 8px 16px),
        var(--cat-color, #1d4ed8);
}

Kitap detayındaki puan halkası iki katmanlı: altta puanın oranı kadar dolan bir conic-gradient, üstte ortası beyaz bir radial-gradient. radial-gradient'teki %78 ile %80 arasındaki küçük boşluk, halkanın iç kenarını yumuşatıyor; iki stop aynı konumda olsaydı kenar tırtıklı görünürdü:

.rating-ring {
    display: grid;
    place-items: center;
    inline-size: 4rem;
    block-size: 4rem;
    border-radius: 50%;
    font-weight: 700;
    /* conic halkanın üstünde beyaz bir disk */
    background:
        radial-gradient(closest-side, #ffffff 78%, transparent 80%),
        conic-gradient(#f59e0b calc(var(--rating) / 5 * 100%), #e5e7eb 0);
}
<div class="rating-ring" style="--rating: 4.2" role="img" aria-label="Rated 4.2 out of 5">4.2</div>

Lab'deki tek katmanlı halkada 4.2 puan, turun %84'ü, yani 302.4 derece demek. 90, 300 ve 310 derecedeki pikselleri okudum:

/* 5) puan halkası: 5 üzerinden 4.2 */
.rating {
    --rating: 4.2;
    inline-size: 120px;
    block-size: 120px;
    border-radius: 50%;
    background: conic-gradient(#f59e0b calc(var(--rating) / 5 * 100%), #e5e7eb 0);
}
rating    90deg: rgb(245, 158, 11)
rating   300deg: rgb(245, 158, 11)
rating   310deg: rgb(229, 231, 235)

300 derece hâlâ turuncu, 310 derece gri: sınır beklediğimiz yerde. Halkanın görsel bir element olduğunu unutmamak gerekiyor; puanı ekran okuyucuya role="img" ve aria-label ile, metin olarak da ortadaki "4.2" ile veriyoruz.

Son olarak katalog yüklenirken gösterilen skeleton kartları. Gradient, kutunun iki katı genişliğinde bir görsel olarak çiziliyor ve background-position animasyonla kaydırılıyor:

.skeleton {
    block-size: 8rem;
    border-radius: 0.5rem;
    background: linear-gradient(90deg, #e5e7eb 25%, #f3f4f6 50%, #e5e7eb 75%) 0 0 / 200% 100%;
    animation: shimmer 1.5s linear infinite;
}
@keyframes shimmer {
    to { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton { animation: none; }
}

Ölçümde reducedMotion: 'reduce' ile açılan sayfada animation-name none oluyor; hareket azaltma tercihi olan kullanıcı sabit gri kartlar görüyor. prefers-reduced-motion'ın kendisi CSS3 serisinin 08. yazısında anlatıldı.

Üretimde Dikkat

CSS'teki bir arka plan görseli, tarayıcı stil dosyasını indirip parse ettikten ve kuralın bir element'e uyduğunu hesapladıktan sonra keşfedilir. HTML'deki bir img ise parser tarafından hemen görülür. Banner gibi sayfanın en büyük görseli LCP (Largest Contentful Paint; sayfadaki en büyük içeriğin ekrana çizildiği an) adayıdır ve CSS'te verildiğinde geç başlar. Banner görselin içeriğin parçasıysa img ya da picture kullan; CSS'te kalacaksa <link rel="preload" as="image" imagesrcset="…"> ile önceden yüklemeyi düşün. Ayrıntılar Web Performansı serisinin görsel ve font optimizasyonu yazısında.

Contrast'ı gözle değil ölçerek kontrol et. Banner görseli kampanyadan kampanyaya değişir ve bir sonraki görselde beyaz bir bölge tam metnin arkasına gelebilir. Bu yazıdaki worstContrast fonksiyonu gibi bir kontrolü, yeni banner'ı yayına almadan önce çalışan bir teste koymak bu riski azaltır.

background-position animasyonu her karede repaint gerektirir. Üç skeleton kartta bu önemsiz; yüzlerce satırlık bir listede ise shimmer'ı bir pseudo-element üzerinde transform ile kaydırmak daha ucuz. Compositor'ın hangi property'leri ucuza canlandırdığı CSS3 serisinin 13. yazısında.

Sık Düşülen Tuzaklar

Birincisi, içerik görselini background-image ile vermek. Arka plan görselinin alt metni olmaz, ekran okuyucu onu hiç duyurmaz, kullanıcı sağ tıklayıp kaydedemez ve tarayıcı onu geç keşfeder. Ürün kapağı, yazar fotoğrafı ve banner'daki anlamlı görsel HTML'de olmalı; markup tarafı sitedeki HTML serisinin (HTML5 ve Semantic HTML) Image ve Media yazısında.

İkincisi, iki renk arasındaki gradient'in ortasının grileşmesini tasarım hatası sanmak. Ölçtüğümüz gibi bu sRGB interpolation'ın sonucu; in oklab ya da in oklch ekleyerek düzelt.

Üçüncüsü, background shorthand'i ile önceki bir kuraldan gelen background-color'ı silmek. Hover ya da modifier class'larında yalnız değişen longhand'i yaz.

Dördüncüsü, background-clip: text ile gradient yazı yaparken yedek rengi unutmak. Bu değer web-features'ta Baseline değil: Firefox'ta kısmi destek var ve çoklu katmanlarda çalışmıyor. Metnin rengini önce düz bir color ile ver, gradient'i @supports (background-clip: text) içinde ekle.

Baseline Durumu

node scripts/baseline.mjs background background-size background-position background-clip background-attachment gradients conic-gradients gradient-interpolation image-set background-clip-text
background                 widely 2018-01-29  Chrome 1, Firefox 3.6, Safari 1.3, iOS 1
background-size            widely 2018-01-29  Chrome 21, Firefox 9, Safari 5.1, iOS 4.2
background-position        widely 2018-01-29  Chrome 25, Firefox 13, Safari 7, iOS 7
background-clip            widely 2018-01-29  Chrome 21, Firefox 22, Safari 5.1, iOS 5
background-attachment      not Baseline       Chrome 1, Firefox 25, Safari 15.4, iOS -
gradients                  widely 2018-01-29  Chrome 26, Firefox 3.6, Safari 7, iOS 7
conic-gradients            widely 2023-05-17  Chrome 69, Firefox 83, Safari 12.1, iOS 12.2
gradient-interpolation     newly 2024-06-11   Chrome 111, Firefox 127, Safari 16.2, iOS 16.2
image-set                  widely 2026-03-18  Chrome 113, Firefox 89, Safari 17, iOS 17
background-clip-text       not Baseline       Chrome 120, Firefox -, Safari 14, iOS 14
ÖzellikDurumNot
background ve longhand'leri (attachment hariç)Widely availableShorthand background-color'ı da sıfırlar
background-attachmentBaseline değilfixed iOS Safari'de yok
Linear, radial ve repeating gradient'ler, color hint, double positionWidely available
conic-gradientWidely available2023-05'ten beri
Gradient'te renk uzayı (in oklch)Newly available2024-06'dan beri
image-set() ve type()Widely available2026-03'ten beri
background-clip: textBaseline değilFirefox kısmi; @supports ile

Küçük Bir Deneme

★☆☆ Banner overlay'inin üst durağını rgb(15 23 42 / 0.1) yap ve 04-pages.mjs ile başlığın contrast oranının 3:1'in altına düşüp düşmediğini ölç.

★★☆ Kategori şeridindeki pattern'i in oklch ile iki renkli bir gradient'e çevir ve lab'deki piksel okuyucuyla şeridin ortasındaki rengi sRGB sürümüyle karşılaştır.

★★★ Puan halkasını, puanı yarım yıldızlara (0.5 adım) yuvarlayan bir round() hesabı ile güncelle ve 3.8 puan için halkanın 4.0'a yuvarlanıp sınırın 288 derecede (%80) olduğunu piksel ölçümüyle doğrula.

Bir sonraki yazıda kutunun kenarlarına geçiyoruz: border ve kenar bazlı longhand'ler, border-radius'un eliptik ve pill kullanımı, background-clip ile gradient kenarlık, outline ile focus halkası ve box-shadow ile bir elevation ölçeği.