clip-path, mask ve shape-outside: Element'leri Kırpmak, Maskelemek ve Metni Şeklin Etrafından Akıtmak
clip-path element'in yalnız bir şeklin içini çizer ve hit testing'i de o şekle göre yapar, mask-image bir görselin alfa kanalıyla kısmi görünürlük verir, shape-outside ise float edilmiş bir element'in çevresindeki metni bir şekle göre akıtır; üçü de layout'taki kutuyu değiştirmez. Yazı shape() ile path() farkını, forced colors'da maskeli ikonu ve Bookstore'da yuvarlak yazar fotoğrafı, kenarı solan kapak şeridi ve eğik kitabın çevresinden akan metni ölçümlerle anlatır.
Sürüm notu: Bu yazı CSS Masking Module Level 1, CSS Shapes Level 1 ve Level 2 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çı) için kitap detay sayfasına üç şey ekliyoruz: yuvarlak bir yazar fotoğrafı, yana kaydırılan bir "Recommended" şeridi ve eğik duran bir kitap görselinin kenarını takip eden bir tanıtım metni. Üçü de element'in bir kısmını göstermemek ya da metni bir şeklin çevresinden akıtmak üzerine. Önce bir soru:
/* 1) basic shape'ler */
#inset { clip-path: inset(10px round 8px); }
#circle { clip-path: circle(40px at 50% 50%); }
#polygon { clip-path: polygon(50% 0, 100% 100%, 0 100%); }
/* box-shadow da kırpılır */
#shadowed { clip-path: inset(0 round 8px); box-shadow: 0 0 0 6px #b91c1c; }
200×100'lük mavi kutuları bu şekillerle kırptık. Kutunun kırpılan sol üst köşesine tıklarsan tıklama kime gider: kutuya mı, arkasındaki element'e mi? Ve kutunun layout'taki boyutu küçülür mü?
clip-path'in Basic Shape'leri
clip-path, element'in yalnız bir şeklin içinde kalan kısmını çizer. Basic shape'ler dört tane: inset() (kenarlardan içeri çekilmiş, istenirse köşesi yuvarlak bir dikdörtgen), circle(), ellipse() ve noktaları virgülle sıralanan polygon(). Yüzdeler element'in kutusuna göre hesaplanır. Lab'deki ölçüm:
node scripts/08-clip-mask.mjs
inset box 200x100 corner pixel rgb(255, 255, 255) click at corner hits SECTION, at center hits inset
circle box 200x100 corner pixel rgb(255, 255, 255) click at corner hits SECTION, at center hits circle
polygon box 200x100 corner pixel rgb(255, 255, 255) click at corner hits SECTION, at center hits polygon
shadowed 3px outside the box: rgb(255, 255, 255)
Üç kutu da 200×100 kalıyor: clip-path layout'u değiştirmez, kırpılan kısım yer kaplamaya devam eder. Köşe pikselleri beyaz, yani kırpılan kısım çizilmiyor. Giriş sorusunun cevabı da burada: köşeye yapılan tıklama kutuya değil arkasındaki section'a gidiyor, ortaya yapılan tıklama kutuya. Tarayıcı hit testing'i (bir noktanın hangi element'e denk geldiğini bulmak) kırpılmış şekle göre yapar; daire biçimli bir butonun köşesine tıklamak butonu tetiklemez. İki motorda da sonuç aynı.
Son satır bir yan etkiyi gösteriyor: clip-path element'in kendi box-shadow'unu ve outline'ını da keser, çünkü onlar da element'in çizdiği şeylerin parçası. Kutunun 3px dışında olması gereken kırmızı gölge görünmüyor. Kırpılmış bir şekle gölge vermek istiyorsan gölgeyi bir üst element'e filter: drop-shadow() olarak ver; bir önceki yazıda gördüğümüz gibi drop-shadow, içindeki şeklin piksellerine göre çizilir.
path() ve shape(): Sabit Piksel mi, Yüzde mi?
Basic shape'lerin yetmediği eğriler için iki function var. path(), SVG'nin (vektörel görsel formatı) path syntax'ını alır ve yıllardır clip-path'te çalışıyor. shape() ise 2026'da Baseline oldu ve aynı komutları CSS syntax'ıyla, yüzde ve calc() kullanarak yazdırır. Alt kenarı dalgalı bir şekli ikisiyle de yazdım ve iki farklı genişlikte ölçtüm:
/* 2) path() sabit piksel kullanır, shape() yüzde kullanabilir */
.wave { display: block; inline-size: 200px; block-size: 100px; margin-block-end: 8px; background: #15803d; }
.wave.wide { inline-size: 400px; }
.wave.path { clip-path: path("M 0 0 H 200 V 70 Q 100 100 0 70 Z"); }
.wave.shape { clip-path: shape(from 0 0, hline to 100%, vline to 70%, curve to 0 70% with 50% 100%, close); }
--- chromium
.wave.path width 200 (150, 40): rgb(21, 128, 61) (100, 80): rgb(21, 128, 61)
.wave.path.wide width 400 (300, 40): rgb(255, 255, 255) (200, 80): rgb(255, 255, 255)
.wave.shape width 200 (150, 40): rgb(21, 128, 61) (100, 80): rgb(21, 128, 61)
.wave.shape.wide width 400 (300, 40): rgb(21, 128, 61) (200, 80): rgb(21, 128, 61)
--- webkit
.wave.path width 200 (150, 40): rgb(21, 128, 61) (100, 80): rgb(21, 128, 61)
.wave.path.wide width 400 (300, 40): rgb(255, 255, 255) (200, 80): rgb(255, 255, 255)
.wave.shape width 200 (150, 40): rgb(21, 128, 61) (100, 80): rgb(21, 128, 61)
.wave.shape.wide width 400 (300, 40): rgb(21, 128, 61) (200, 80): rgb(21, 128, 61)
200px'lik kutuda iki yöntem de aynı: (150, 40) ve dalganın en alçak noktasına yakın (100, 80) boyanmış. 400px'lik kutuda fark ortaya çıkıyor. path()'in koordinatları piksel ve sabit; şekil hâlâ 200px genişliğinde kaldığı için (300, 40) ve (200, 80) boş. shape() yüzdelerle yazıldığı için kutuyla birlikte uzuyor ve iki nokta da boyanmış. Responsive bir component'te kırpma şeklinin kutuyla büyümesi gerekiyorsa shape() kullan; desteklemeyen eski tarayıcılar için önce bir polygon() ya da inset() yazıp ardından shape()'li satırı eklemek yeterli bir fallback olur.
Baseline tablosunda bir ayrıntı göreceksin: web-features, path()'i "Baseline değil" gösteriyor. Sebep clip-path değil; aynı kayıtta SVG'nin d property'si de var ve Safari onu desteklemiyor. browser-compat-data'ya göre clip-path: path() Safari 13.1'den beri prefix'siz çalışıyor ve kendi başına 2021'den beri widely available.
Yazar Fotoğrafı: circle()
Bookstore'da yazar fotoğrafını daire içinde gösteriyoruz:
<section class="author-card">
<img class="author-photo" src="img/author-eliot.webp" alt="Portrait of George Eliot" width="240" height="360" loading="lazy">
<p>George Eliot was the pen name of Mary Ann Evans (1819-1880), one of the leading English novelists of her time.</p>
</section>
.author-card { display: flex; gap: 1rem; align-items: center; margin-block: 1.5rem; }
.author-photo {
flex: none;
inline-size: 6rem;
block-size: 6rem;
object-fit: cover;
/* burada border-radius: 50% ile aynı sonuç, ama outline ve gölgeleri de keser */
clip-path: circle(50%);
}
node scripts/08-pages.mjs
--- chromium
author photo: 96x96, clip-path circle(50%)
corner (3, 3): rgb(249, 250, 251) center: rgb(50, 65, 85)
strip: scrollWidth 1104, clientWidth 720
mask-image linear-gradient(to right, rgba(0, 0, 0, 0), rgb(0, 0, 0) 32px, rgb(0, 0, 0) calc(100% - 32px), rgba(0, 0, 0, 0))
pixel 6px from the right edge: rgb(239, 228, 215) 60px from the edge: rgb(193, 120, 40)
edition text line starts: 136, 144, 152, 161, 169, 174
--- webkit
author photo: 96x96, clip-path circle(50%)
corner (3, 3): rgb(249, 250, 251) center: rgb(50, 65, 85)
strip: scrollWidth 1104, clientWidth 720
mask-image linear-gradient(to right, rgba(0, 0, 0, 0), rgb(0, 0, 0) 32px, rgb(0, 0, 0) calc(100% - 32px), rgba(0, 0, 0, 0))
pixel 6px from the right edge: rgb(239, 228, 216) 60px from the edge: rgb(193, 120, 40)
edition text line starts: 136, 145, 153, 161, 169, 174
İlk iki satır fotoğraf için: 96×96'lık kutunun köşe pikseli sayfa arka planıyla aynı (249, 250, 251), ortası fotoğrafın kendisi. Burada clip-path: circle(50%) ile border-radius: 50% aynı görüntüyü verir. Fark ayrıntıda: border-radius'lu bir görsele outline ve gölge verebilirsin ve ikisi de daireyi takip eder; clip-path ikisini de keser. Buna karşılık clip-path daireden başka şekiller alabilir ve aynı türden iki shape arasında (ör. circle(0)'dan circle(75%)'e) animasyonla geçilebilir. Fotoğraf 240×360'lık dikey bir görsel; object-fit: cover onu kare kutuya ortadan kırparak sığdırıyor (property'nin kendisi sitedeki CSS serisinin (CSS3: Flexbox, Grid ve Modern CSS) 08. yazısında) ve yazarın başının üstü biraz kesiliyor. Kırpmanın odak noktasını seçmek bu serinin bir sonraki yazısının konusu.
mask-image ile Kenarları Soldurmak
clip-path bir şeklin içini tamamen gösterir, dışını tamamen gizler. mask-image ise bir görselin alfa kanalını kullanır: maskenin opak olduğu yer görünür, şeffaf olduğu yer görünmez, aradaki her değer kısmen görünür. Bu yüzden gradient'le bir element'in kenarını yumuşakça soldurmak mümkün:
/* 3) gradient maske kenarları soldurur */
.strip { inline-size: 300px; block-size: 40px; background: #1d4ed8;
mask-image: linear-gradient(to right, transparent, #000000 40px, #000000 calc(100% - 40px), transparent); }
strip mask x=1: rgb(246, 248, 253) x=20: rgb(139, 164, 235) x=150: rgb(29, 78, 216)
Şeridin rengi #1d4ed8 (29, 78, 216). En solda maske neredeyse tamamen şeffaf ve piksel beyaza çok yakın (246, 248, 253); 20px içeride maske yarı opak ve renk mavi ile beyazın ortasında (139, 164, 235); ortada maske tam opak ve renk olduğu gibi. Gradient'teki transparent ve #000000'ın rengi önemsiz, yalnız alfa değerleri sayılır.
Bookstore'un kitap detayındaki "Recommended" şeridi yana kaydırılan bir kapak listesi. Kenarlardaki solma, kullanıcıya satırın devam ettiğini anlatıyor:
.book-strip {
display: flex;
gap: 1rem;
margin: 0;
/* ilk ve son kapak solan kenarların dışında başlar */
padding: 0 2rem;
list-style: none;
overflow-x: auto;
/* solan kenarlar: satırın yana kaydığına dair ipucu */
mask-image: linear-gradient(to right, transparent, #000000 2rem, #000000 calc(100% - 2rem), transparent);
}
.book-strip img { display: block; inline-size: 10rem; block-size: auto; border-radius: 0.25rem; }
Sayfa ölçümünde şeridin scrollWidth'i 1104, görünen genişliği 720; yani yaklaşık 380px'lik içerik sağda gizli. Sağ kenara 6px uzaklıktaki piksel, kesilen Dune kapağının turuncusunun (193, 120, 40) neredeyse arka plana karışmış hâli (239, 228, 215). padding ile ilk kapak solan bölgenin dışında başlıyor; kaydırma başlangıcındayken ilk kapak tam görünüyor. Kaydırmayı JavaScript'siz ve kapaklara oturacak biçimde yapmak (scroll snap) bu serinin overflow ve scroll yazısında.
Eski kodlarda -webkit-mask-image çok sık görülür. Prefix'siz mask-image Chrome'a 120 sürümünde, Aralık 2023'te geldi ve Baseline'a o zaman girdi; Haziran 2026'dan beri widely available. Yalnız prefix'li yazılmış eski bir kod çalışmaya devam eder, ama prefix'siz satırı da ekle; yeni maske özellikleri (mask-composite'in standart değerleri gibi) prefix'siz property'lere geliyor.
SVG İkon Maskesi ve Forced Colors
mask shorthand'inin yaygın bir kullanımı, tek renkli bir SVG ikonunu her renge boyanabilir hâle getirmek: element'in arka planını currentColor yaparsın, ikon dosyası maske olur ve ikon metnin rengini alır.
<svg xmlns="http://www.w3.org/2000/svg" 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"/></svg>
/* maske olarak SVG ikon: renk currentColor'dan gelir */
.icon { display: inline-block; inline-size: 48px; block-size: 48px; background-color: currentColor;
mask: url("../img/heart.svg") center / contain no-repeat; }
#icon-red { color: #b91c1c; }
#icon-fixed { color: #b91c1c; }
@media (forced-colors: active) {
#icon-fixed { background-color: CanvasText; }
}
--- chromium, forced colors none
icon-red background-color rgb(185, 28, 28), center pixel rgb(185, 28, 28)
icon-fixed background-color rgb(185, 28, 28), center pixel rgb(185, 28, 28)
--- chromium, forced colors active
icon-red background-color rgb(255, 255, 255), center pixel rgb(255, 255, 255)
icon-fixed background-color rgb(0, 0, 0), center pixel rgb(0, 0, 0)
Normal modda iki kalp de kırmızı. Forced colors modunda (Windows'un yüksek contrast teması gibi) ise tarayıcı background-color'ı sayfanın arka plan rengine çevirir; burada beyaz. Arka planı beyaz olan bir ikon beyaz sayfada kaybolur: #icon-red'in ortası beyaz. @media (forced-colors: active) içinde arka planı CanvasText sistem rengine çeviren #icon-fixed ise siyah ve görünür. İkon yalnız süs değil de bir butonun tek içeriğiyse bu satır şart.
Bu ölçümü ayrı ve küçük bir lab sayfasında yaptım. İkonları büyük lab sayfasında bıraktığımda, Chromium computed değeri beyaz gösterdiği hâlde ikonu eski kırmızısıyla çizdi; Playwright'ın forced colors emülasyonu her sayfada gerçek modu birebir yansıtmıyor. Kullanıcıların gerçek forced colors ayarıyla bir kez elle denemek bu yüzden hâlâ gerekli.
shape-outside: Metni Kitabın Çevresinden Akıtmak
Float edilmiş bir element'in çevresinden akan metin normalde element'in dikdörtgen kutusunu takip eder. shape-outside bu sınırı bir şekle çevirir: basic shape'ler (circle(), polygon()…) ya da bir görselin opak pikselleri. Şeffaf arka planlı, eğik duran bir kitap görselini float ettim ve paragrafın ilk satırlarının nereden başladığını ölçtüm:
/* 4) metin görselin opak piksellerinin çevresinden akar */
.flow { inline-size: 420px; }
.flow img { float: left; inline-size: 180px; block-size: 180px; }
#shaped img { shape-outside: url("../img/book-tilted.png"); shape-margin: 8px; }
--- chromium
boxed line starts: 180, 180, 180, 180, 180, 180, 180, 180
shaped line starts: 131, 139, 147, 155, 162, 170, 169, 109
--- webkit
boxed line starts: 180, 180, 180, 180, 180, 180, 180, 180
shaped line starts: 131, 140, 147, 155, 163, 170, 169, 109
Şekilsiz float'ta her satır 180. pikselden, yani görselin kutusunun sağından başlıyor. shape-outside: url(...) ile satırlar 131'den başlayıp aşağı indikçe sağa kayıyor (139, 147, 155…) ve eğik kitabın kenarını 8px'lik shape-margin ile takip ediyor; kitabın alt köşesini geçince 109'a geri çekiliyor. Görselin hangi alfa değerinden itibaren şekle sayılacağını shape-image-threshold belirler (varsayılan 0, yani tamamen şeffaf olmayan her piksel). Bookstore'daki "About this edition" bölümü aynı tekniği kullanıyor:
.edition-cover {
float: left;
inline-size: 180px;
block-size: auto;
/* metin kare görsel kutusunu değil, kitabın kenarını takip eder */
shape-outside: url("../../img/book-tilted.png");
shape-margin: 0.75rem;
}
.edition { display: flow-root; }
Sayfa ölçümünün son satırı: tanıtım metninin altı satırı 136'dan 174'e doğru kitabın kenarını takip ediyor. display: flow-root, bölümün float'ı içine alıp bir sonraki bölümün kitabın yanına kaymasını önlüyor (block formatting context'in kendisi CSS3 serisinin 04. yazısında).
shape-outside: url()'in bir koşulu var: görsel CORS (Cross-Origin Resource Sharing; başka bir origin'den gelen kaynağı okumaya izin veren HTTP mekanizması) açısından okunabilir olmalı. Sayfayla aynı origin'den gelen görselde sorun yok; görsel bir CDN'den (içerik dağıtım ağı) geliyorsa ve CORS header'ı yoksa tarayıcı şekli yok sayar ve metin yine dikdörtgeni takip eder. Hata vermez, yalnız sessizce çalışmaz.
Üretimde Dikkat
clip-path ve mask, filter gibi element'i yeni bir stacking context yapar (CSS3 serisinin 04. yazısı). Kırpılmış bir kartın içinden dışarı taşması gereken bir dropdown ya da tooltip kesilir; böyle bir içerik varsa kırpmayı yalnız görsele ver.
Maske ve kırpma, layout'u değiştirmediği için kırpılan alan hâlâ yer kaplar ve görünmeyen içerik klavyeyle odaklanabilir, screen reader tarafından okunabilir kalır. Bir içeriği "gizlemek" için clip-path kullanmak, görsel olarak gizleyip erişilebilir bırakmak anlamına gelir; bu bazen istenen şeydir (yalnız screen reader'ın okuduğu metinler), bazen değildir.
Maske görselleri ayrı bir istek demek. İkon maskeleri için tek bir SVG sprite ya da inline SVG çoğu zaman daha az istek ve daha kolay stil verir; inline SVG ikonlarına bu serinin bir sonraki yazısında bakacağız.
Sık Düşülen Tuzaklar
Birincisi, eski -webkit-mask ve -webkit-mask-image kodunu prefix'siz karşılığı olmadan bırakmak. Bugün çalışıyor, ama prefix'siz property'ler standart ve yeni özellikler oraya geliyor.
İkincisi, clip-path ile kırpılan kısmın yer kaplamaya devam ettiğini unutmak. Kırpılmış bir görselin yanındaki boşluk, kutunun hâlâ eski boyutunda olmasından gelir.
Üçüncüsü, background-color ile boyanmış maskeli bir ikonun forced colors modunda kaybolması. @media (forced-colors: active) içinde CanvasText gibi bir sistem rengi ver.
Dördüncüsü, clip-path'li bir element'e gölge ya da outline verip görünmemesine şaşırmak. İkisi de kırpılır; gölgeyi üst element'e drop-shadow() ile ver, outline için border-radius kullan.
Beşincisi, shape-outside: url() için başka bir origin'den CORS header'ı olmayan bir görsel kullanmak. Şekil sessizce yok sayılır.
Baseline Durumu
node scripts/baseline.mjs clip-path shape-function path-shape masks shape-outside
clip-path widely 2023-07-21 Chrome 88, Firefox 71, Safari 13.1, iOS 13
shape-function newly 2026-02-24 Chrome 135, Firefox 148, Safari 18.4, iOS 18.4
path-shape not Baseline Chrome 88, Firefox 97, Safari -, iOS -
masks widely 2026-06-07 Chrome 120, Firefox 53, Safari 15.4, iOS 15.4
shape-outside widely 2022-07-15 Chrome 37, Firefox 62, Safari 10.1, iOS 10.3
| Özellik | Durum | Not |
|---|---|---|
clip-path (basic shape'ler) | Widely available | Hit testing kırpılmış şekle göre yapılır |
shape() | Newly available (2026-02) | Yüzde ve calc() ile responsive şekil |
path() | Baseline değil (web-features) | clip-path: path() tek başına 2021'den beri widely; Safari'de eksik olan SVG d property'si |
mask, mask-image | Widely available | Prefix'siz 2023-12'de newly, 2026-06'da widely |
shape-outside, shape-margin | Widely available | url() için CORS gerekir |
Küçük Bir Deneme
★☆☆ Lab'deki #polygon'u köşeleri kesik bir sekizgene çevir ve 08-clip-mask.mjs ile sol üst köşeye yapılan tıklamanın hâlâ section'a gittiğini, ortadakinin kutuya gittiğini doğrula.
★★☆ "Recommended" şeridine yalnız sağ kenarı soldurup sol kenarı keskin bırakan bir maske yaz; 08-pages.mjs'e şeridin sol kenarından 6px içerideki pikseli ölçen bir satır ekle.
★★★ Kitap detayındaki yazar fotoğrafını, hover'da circle(45%)'ten circle(50%)'e büyüyen bir kırpmayla göster (geçiş prefers-reduced-motion'a uysun) ve fotoğrafa clip-path'i bozmadan görünür bir gölge ekle.
Bir sonraki yazıda görselleri ve SVG ikonlarını stilliyoruz: img'nin inline davranışı ve reset'i, yazar fotoğrafındaki kesilen başı düzelten object-position, image-rendering, inline SVG'de fill, stroke ve currentColor, ve tek bir sprite'tan gelen katalog ikonları.