Açık kaynak proje
paintcheck: CSS'in cevabı olmayan yerde kontrast ölçmek
Bir sayfanın ekran görüntüsünü alıp metin ve arka plan renklerini tarayıcının boyadığı piksellerden okuyan komut satırı aracı. CSS tabanlı denetleyiciler sitemin temiz olduğunu söylediği ve öyle olmadığı için var.
- Lisans
- MIT
- Sürüm
- 0.2.0 · 37bbe2a GitHub'da · npm'de yok
- Dil
- JavaScript (Node 20+), Türkçe tanımlayıcılar, İngilizce CLI
- Bağımlılıklar
- puppeteer-core, sharp
- Testler
- 57 test yerelde ve CI'da geçti (Ubuntu, Node 20/22)
Ölçümleri dene
Ekran görüntüsünü ve kayıtlı sonuçları karşılaştırmak için bir test örneği seç. Ölçümler önceden hesaplandı; bu gösterim canlı tarama yapmaz.

Görselin tamamı için yatay kaydır. Ölçüm, kayıtlı tarayıcı görüntüsüne aittir.
Zemin metnin kendi renginden geçiyor. Tek bir CSS cevabı yok, axe "incomplete" der. paintcheck harf piksellerini örnekleyip en kötü noktayı raporlar.
- Referans değer
- 1.1:1
- axe-core 4.13
- belirsiz
- paintcheck
- kaldı 1.10:1
Kayıtlı karşılaştırma sonuçları; referans değerler hesaplanabildiği örneklerde gösterilir. Kaynak: benchmark/SONUC.md, GitHub'daki 37bbe2a revizyonu, 2026-09-15.
axe-core'u tamamlayan, görüntü üzerinden kontrast denetimi. Demo, GitHub'da yayımlanan ve CI'dan geçen 37bbe2a revizyonuna ait. Yöntem, kapsam ve kurulum proje sayfasında.
Kayıtlı demo çalışmasının çıktısı
Depodaki npm run demo çıktısından: iki sentetik sayfa, gerçek site yok. Kahraman paragrafı medyanda geçiyor, en kötü noktada kalıyor; yapışkan bağlantı iki kaydırma konumunun kötüsünde bir kez raporlanıyor. Tüm kırpımlarıyla tam HTML raporu yukarıda bağlantılı.
$ npm run demo
demo/sayfalar/landing.html
! [contrast] body > section.hero > div.kap > p.destek
"We plan, design and ship product interfaces for small teams."
15px — 4.38:1 worst (median 6.57, best 7.8, 453 samples) — needs 4.5:1
rgb(248,236,211) on rgb(168,87,60) — at 383,281 (384x45)
! [contrast] body > header.yapiskan > div.kap > a
"Home"
14px — 2.07:1 — needs 4.5:1
rgb(110,109,113) on rgb(198,155,148) — sticky — worst of 2 scroll positions (38x16)
! [contrast] body > section.bant > div.kap > p
"Trusted by teams who ship on a schedule and would rather not"
16px — 1.66:1 worst (median 2.57, best 4.11, 362 samples) — needs 4.5:1
rgb(154,154,158) on rgb(116,116,120) — at 264,453 (912x24)
· scanned 2 scroll positions
Findings: 15
--output → shots/001-we-plan-design-and-ship-product-interfac.pngKaynaktan kurulum
Bu komutlar sayfada gösterilen revizyonu tam olarak seçer. Node 20+ ve yerel Chrome, Chromium ya da Edge gerekir. npm paketi henüz yayımlanmadı.
git clone https://github.com/hasanyahsitr/paintcheck.git
cd paintcheck
git checkout 37bbe2aa3b4bf2c37d879b020148037b57abfd82
npm ci
npm start -- https://example.comSorun
WCAG metin ile arka planı arasında bir kontrast oranı ister. axe-core gibi araçlar bunu color ve background-color değerlerinden hesaplar. Arka plan fotoğraf, gradyan ya da bunların üstünde yarı saydam bir perdeyse CSS'in verecek tek bir rengi yoktur; axe ögeyi "incomplete" olarak raporlar ve insana bırakır.
Bunu kendi sitemde yaşadım. Kahraman metni, gradyan perde altındaki bir fotoğrafın üstündeydi. Denetleyiciler sorun görmedi. Sonunda ölçtüğümde en kötü noktadaki gerçek oran 4,5:1 gereksinime karşı 3:1 civarındaydı.

