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

Görselleri ve SVG İkonları Stillemek: img Reset'i, object-position, image-rendering, fill, stroke ve currentColor

Mert Özen 15 Kas 2026 13 dk 14 görüntülenme
Görselleri ve SVG İkonları Stillemek: img Reset'i, object-position, image-rendering, fill, stroke ve currentColor

Görsellerin altındaki boşluk img'nin inline olmasından gelir; display: block, max-inline-size: 100% ve block-size: auto'dan oluşan reset bu boşluğu kaldırır, oranı korur ve width/height attribute'larıyla yer ayırmayı bozmaz. Yazı object-position ile odak noktasını, image-rendering ve image-orientation'ı, inline SVG'de fill, stroke ve currentColor'ı, Bookstore'un tek sprite'lık ikon setini ve dark mode'da light-dark() ile logo rengini ölçümlerle anlatır.

Sürüm notu: Bu yazı CSS Images Level 3/4 ve CSS Color Level 5 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

Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) artık kapak görselleri, bir yazar fotoğrafı ve birkaç ikonla dolu. Bu yazıda bunları düzenli hâle getiriyoruz: bütün görseller için tek bir reset, fotoğrafın doğru kısmını gösteren bir kırpma ve katalogdaki ikonlar için tek bir SVG (vektörel görsel formatı) sprite'ı. Önce eski bir soru:

/* 1) inline img baseline'a oturur */
#inline-img img { inline-size: 100px; block-size: 100px; }
#block-img img { display: block; inline-size: 100px; block-size: 100px; }

İki gri kutunun içinde de 100×100'lük bir görsel var ve kutuların başka içeriği yok. Kutuların yüksekliği kaç piksel? Font 20px, line-height 1.5. Cevabını not et.

img'nin Inline Davranışı ve Reset'i

img, varsayılan olarak inline bir element'tir; yani bir harf gibi satırın içine yerleşir ve alt kenarı satırın baseline'ına (harflerin üstünde durduğu çizgiye) oturur. Baseline'ın altında, "g" ve "y" gibi harflerin aşağı uzanan kısımlarına ayrılmış bir boşluk kalır. Lab'deki ölçüm:

node scripts/09-images.mjs
--- chromium
inline-img frame 300x108
block-img  frame 300x100
--- webkit
inline-img frame 300x107.44
block-img  frame 300x100

