Bu fark, tarayıcının sayfayı ekranda oluşturma sürecinin üç aşamaya bölünmesinden kaynaklanır: layout hesabı, paint işlemi ve composite. Her CSS değişikliği bu üç aşamanın tamamını tetiklemez. Bazı özellikler pipeline'ı başından başlatır ve tüm sayfayı yeniden hesaplamaya zorlar; bazıları yalnızca boyama adımında kalır; bazıları ise doğrudan GPU'ya devredilir ve main thread'i hiç meşgul etmez. Reflow, pipeline'ın en başından başlayan ve en geniş alana yayılan değişiklikleri tanımlar. Repaint ise layout sabit kalırken yalnızca görsel güncelleme gerektiren durumları anlatır.

Hangi CSS özelliğinin hangi aşamayı tetiklediğini bilmek, performans sorunlarını kaynaklarında tanımayı sağlar. Özellikle JavaScript üzerinden yapılan DOM güncellemeleri, farkında olmadan tarayıcıyı art arda layout hesabı yapmaya zorlayabilir — bu durum, ölçümlerde belirgin ama kaynağı anlaşılması güç gecikmelere yol açar.

Rendering pipeline'ın üç katmanı: layout, paint ve composite

Tarayıcı bir HTML belgesi yüklediğinde önce DOM ve CSSOM ağaçlarını oluşturur, ardından bu ikisini birleştirerek render ağacını üretir. Render ağacı tamamlandığında layout aşaması başlar: her öğenin ekrandaki boyutu, konumu ve çevre öğelerle geometrik ilişkisi bu adımda hesaplanır. Piksel koordinatları burada belirlenir; bu aşamanın çıktısı olmadan hiçbir öğenin nerede duracağı bilinemez.

Layout tamamlandıktan sonra paint aşaması gelir. Paint, her öğenin görsel içeriğini çizmek için gerekli komutları üretir: renk, kenarlık, gölge, metin. Bu aşama piksel konumlarını değil, stil bilgilerini katmanlara dönüştürür. Son adım composite'tir: paint aşamasında üretilen katmanlar GPU'ya gönderilir ve ekranda birleştirilir. Kullanıcının gördüğü final görüntü bu adımda oluşur.

Bir CSS değişikliği bu zinciri nereden başlatıyor? Layout'u etkileyen bir değişiklik her şeyi baştan tetikler: layout → paint → composite. Yalnızca görsel bir özellik değişiyorsa paint → composite. Yalnızca katman dönüşümü söz konusuysa composite tek başına yeterlidir. Bu üç senaryo arasındaki maliyet farkı, küçük sayfalarda fark edilmeyebilir; ama yüzlerce bileşenden oluşan büyük uygulamalarda onlarca milisaniyeye ulaşabilir ve etkileşim kalitesini doğrudan bozar.

Reflow'u tetikleyen özellikler ve maliyet boyutu

Reflow, layout aşamasının yeniden çalışmasını gerektirir. Bu aşama yeniden çalıştığında tarayıcının boyutları ve konumları yeniden hesaplaması gerekir — yalnızca değişen öğe için değil, onunla aynı akış bağlamını paylaşan tüm öğeler için. Büyük ve iç içe geçmiş DOM ağaçlarında bu hesap kapsamı hızla genişler; bir öğedeki değişiklik parent container'ını, container'ın komşularını ve komşuların alt öğelerini zincirleme etkiler.

Reflow tetikleyen CSS özelliklerinin ortak özelliği, öğenin boyutunu veya konumunu etkilemesidir: width, height, margin, padding, border-width, top, left, font-size, line-height, display, position, flex, grid. Bu özelliklerde yapılan herhangi bir değişiklik layout hesabını başlatır. Bunların yanı sıra içeriğin kendisi de reflow'a yol açar: bir paragrafa metin eklenmesi o paragrafın yüksekliğini değiştirebilir ve bu değişiklik yukarı doğru yayılarak üst container'ları da yeniden hesaplamaya zorlar.

Reflow maliyetinin değişkenliği DOM karmaşıklığına bağlıdır. Yalnızca birkaç öğeden oluşan basit bir sayfada bir reflow 1–2 ms sürebilir ve fark edilmez. Yüzlerce iç içe öğe ve karmaşık grid düzeni içeren büyük bir uygulamada aynı tetikleyici 20–30 ms'ye veya daha uzuna ulaşabilir. INP bağlamında bu süre doğrudan kullanıcının hissettiği gecikmeye dönüşür: bir düğmeye tıklandığında tarayıcı layout hesabını bitirene kadar etkileşim askıda kalır ve bu bekleme, etkileşim skorunu olumsuz etkiler.