scrim.html fixture'ı: çizgili zemin üstünde pseudo-element perde. Metnin neyin üstünde durduğunu anlatan bir CSS değeri yok.Görselin tamamı için yatay kaydır.
Yanlış çıkan iki deneme
İlk sürüm arka planı metnin yanından örnekledi. Gradyanda harfin yanındaki piksel, altındaki pikselden farklı renktir; okumalar iki yönde de kendinden emin biçimde yanlıştı: geçen metne yanlış alarm, kalan metne temiz rapor.
İkinci sürüm glyph kapsamasını, normal görüntü ile metni gizlenmiş görüntü arasında pikselin ne kadar değiştiğinden tahmin etti. Gradyanda bu değişim zeminin koyuluğuyla büyür; tahmin yalnızca en yüksek kontrastlı bölgenin piksellerini seçti ve gerçek en kötü noktası 1,1:1 olan metne 4,47:1 dedi. Bunu gradient.html fixture'ı yakaladı, ben yakalayamadım.

gradient.html fixture'ı: zemin metnin kendi renginden geçiyor; bir noktada kontrast 1:1'e yaklaşıyor, diğer uçta 5:1'i aşıyor.Görselin tamamı için yatay kaydır.
Dayanan yöntem
Aynı görünümün beş ekran görüntüsü: sayfanın kendisi (A), 300 ms sonra hâlâ hareket edeni elemek için aynısı (A2), tüm metin saydam (B), tüm metin siyah (C), tüm metin beyaz (D).
C ve D her pikselin kapsamasını arka plandan ve metnin gerçek renginden bağımsız, tam olarak verir: α oranında örtülen piksel C'de α·0 + (1−α)·bg, D'de α·255 + (1−α)·bg okunur, yani α = (D − C)/255. Ögenin en büyük α'sına yakın pikseller harfin çekirdeğidir; kenar yumuşatma pikselleri karışım olduğu için atılır.
Sonra ön plan A'dan, arka plan B'den aynı piksellerden okunur. CSS'ten hiçbir şey hesaplanmaz. Ebeveynde opacity: .5, çocukta .8, bir rgba renk, bir pseudo-element perde: Chrome hepsini boyarken uyguladı, ölçülen sonuç bu.
Harf gövdesi bir pikselden inceyse hiçbir piksel tam kapanmaz, en yoğun olan bile karışımdır. α bilindiği için karışım geri alınır: E = B + (A − B)/α. Bu adım olmadan gerçekte 5,85:1 olan 12 px altbilgi bağlantıları 3,81:1 çıkıyordu.

nested-opacity.html fixture'ı: yalnızca çocuğun opacity: .8 değerini okuyan araç 12,6:1 görür. Pikseller 2,85:1 diyor, çünkü ebeveyn de .5'te.Görselin tamamı için yatay kaydır.
Testlerin zorladığı iki düzeltme daha
Windows'ta Chrome'un alt piksel yumuşatması her renk kanalına farklı kapsama verir; tam kapalı sanılan pikseller hâlâ karışımdı ve her okuma yaklaşık yarım puan düşük çıkıyordu: hesaplanan 4,48'e karşı ölçülen 3,92. Chrome artık gri tonlamalı yumuşatmayla açılıyor.
Antialiasing düzeltmesi başta kapsama haritasından hem ögenin opaklığını hem rengin kendi alfasını bölüyordu. Siyah ve beyaz sondalar rengi değiştirir ama opaklığı değil; bölünmesi gereken tek şey opaklık. Alfayı da bölmek düzeltmeyi ince rgba metinde tamamen atlatıyordu. Yazarın koyduğu saydamlık sonuçta kalır, çünkü okunurluğu gerçekten düşürür ve WCAG onu sayar.