Inline görselli kutu Chromium'da 108, WebKit'te 107.44 piksel; görselin altında 7-8 piksellik bir boşluk var. display: block ile görsel satırdan çıkıyor ve kutu tam 100 piksel. Boşluğun miktarı font'a ve line-height'a bağlı; motorlar arasındaki küçük fark da font metriklerinden geliyor. vertical-align: middle ya da bottom da boşluğu kaldırır (bu property'yi serinin ikinci yazısında ikon hizası için kullandık), ama bir görselin satırın parçası olması nadiren istenir. Bu yüzden çoğu proje görselleri baştan block yapar.

İkinci sorun boyut. HTML'deki width ve height attribute'ları layout'un görselin yerini dosya gelmeden ayırabilmesi için şart (gerekçesi sitedeki HTML serisinin (HTML5 ve Semantic HTML) 08. yazısında), ama CSS'te kontrol edilmezlerse görseli o boyutta tutarlar. 1200×400'lük bir banner'ı 300px genişliğindeki bir kutuya koydum:

/* 2) reset: container'dan geniş olmaz, yükseklik orandan gelir */
#max-only img { max-inline-size: 100%; }
#reset img, #pending img { display: block; max-inline-size: 100%; block-size: auto; }
max-only   img 300x400
reset      img 300x100
pending    img 300x100 before the file arrives

Yalnız max-inline-size: 100% yazınca görsel 300px'e daralıyor ama yüksekliği height attribute'undan gelen 400px'te kalıyor; oran bozuluyor. block-size: auto eklenince yükseklik orandan hesaplanıyor ve görsel 300×100 oluyor. Üçüncü satır daha önemli: dosyası hiç gelmeyen bir görsel de 300×100 yer kaplıyor. Tarayıcılar width ve height attribute'larını bir aspect-ratio varsayılanına çeviriyor ve block-size: auto bu oranı kullanıyor. Yani reset, attribute'ların CLS'i (Cumulative Layout Shift; sayfa yüklenirken içeriğin kayma miktarı) önleme işini bozmuyor.

Bookstore'un base stiline bu reset'i ekledik ve component'lerde tekrar eden display: block ve block-size: auto satırlarını sildik:

/* görseller: block, container'larından geniş olmaz, yükseklik width/height oranından gelir */
img, picture, video {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
}

svg'yi bu listeye koymadık: fiyatın yanındaki ikon gibi metnin içinde duran ikonlar inline kalmalı. max-inline-size yerine max-width da yazabilirsin; logical karşılıklar yazı yönüne göre çalışır (CSS3 serisinin 03. yazısı).

object-position ile Odak Noktası

Önceki yazıda yazar fotoğrafını object-fit: cover ile kare bir kutuya sığdırdık ve yazarın başının üstü kesildi. object-fit görselin kutuyu nasıl dolduracağını belirler (sitedeki CSS serisinin (CSS3: Flexbox, Grid ve Modern CSS) 08. yazısı); object-position ise kırpılan görselin hangi kısmının kutuda kalacağını. Başlangıç değeri 50% 50%, yani ortası:

/* 3) object-fit kırpar, object-position kalacak kısmı seçer */
.portrait { display: block; inline-size: 96px; block-size: 96px; object-fit: cover; }
#focus-top { object-position: top; }
focus-center object-position 50% 50%  pixel (48, 12): rgb(180, 121, 91)
focus-top    object-position 50% 0%   pixel (48, 12): rgb(69, 49, 41)

240×360'lık portre 96 piksel genişliğe ölçekleniyor ve 144 piksel yüksekliğinden 48 pikseli kırpılıyor. Ortadan kırpınca 12. satırda yüz var (180, 121, 91); üstten kırpınca aynı noktada saç (69, 49, 41), yani başın tepesi artık kutunun içinde. object-position, background-position ile aynı syntax'ı alır: anahtar kelimeler, yüzdeler ve kenardan offset'ler (right 20% bottom 0). Fotoğraflarda yüz genellikle üst üçte birde olduğu için top ya da 50% 25% iyi bir başlangıç. Bookstore'daki yazar fotoğrafına da bunu ekledik:

.author-photo {
    flex: none;
    inline-size: 6rem;
    block-size: 6rem;
    object-fit: cover;
    /* yüz portrenin üst üçte birinde */
    object-position: top;
    /* burada border-radius: 50% ile aynı sonuç, ama outline ve gölgeleri de keser */
    clip-path: circle(50%);
}

image-rendering ve image-orientation

Tarayıcı bir görseli büyütürken pikseller arasını yumuşak geçişlerle doldurur. Fotoğraf için doğru olan bu, ama piksel sanatı, küçük bir QR (karekod) kodu ya da mağazadan teslim alma kodu gibi keskin kenarlı görseller bulanıklaşır. image-rendering: pixelated her pikseli büyütülmüş bir kare olarak çizer:

/* 4) sekiz kat büyütülmüş küçük bir kod görseli */
.code { display: block; inline-size: 64px; block-size: 64px; }
#code-pixelated { image-rendering: pixelated; }
--- chromium
code-smooth    distinct colors: 91
code-pixelated distinct colors: 2
--- webkit
code-smooth    distinct colors: 58
code-pixelated distinct colors: 2

Görselde yalnız iki renk var. Sekiz kat büyütülünce varsayılan çizimde Chromium 91, WebKit 58 farklı renk üretiyor: kenarlardaki ara tonlar. pixelated ile iki motorda da yalnız 2 renk kalıyor.

Telefonla çekilen fotoğraflarda görüntü dosyaya genellikle yan yatık kaydedilir ve doğru yön EXIF (fotoğraf dosyasındaki çekim bilgileri) verisinde yazar. image-orientation'ın varsayılanı from-image: tarayıcı bu bilgiye göre döndürür.

<img id="photo-auto" src="../img/shelf-photo.jpg" alt="">
<img id="photo-none" src="../img/shelf-photo.jpg" alt="" style="image-orientation: none">
photo-auto image-orientation from-image box 100x200  bottom left pixel rgb(21, 127, 61)
photo-none image-orientation none       box 200x100  bottom left pixel rgb(184, 28, 29)

Dosya 200×100 ve EXIF'te "90 derece döndür" yazıyor. Varsayılan değerle görsel 100×200 çiziliyor ve sol alt köşesi yeşil; none ile dosyadaki ham hâli, 200×100 ve sol alt köşesi kırmızı. Kullanıcıların yüklediği fotoğraflarda varsayılanı değiştirmek için bir sebep yok; none'ın tek kullanımı, görseli sunucuda zaten döndürdüğün ve EXIF'i silmediğin durumda ikinci kez dönmesini önlemek.

Inline SVG: fill, stroke ve currentColor

Bir SVG'yi sayfaya img ile eklersen tek bir görsel gibi davranır; içindeki şekillere CSS ile ulaşamazsın. Inline yazılan ya da <use> ile bir sprite'tan çağrılan SVG'de ise fill, stroke, stroke-width gibi değerler normal CSS property'leridir ve inherit edilir. Lab'de üç kalbi karşılaştırdım:

<!-- 5) sprite: symbol'ler bir kez tanımlanır, sonra id ile kullanılır -->
<svg hidden>
    <symbol id="heart" viewBox="0 0 24 24"><path d="M12 21s-7.5-4.6-9.4-9.3C1.2 8.3 3.4 4.5 7 4.5c2 0 3.6 1.1 5 2.8 1.4-1.7 3-2.8 5-2.8 3.6 0 5.8 3.8 4.4 7.2C19.5 16.4 12 21 12 21z"/></symbol>
    <symbol id="heart-fixed" viewBox="0 0 24 24"><path fill="#f59e0b" d="M12 21s-7.5-4.6-9.4-9.3C1.2 8.3 3.4 4.5 7 4.5c2 0 3.6 1.1 5 2.8 1.4-1.7 3-2.8 5-2.8 3.6 0 5.8 3.8 4.4 7.2C19.5 16.4 12 21 12 21z"/></symbol>