Repaint: layout sabit, yalnızca görsel güncelleme

Bazı CSS değişiklikleri öğenin boyutunu veya konumunu hiç etkilemez. Bu durumda layout aşaması yeniden çalışmaz; tarayıcı doğrudan paint adımına geçer. Buna repaint denir ve reflow'a kıyasla çok daha dar bir hesap bölgesini kapsar.

background-color, color, border-color, outline, box-shadow, visibility gibi özellikler repaint tetikler. Öğe aynı alanda kalmaya devam eder; yalnızca o alanın görünümü değişir. Komşu öğeler etkilenmez, parent ve sibling'ların yeniden hesaplanmasına gerek yoktur. Hesap, yalnızca değişen öğenin ve etkilediği paint katmanlarının boyama komutlarıyla sınırlı kalır.

Repaint reflow'dan önemli ölçüde daha ucuzdur. Ancak "ucuz" ifadesini görece anlamda değerlendirmek gerekir: çok sık ve geniş bir yüzey alanında gerçekleşen repaint de ölçülebilir yük oluşturabilir. box-shadow gibi hesaplama gerektiren görsel özellikler ya da büyük bir alana sahip öğelerdeki renk değişiklikleri, DevTools'ta "Paint" görevleri olarak görünür ve bazen beklenenden uzun sürer. CLS metriği açısından ise repaint bir sayfa kayması üretmez — öğe aynı alanda kalır — bu yüzden layout değişiklikleri, saf görsel değişikliklerden çok daha kritik bir izleme önceliği taşır.

Composite aşaması ve GPU'ya devredilen dönüşümler

Reflow ve repaint main thread üzerinde çalışır. Composite aşaması ise farklıdır: bu aşama GPU tarafından yönetilir ve çoğunlukla main thread'i meşgul etmez. Bu yapısal fark, composite'e dayanan animasyonların neden reflow veya repaint içeren animasyonlara göre çok daha akıcı çalıştığını açıklar — GPU bağımsız bir birimdir ve JavaScript çalışırken ya da layout hesabı sürerken bile compositing işini sürdürebilir.

Yalnızca composite aşamasını tetikleyen iki temel CSS özelliği vardır: transform ve opacity. Bir öğeyi hareket ettirmek için left veya top yerine transform: translateX() kullanmak, işlemi layout ve paint aşamalarından tamamen çıkarır. Tarayıcı bu öğeyi önceden ayrı bir compositor katmanına almış ve tüm dönüşümü GPU'ya bırakmıştır. Benzer şekilde bir öğeyi yavaşça görünür yapmak için opacity değişikliği kullanmak da compositor düzeyinde gerçekleşir ve main thread'i meşgul etmez.

Bu bilgi, animasyon kararlarını doğrudan etkiler. 60 fps veya 120 fps hedeflenen bir animasyon, her kareyi main thread üzerinde layout ya da paint hesabına zorlamamalıdır. transform ve opacity dışındaki bir özelliği animasyona taşımak, her frame için reflow veya repaint çalışması anlamına gelir ve bu iş yükü frame bütçesini aşabilir. Main thread blocking bağlamında bu durum özellikle düşük güçlü cihazlarda görünür hale gelir: akıcı görünen bir animasyon gerçekte sürekli long task üretiyorsa bu, ölçümde TBT ve INP üzerinde iz bırakır.

Layout thrashing: JavaScript'in tetiklediği zorunlu senkron layout

JavaScript üzerinden DOM güncellemesi yapılırken en sık karşılaşılan reflow tuzağı layout thrashing olarak bilinir. Bu durum, JavaScript'in layout bilgisi okuma ve yazma işlemlerini birbirine karıştırması sonucu ortaya çıkar ve beklenmedik biçimde yüksek layout maliyetlerine neden olur.

Tarayıcı, JavaScript yürütme tamamlandıktan sonra bekleyen layout hesabını toplu biçimde yapar — bu normal davranışıdır. Ancak JavaScript kodu, henüz tamamlanmamış layout hesabının sonuçlarını okumaya çalışırsa tarayıcı o hesabı hemen, senkron olarak yapmak zorunda kalır. offsetHeight, offsetWidth, getBoundingClientRect(), scrollTop gibi özelliklerin okunması layout bilgisi gerektirdiğinden, bu okumadan önce bekleyen bir yazma işlemi varsa tarayıcı önce o yazmayı işler, layout hesabını tamamlar, ardından okuma değerini döndürür. Bu döngü bir döngü içinde tekrarlanırsa her iterasyonda bir forced synchronous layout oluşur.