thin-text.html fixture'ı: geçmesi gereken rgba .68 satır ve 3,32:1'de kalması gereken rgba .5 satır; ne fazla ne eksik düzeltme kaçamaz.Görselin tamamı için yatay kaydır.
Tam sayfa ve yapışkan başlıklar
Varsayılan olarak yalnızca ilk görünüm ölçülür. --full-page ile belge bindirmeli adımlarla kaydırılır; aksi hâlde adım sınırına denk gelen öge iki adımda da kırpılır ve hiç ölçülmezdi.
Yapışkan ve sabit ögeler her kaydırma konumunda ölçülür ve en kötüsünde bir kez raporlanır. Benchmark'ta axe-core fixture başlığını başladığı yerde 4,54:1 ile geçirir; üç ekran aşağıda, gri bandın üstünde 1,32:1 ölçülür.

sticky-header.html gri banda kaydırılmış. Başlığın CSS'i başladığı yerdekiyle aynı.Görselin tamamı için yatay kaydır.
Doğrulama
Kayıtlı çalışmada 37bbe2a revizyonunun 57 testi Windows 11 ve Chrome 152 ile yerelde geçti. GitHub CI (Ubuntu, Node 20 ve 22) aynı revizyon için 16 Eylül 2026'da geçti. Düz renkler hesaplanan oranlarla, gradyan ve fotoğraflar aralıklarla karşılaştırılıyor. Yapışkan başlığın en kötü konumda bir kez raporlanması ve uzun sayfanın --full-page ile taranması da denetleniyor.
Bir benchmark betiği axe-core'un color-contrast kuralını ve paintcheck'i aynı fixture'larda çalıştırıp ikisini de doğru cevapla karşılaştırır. Zemin düzken ikisi 0,02 içinde uyuşur. Değilken axe "incomplete" der, paintcheck sayı verir. Tablo ve üretildiği ortam depoda kayıtlı.
37bbe2a revizyonu Windows'ta (yerel) ve Ubuntu'da (CI) çalıştı, macOS'ta değil. Yazı tiplerinin ekrana çizilmesi ölçülen oranları değiştirebilir; karşılaştırma kaydında makineler arası yaklaşık ±0,1 fark not edilmiş, eşiğe yakın sonuçlara yine elle bakmak gerekir.
Bilinen sınırlar
- axe-core ya da Lighthouse'un yerine geçmez; onların açık bıraktığı kontrast durumlarını ve birkaç yapısal kontrolü kapsar.
- Uygunluk belgesi değildir. Otomatik denetim WCAG'ın küçük bir kısmını yakalar.
- Düz zeminde CSS aritmetiği kesindir ve ölçümden iyidir; paintcheck yalnızca aritmetiğin elinde veri olmayan yerde işe yarar.
- Statik kare: hover durumları, açık menüler ve karusel kareleri ölçülmez.
- Yazı tiplerinin ekrana çizilmesi ölçülen oranları değiştirebilir. Eşiğe yakın sonuçları elle incelemek gerekir.
Durum
- MIT lisansı. Sürüm 0.2.0, npm'de yayımlanmadı.
- Bu sayfa 16 Eylül 2026'da GitHub'a gönderilen 37bbe2a revizyonunu anlatıyor. npm paketi henüz yok; kurulum depodan.
- 57 test yerelde Windows 11 ve Chrome 152 ile, aynı revizyon için GitHub CI'da Ubuntu, Node 20 ve 22 ile geçti. macOS kapsanmıyor.