</svg>
    <svg class="icon" id="icon-use" aria-hidden="true"><use href="#heart"/></svg>
    <svg class="icon" id="icon-fixed" aria-hidden="true"><use href="#heart-fixed"/></svg>
    <img class="icon" id="icon-img" src="../img/heart.svg" alt="">
<svg class="icon" id="icon-use" aria-hidden="true"><use href="#heart"/></svg>
<svg class="icon" id="icon-fixed" aria-hidden="true"><use href="#heart-fixed"/></svg>
<img class="icon" id="icon-img" src="../img/heart.svg" alt="">
/* 6) inline SVG rengini metinden alır */
.icon { display: inline-block; inline-size: 1em; block-size: 1em; fill: currentColor; }
.links { color: #1d4ed8; }
img.icon { fill: #b91c1c; }
icon-use   size 20x20  pixel: rgb(29, 78, 216)
icon-fixed size 20x20  pixel: rgb(245, 158, 11)
icon-img   size 20x20  pixel: rgb(0, 0, 0)

Üç ikon da 20×20: 1em, font boyutuna eşit; ikon metinle birlikte büyür. <use> ile çağrılan kalp, fill: currentColor sayesinde bölümün mavi metin rengini (29, 78, 216) alıyor. heart-fixed symbol'ündeki path'te ise fill="#f59e0b" attribute'u var ve ikon turuncu kalıyor: <use>'ın içine kopyalanan path kendi değerini taşıyor, dışarıdan inherit edilen değer ona ulaşmıyor. Sprite'taki şekillerde fill ve stroke attribute'larını sil, rengi CSS'e bırak. img ile eklenen kalp ise fill: #b91c1c kuralına rağmen siyah (0, 0, 0): SVG'nin varsayılan dolgusu siyah ve CSS içeri giremiyor.

Katalog İkonları: Tek Sprite ve currentColor

Bookstore'da üç ikonu (sepet, kalp, fiyat etiketi) tek bir dosyada symbol olarak topladık:

<svg xmlns="http://www.w3.org/2000/svg">
    <symbol id="cart" viewBox="0 0 24 24"><path d="M3 4h2l2.4 10.2a1 1 0 0 0 1 .8h8.9a1 1 0 0 0 1-.8L20 7H6"/><circle cx="9" cy="19" r="1.5"/><circle cx="17" cy="19" r="1.5"/></symbol>
    <symbol id="heart" viewBox="0 0 24 24"><path d="M12 21s-7.5-4.6-9.4-9.3C1.2 8.3 3.4 4.5 7 4.5c2 0 3.6 1.1 5 2.8 1.4-1.7 3-2.8 5-2.8 3.6 0 5.8 3.8 4.4 7.2C19.5 16.4 12 21 12 21z"/></symbol>
    <symbol id="tag" viewBox="0 0 24 24"><path d="M3 3h8l10 10-8 8L3 11z"/></symbol>
</svg>
/* img/icons.svg'deki ikonlar: metin kadar büyük, metnin rengiyle boyanır */
.icon {
    inline-size: 1em;
    block-size: 1em;
    flex: none;
    fill: currentColor;
}
/* çizgi ikonlar: dolgu yok, stroke metnin rengini alır */
.icon-line {
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linejoin: round;
}
<p class="price"><svg class="icon" aria-hidden="true"><use href="img/icons.svg#tag"/></svg> ₺420.00</p>
<button type="button" class="fav" aria-pressed="false" aria-label="Favorite: Dune"><svg class="icon icon-line" aria-hidden="true"><use href="img/icons.svg#heart"/></svg></button>
.fav {
    margin-block-start: 0.5rem;
    padding: 0.25rem;
    border: 0;
    background: none;
    color: var(--color-muted);
    font-size: 1.5rem;
    line-height: 0;
    cursor: pointer;
}
.fav:hover, .fav[aria-pressed="true"] { color: var(--color-accent); }
/* aynı symbol, dolu hâli: fill bir CSS property'si */
.fav[aria-pressed="true"] .icon { fill: currentColor; }

Dolgulu ikonlar (fiyat etiketi) fill: currentColor ile, çizgi ikonlar (sepet, kalp) icon-line ile boyanıyor. Kalbin favori durumunda dolması için ikinci bir ikon gerekmiyor: aynı symbol'e fill veriyoruz. Butonun durumunu aria-pressed taşıyor ve görünüş ona bağlı; JavaScript yalnız attribute'u değiştiriyor. Sayfadaki ölçüm:

node scripts/09-pages.mjs
--- chromium
cover frame height 289.796875, img height 289.796875
price icon 16x16, sprite requests: 1
heart normal   fill none, stroke rgb(75, 85, 99), center pixel rgb(255, 255, 255)
heart hover    fill none, stroke rgb(185, 28, 28), center pixel rgb(255, 255, 255)
heart pressed  fill rgb(185, 28, 28), stroke rgb(185, 28, 28), center pixel rgb(185, 28, 28)
aria-pressed: true
author photo object-position 50% 0%, pixel (48, 12): rgb(69, 49, 41)
--- webkit
cover frame height 289.796875, img height 289.796875
price icon 16x16, sprite requests: 1
heart normal   fill none, stroke rgb(75, 85, 99), center pixel rgb(255, 255, 255)
heart hover    fill none, stroke rgb(185, 28, 28), center pixel rgb(255, 255, 255)
heart pressed  fill rgb(185, 28, 28), stroke rgb(185, 28, 28), center pixel rgb(185, 28, 28)
aria-pressed: true
author photo object-position 50% 0%, pixel (48, 12): rgb(69, 50, 40)

Kapak çerçevesi ile görsel aynı yükseklikte (289.80); alttaki boşluk reset ile gitti. Fiyat ikonu 16×16, yani 1rem'lik metinle aynı. Sayfada dokuz yerde (dört fiyat etiketi, dört kalp, bir sepet) kullanılan sprite yalnız bir kez isteniyor. Kalp normalde gri bir çizgi (75, 85, 99) ve ortası kartın beyazı; hover'da çizgi kırmızıya dönüyor; basılınca fill da kırmızı oluyor ve ortadaki piksel (185, 28, 28). Son satır yazar fotoğrafı için: object-position: 50% 0% ve 12. satırda saç.

Harici sprite'ın (icons.svg#cart) bir koşulu var: <use> yalnız aynı origin'deki dosyalara izin verir. İkonları bir CDN'den (içerik dağıtım ağı) sunuyorsan sprite'ı sayfaya inline gömmek gerekir. Harici dosyanın faydası ise tarayıcı cache'inde (önbellek) kalması: her sayfa HTML'inde aynı SVG'yi taşımaz.

Dark Mode'da Görseller ve Logolar

Koyu temada renkli bir logo ya da ikon için en kolay görünen yol filter: invert(1), ama bu marka rengini de ters çevirir. Rengi temaya göre seçen light-dark() daha doğru sonuç verir:

/* 7) her renk şeması için bir logo rengi ve bunun yerine invert()'ün yaptığı */
.logo { inline-size: 48px; block-size: 48px; fill: light-dark(#b91c1c, #fca5a5); }
#logo-inverted { fill: #b91c1c; filter: invert(1); }
--- chromium, light: logo rgb(185, 28, 28), inverted logo rgb(70, 227, 227)
--- chromium, dark: logo rgb(252, 165, 165), inverted logo rgb(70, 227, 227)

light-dark()'lı logo açık temada kırmızı (185, 28, 28), koyu temada açık kırmızı (252, 165, 165): aynı rengin koyu zeminde okunan tonu. invert(1) ise kırmızıyı camgöbeğine (70, 227, 227) çeviriyor ve bunu iki temada da yapıyor, çünkü filter temayı bilmiyor. light-dark() yalnız color-scheme'ı light dark olan bir sayfada çalışır; property'nin kendisi ve tema token'ları CSS3 serisinin 10. yazısında. Fotoğraf ya da çok renkli bir logo gibi CSS ile boyanamayan görsellerde ayrı bir kaynak kullan: <picture> içinde media="(prefers-color-scheme: dark)" olan bir <source>.

Üretimde Dikkat

İkon font'ları (ikonların bir font dosyasındaki karakterler olarak gelmesi) yerine SVG kullan. İkon font'u yüklenemediğinde ya da kullanıcı kendi font'unu zorladığında ikonlar anlamsız karakterlere döner, screen reader bu karakterleri okumaya çalışabilir ve font, sayfanın kullanmadığı yüzlerce ikonu da indirtir. SVG sprite'ta her ikon ayrı bir symbol ve yalnız kullanılanlar çizilir.

Süs amaçlı ikonlara aria-hidden="true" ver; bilgi taşıyan ikonun (yalnız ikondan oluşan bir buton gibi) erişilebilir adı butonun kendisinde olmalı (aria-label ya da görünmeyen bir metin). Katalogdaki favori butonları aria-label="Favorite: Dune" taşıyor.

Görsellerde width ve height attribute'larını hiçbir zaman silme; reset'le birlikte doğru oranı ve yer ayırmayı sağlayan onlar. Responsive görsel seçimi (srcset, sizes, <picture>) markup tarafında, HTML5 serisinin 08. yazısında.

Sık Düşülen Tuzaklar

Birincisi, görselin altındaki boşluğu margin ya da negatif değerlerle kapatmaya çalışmak. Boşluğun sebebi inline davranış; display: block yeterli.

İkincisi, reset'te max-inline-size: 100%'ü yazıp block-size: auto'yu unutmak. Görsel daralır ama yüksekliği attribute'ta kalır ve oran bozulur.

Üçüncüsü, sprite'taki ya da inline SVG'deki şekillerde fill ve stroke attribute'larını bırakmak. CSS'teki currentColor o şekillere ulaşmaz.

Dördüncüsü, rengini CSS ile değiştirmek istediğin bir SVG'yi img ile eklemek. img içindeki SVG stillenemez; inline SVG, <use> ya da önceki yazıdaki mask yöntemini kullan.

Beşincisi, dark mode için renkli görsellere filter: invert() vermek. Marka renkleri bozulur; light-dark() ya da ayrı bir kaynak kullan.

Baseline Durumu

node scripts/baseline.mjs object-fit object-position image-rendering image-orientation currentcolor light-dark svg
object-fit                 widely 2022-07-15  Chrome 32, Firefox 36, Safari 10, iOS 10
object-position            widely 2022-07-15  Chrome 32, Firefox 36, Safari 10, iOS 10
image-rendering            widely 2024-04-05  Chrome 41, Firefox 93, Safari 10, iOS 10
image-orientation          widely 2022-10-13  Chrome 81, Firefox 26, Safari 13.1, iOS 13.4
currentcolor               widely 2018-01-29  Chrome 1, Firefox 1.5, Safari 4, iOS 3.2
light-dark                 newly 2024-05-13   Chrome 123, Firefox 120, Safari 17.5, iOS 17.5
svg                        widely 2022-07-15  Chrome 1, Firefox 3, Safari 3, iOS 2
ÖzellikDurumNot
object-fit, object-positionWidely availablebackground-position ile aynı syntax
image-rendering: pixelatedWidely available2021-10'da newly, 2024-04'te widely
image-orientationWidely availableVarsayılan from-image
SVG, <use href>, currentColorWidely availableHarici sprite yalnız aynı origin'den
light-dark()Newly available (2024-05)color-scheme gerekir

Bu yazıdaki özelliklerin hiçbiri "Baseline değil" kategorisinde değil. Küçük bir ayrıntı: image-rendering: crisp-edges değeri browser-compat-data'ya göre Chrome'a prefix'siz olarak ancak 148 sürümünde geldi; piksel sanatı ve kodlar için pixelated daha eski ve güvenli seçim.

Küçük Bir Deneme

★☆☆ Lab'deki #inline-img'deki boşluğu display'i değiştirmeden vertical-align ile kaldır ve 09-images.mjs ile kutunun 100 piksele indiğini doğrula.

★★☆ Sprite'a bir yıldız ikonu ekle ve kitap detayındaki puan halkasının yanına koy. İkonun rengi currentColor'dan gelsin; symbol'deki path'e fill attribute'u yazmadan, hover'da renginin değiştiğini 09-pages.mjs'e eklediğin bir satırla ölç.

★★★ Bookstore'a koyu tema ekle (color-scheme: light dark): favori kalbinin, fiyat ikonunun ve sepet ikonunun koyu zeminde okunur kaldığını colorScheme: 'dark' ile açtığın bir sayfada piksel ölçümüyle göster.

Bir sonraki yazıda üretilmiş içeriğe geçiyoruz: ::before ve ::after, content değerleri ve alternatif metni, checkout adımlarını numaralayan counter'lar, @counter-style, ::marker ve list-style.