// Tehlikeli: her iterasyonda okuma + yazma → forced layout
const items = document.querySelectorAll('.item');
items.forEach(item => {
  const height = item.offsetHeight; // layout okuma — önceki yazma nedeniyle layout zorlanır
  item.style.height = (height + 10) + 'px'; // layout yazma
});

// Doğru: önce tüm okumaları yap, sonra tüm yazmaları
const heights = Array.from(items).map(item => item.offsetHeight); // okuma bloğu
items.forEach((item, i) => {
  item.style.height = (heights[i] + 10) + 'px'; // yazma bloğu
});

Layout thrashing, DevTools Performance panelinde "Forced reflow" uyarısı olarak işaretlenir. Uyarı göründüğünde okuma ve yazma sırasını ayırmak — önce tüm geometri okumalarını, ardından tüm DOM yazma işlemlerini toplu biçimde yapmak — genellikle sorunu çözer. requestAnimationFrame ile yazma işlemlerini bir sonraki frame'e taşımak da bu döngüyü kıran yaygın bir yöntemdir; böylece okuma ve yazma işlemleri farklı frame'lere dağıtılır ve her frame'de yalnızca bir layout çalışması gerçekleşir.

DevTools ile reflow ve repaint maliyetini okumak

Tarayıcı, reflow ve repaint maliyetini ölçmek için gerekli araçları sunar. Chrome DevTools'ta Performance paneli kaydedildiğinde "Recalculate Style" ve "Layout" görevleri main thread üzerinde ayrı ayrı görünür. "Recalculate Style" CSS cascade ve stil hesabını; "Layout" boyut ve konum hesabını temsil eder. Bu görevlerin süresi ve sıklığı, sayfanın hangi etkileşimlerde ne kadar reflow yükü ürettiğini gösterir. Bir etkileşim sonrası Layout görevinin 10 ms'nin üzerinde sürdüğü durumlarda DOM izolasyonu veya CSS yapısı gözden geçirilmelidir.

Paint maliyetini izlemek için DevTools'ta "Rendering" sekmesinde "Paint flashing" etkinleştirilebilir. Bu seçenek açıkken yeniden boyanan alanlar yeşil renkle vurgulanır. Beklenenden geniş alanların boyanması, gereğinden büyük bir paint bölgesi olduğuna işaret edebilir; bu noktada contain: paint veya katman izolasyonu değerlendirilebilir.

Composite katmanlarını incelemek için "Layers" paneli kullanılır. Tarayıcının kaç ayrı katman oluşturduğu ve bu katmanların boyutları burada görünür. Aşırı sayıda katman — özellikle will-change veya transform: translateZ(0) eklenmiş fazla öğe — GPU belleğini gereksiz yere meşgul eder ve katman yönetim maliyeti artar. Her öğeyi kendi compositor katmanına almak reflow sorununu çözmez; aksine katman sayısı kontrolsüz artarsa composite aşamasının kendi maliyeti belirgin hale gelir.

CSS değişikliklerinin hangi pipeline aşamasını tetiklediğini bilmek, animasyon tasarımından JavaScript DOM güncellemelerine kadar pek çok kararı doğrudan etkiler. Bir öğeyi hareket ettirmek için left yerine transform kullanmak, görsel sonuç aynı olsa da tarayıcının yaptığı iş tamamen farklıdır. Sayfada gerçek zamanlı veri güncelleme, kullanıcı etkileşimine bağlı DOM değişiklikleri veya scroll bazlı animasyonlar söz konusu olduğunda bu farkın maliyeti birikir ve ölçümlere yansır.

Layout thrashing'in önlenmesi, DOM okuma ve yazma sırasının bilinçli yönetilmesiyle mümkündür. Composite'e dayanan animasyonlar, frame bütçesini korumak için en sağlam tercihdir. Geniş DOM ağaçlarında reflow yayılımını kısıtlamak için layout containment uygulamak, her DOM değişikliğinin ne kadar alana yayıldığını doğrudan sınırlar. Tüm bu kararlar küçük görünür ama düşük TBT, daha iyi INP ve akıcı etkileşimler olarak ölçüm sonuçlarına yansır.