Cascade'de CSS Özel Özellikleri
Yayınlanan: 2022-03-10Geçen ay Twitter'da "kapsamlı" stiller (bir derleme sürecinde oluşturulan) ile CSS'ye özgü "iç içe" stiller arasındaki fark hakkında bir konuşma yaptım. Geliştiricilerin neden JavaScript tarafından oluşturulan "kapsamlı stilleri" benimserken kimlik seçicilerinin özgünlüğünden kaçındıklarını sordum. Keith Grant, farkın kademeli* ve kalıtımın dengelenmesinde, yani özgüllüğe göre yakınlığa öncelik verilmesinde yattığını öne sürdü. Hadi bir bakalım.
Çağlayan
CSS kademesi üç faktöre dayanır:
-
!important
bayrağı ve stil kaynağı tarafından tanımlanan önem (kullanıcı > yazar > tarayıcı) - Kullanılan seçicilerin özgünlüğü (inline > ID > class > element)
- Kodun kendisinin Kaynak Sırası (en sonuncusu öncelik taşır)
Yakınlıktan hiçbir yerde bahsedilmez - bir seçicinin parçaları arasındaki DOM-ağacı ilişkisi. İkinci paragraf için #inner p
, #outer p
daha yakın bir ilişki tanımlasa da, aşağıdaki paragrafların her ikisi de kırmızı olacaktır:
<section> <p>This text is red</p> <div> <p>This text is also red!</p> </div> </section>
#inner p { color: green; } #outer p { color: red; }
Her iki seçici de aynı özgüllüğe sahiptir, ikisi de aynı p
öğesini tanımlar ve hiçbiri !important
olarak işaretlenmez - bu nedenle sonuç yalnızca kaynak sırasına dayanır.
BEM ve Kapsamlı Stiller
BEM ("Block__Element—Değiştirici") gibi adlandırma kuralları, her paragrafın yalnızca bir üst öğeye "kapsamlı" olmasını sağlamak için kullanılır ve basamaklamadan tamamen kaçınılır. Paragraf "öğelerine", "blok" bağlamlarına özgü benzersiz sınıflar verilir:
<section class="outer"> <p class="outer__p">This text is red</p> <div class="inner"> <p class="inner__p">This text is green!</p> </div> </section>
.inner__p { color: green; } .outer__p { color: red; }
Bu seçiciler hala aynı göreceli öneme, özgünlüğe ve kaynak sırasına sahiptir - ancak sonuçlar farklıdır. "Kapsamlı" veya "modüler" CSS araçları, HTML'ye dayalı olarak bizim için CSS'mizi yeniden yazarak bu süreci otomatikleştirir. Aşağıdaki kodda, her paragraf doğrudan ebeveynine göre kapsamlandırılmıştır:
<section outer-scope> <p outer-scope>This text is red</p> <div outer-scope inner-scope> <p inner-scope>This text is green!</p> </div> </section>
p[inner-scope] { color: green } p[outer-scope] { color: red; }
Miras
Yakınlık, kaskadın bir parçası değildir, ancak CSS'nin bir parçasıdır. İşte burada mirasın önemi ortaya çıkıyor. Seçicilerimizden p
çıkarırsak, her paragraf en yakın atasından bir renk alır:
#inner { color: green; } #outer { color: red; }
#inner
ve #outer
farklı öğeleri tanımladığından, sırasıyla div
ve section
, her iki renk özelliği de çakışma olmadan uygulanır. İç içe p
öğesinin belirtilmiş bir rengi yoktur, bu nedenle sonuçlar kademeli yerine kalıtımla (doğrudan ebeveynin rengi) belirlenir. Yakınlık önceliklidir ve #inner
değeri #outer
değerini geçersiz kılar.
Ancak bir sorun var: Kalıtımı kullanmak için section
ve div
içindeki her şeyi şekillendiriyoruz. Paragraf rengini özellikle hedeflemek istiyoruz.
(Yeniden-)Özel Özelliklerin Tanıtımı
Özel özellikler, tarayıcıda yerel yeni bir çözüm sağlar; diğer özellikler gibi miras alırlar, ancak tanımlandıkları yerde kullanılmaları gerekmez . Herhangi bir adlandırma kuralı veya oluşturma aracı olmadan düz CSS kullanarak, yakınlığın kademeli olarak öncelikli olduğu hem hedeflenen hem de bağlamsal bir stil oluşturabiliriz:
p { color: var(--paragraph); } #inner { --paragraph: green; } #outer { --paragraph: red; }
Custom --paragraph
özelliği, color
özelliği gibi devralır, ancak şimdi bu değerin tam olarak nasıl ve nereye uygulanacağını kontrol ediyoruz. --paragraph
özelliği, doğrudan seçim (özgüllük kuralları) veya bağlam (yakınlık kuralları) yoluyla p
bileşenine aktarılabilen bir parametreye benzer şekilde hareket eder.
Bunun, genellikle işlevler, karışımlar veya bileşenlerle ilişkilendirdiğimiz özel özellikler için bir potansiyel ortaya çıkardığını düşünüyorum.
Özel “İşlevler” ve Parametreler
İşlevler, karışımlar ve bileşenlerin tümü aynı fikre dayanmaktadır: Tutarlı ancak yapılandırılabilir sonuçlar elde etmek için çeşitli giriş parametreleriyle çalıştırılabilen yeniden kullanılabilir kod. Fark, sonuçlarla ne yaptıklarındadır. Çizgili gradyan bir değişkenle başlayacağız ve sonra onu diğer biçimlere genişletebiliriz:
html { --stripes: linear-gradient( to right, powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }
Bu değişken kök html
öğesinde tanımlanmıştır ( :root
da kullanılabilir, ancak bu gereksiz bir özgünlük ekler), bu nedenle çizgili değişkenimiz belgenin her yerinde kullanılabilir olacaktır. Gradyanların desteklendiği her yere uygulayabiliriz:
body { background-image: var(--stripes); }
Parametre Ekleme
İşlevler, değişkenler gibi kullanılır, ancak çıktıyı değiştirmek için parametreleri tanımlar. --stripes
değişkenimizi, içinde parametre benzeri bazı değişkenler tanımlayarak daha işlevli olacak şekilde güncelleyebiliriz. Açı değiştiren bir parametre oluşturmak to right
var(--stripes-angle)
ile değiştirerek başlayacağım:
html { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }
İşlevin hangi amaca hizmet etmesi gerektiğine bağlı olarak oluşturabileceğimiz başka parametreler de var. Kullanıcıların kendi şerit renklerini seçmelerine izin vermeli miyiz? Eğer öyleyse, fonksiyonumuz 5 farklı renk parametresini mi kabul ediyor yoksa şimdiki gibi sadece 3 tane dışarı-içe girecek mi? Renk durakları için de parametreler oluşturmak istiyor muyuz? Eklediğimiz her parametre, basitlik ve tutarlılık pahasına daha fazla özelleştirme sağlar.
Bu dengeye evrensel bir doğru cevap yok - bazı işlevlerin daha esnek olması ve diğerlerinin daha fazla fikir sahibi olması gerekiyor. Kodunuzda tutarlılık ve okunabilirlik sağlamak için soyutlamalar vardır, bu nedenle bir adım geri atın ve hedeflerinizin ne olduğunu sorun. Gerçekten özelleştirilebilir olması gereken nedir ve tutarlılık nerede uygulanmalıdır? Bazı durumlarda, tamamen özelleştirilebilir bir işlev yerine iki fikirli işleve sahip olmak daha yararlı olabilir.
Yukarıdaki işlevi kullanmak için --stripes-angle
parametresi için bir değer girmemiz ve çıktıyı background-image
gibi bir CSS çıktı özelliğine uygulamamız gerekir:
/* in addition to the code above… */ html { --stripes-angle: 75deg; background-image: var(--stripes); }
Devralınan Evrensele Karşı
--stripes
işlevini html
öğesinde alışkanlıktan tanımladım. Özel özellikler devralır ve işlevimin her yerde kullanılabilir olmasını istiyorum, bu nedenle onu kök öğeye koymak biraz mantıklı. Bu, --brand-color: blue
gibi değişkenleri devralmak için iyi çalışır, bu nedenle "fonksiyonumuz" için de çalışmasını bekleyebiliriz. Ancak bu işlevi yuvalanmış bir seçicide tekrar kullanmayı denersek, çalışmaz:
div { --stripes-angle: 90deg; background-image: var(--stripes); }
Yeni --stripes-angle
tamamen yok sayılır. Yeniden hesaplanması gereken işlevler için mirasa güvenemeyeceğimiz ortaya çıktı. Bunun nedeni, her özellik değerinin öğe başına bir kez hesaplanması (bizim durumumuzda html
kök öğesi) ve ardından hesaplanan değerin devralınmasıdır . İşlevimizi belge kökünde tanımlayarak, işlevin tamamını soyundan gelenler için kullanılabilir hale getirmeyiz - yalnızca işlevimizin hesaplanan sonucunu.
Bunu basamaklı --stripes-angle
parametresi açısından çerçevelerseniz anlamlı olur. Miras alınan herhangi bir CSS özelliği gibi, torunlar tarafından kullanılabilir ancak atalar tarafından kullanılamaz. Yuvalanmış bir div
üzerinde belirlediğimiz değer, html
kök atasında tanımladığımız bir fonksiyon için mevcut değildir. Herhangi bir öğe üzerinde yeniden hesaplayacak evrensel olarak kullanılabilen bir işlev oluşturmak için, onu her öğede tanımlamamız gerekir:
* { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); }
Evrensel seçici, işlevimizi her yerde kullanılabilir hale getirir, ancak istersek daha dar bir şekilde tanımlayabiliriz. Önemli olan, yalnızca açıkça tanımlandığı yerde yeniden hesaplayabilmesidir. İşte bazı alternatifler:
/* make the function available to elements with a given selector */ .stripes { --stripes: /* etc… */; } /* make the function available to elements nested inside a given selector */ .stripes * { --stripes: /* etc… */; } /* make the function available to siblings following a given selector */ .stripes ~ * { --stripes: /* etc… */; }
Bu, mirasa dayanmayan herhangi bir seçici mantıkla genişletilebilir.
Serbest Parametreler ve Geri Dönüş Değerleri
Yukarıdaki örneğimizde var(--stripes-angle)
hiçbir değere ve geri dönüşe sahip değildir. Çağrılmadan önce tanımlanması veya somutlaştırılması gereken Sass veya JS değişkenlerinin aksine, CSS özel özellikleri hiç tanımlanmadan çağrılabilir. Bu, bağlamdan devralınabilen bir işlev parametresine benzer bir "serbest" değişken oluşturur.
Sonunda, miras alınan bir değer ayarlamak için değişkeni html
veya :root
(veya başka herhangi bir ata) üzerinde tanımlayabiliriz, ancak önce herhangi bir değer tanımlanmamışsa geri dönüşü dikkate almamız gerekir. Tam olarak hangi davranışı istediğimize bağlı olarak birkaç seçenek var.
- "Gerekli" parametreler için bir geri dönüş istemiyoruz. Olduğu gibi, işlev
--stripes-angle
tanımlanana kadar hiçbir şey yapmaz. - “Opsiyonel” parametreler için
var()
işlevinde bir geri dönüş değeri sağlayabiliriz. Değişken adından sonra bir virgül ve ardından varsayılan değeri ekleriz:
var(--stripes-angle, 90deg)
Her var()
işlevi yalnızca bir geri dönüşe sahip olabilir - bu nedenle ek virgüller bu değerin bir parçası olacaktır. Bu, dahili virgüllerle karmaşık varsayılanlar sağlamayı mümkün kılar:
html { /* Computed: Hevetica, Ariel, sans-serif */ font-family: var(--sans-family, Hevetica, Ariel, sans-serif); /* Computed: 0 -1px 0 white, 0 1px 0 black */ test-shadow: var(--shadow, 0 -1px 0 white, 0 1px 0 black); }
Farklı değerlere farklı öncelikler vererek kendi kademeli kurallarımızı oluşturmak için iç içe değişkenleri de kullanabiliriz:
var(--stripes-angle, var(--global-default-angle, 90deg))
- İlk önce, açık parametremizi deneyin (
--stripes-angle
); - Varsa, genel bir "kullanıcı varsayılanına" (
--user-default-angle
) geri dönüş; - Son olarak, "fabrika varsayılanımıza"
(90deg
) geri dönüş.
Özel özelliği açık bir şekilde tanımlamak yerine var()
içinde geri dönüş değerleri ayarlayarak, parametrede herhangi bir özgüllük veya kademeli kısıtlama olmadığından emin oluruz. Tüm *-angle
parametreleri, herhangi bir bağlamdan devralınmak için "serbesttir".
Tarayıcı Geri Dönüşleri ve Değişken Geri Dönüşler
Değişkenleri kullanırken aklımızda tutmamız gereken iki geri dönüş yolu vardır:
- Değişken desteği olmayan tarayıcılar tarafından hangi değer kullanılmalıdır?
- Belirli bir değişken eksik veya geçersiz olduğunda, değişkenleri destekleyen tarayıcılar tarafından hangi değer kullanılmalıdır?
p { color: blue; color: var(--paragraph); }
Eski tarayıcılar değişken bildirim özelliğini yok sayar ve blue
geri döner - modern tarayıcılar her ikisini de okur ve ikincisini kullanır. var(--paragraph)
tanımlı olmayabilir, ancak geçerlidir ve önceki özelliği geçersiz kılar, bu nedenle değişken desteği olan tarayıcılar, unset
anahtar sözcüğünü kullanıyormuş gibi devralınan veya ilk değere geri döner.
Bu ilk başta kafa karıştırıcı görünebilir, ancak bunun için iyi nedenler var. Birincisi tekniktir: tarayıcı motorları geçersiz veya bilinmeyen sözdizimini "ayrıştırma zamanında" işler (ki bu önce olur), ancak değişkenler "hesaplanan değer zamanına" (ki bu daha sonra olur) kadar çözümlenmez.
- Ayrıştırma zamanında, geçersiz sözdizimine sahip bildirimler tamamen yok sayılır - önceki bildirimlere geri döner. Eski tarayıcıların izleyeceği yol budur. Modern tarayıcılar değişken sözdizimini destekler, bu nedenle önceki bildirim yerine atılır.
- Hesaplanan değer zamanında değişken geçersiz olarak derlenir, ancak çok geç - önceki bildirim zaten atıldı. Spesifikasyona göre, geçersiz değişken değerleri ,
unset
ile aynı şekilde değerlendirilir:
html { color: red; /* ignored as *invalid syntax* by all browsers */ /* - old browsers: red */ /* - new browsers: red */ color: not a valid color; color: var(not a valid variable name); /* ignored as *invalid syntax* by browsers without var support */ /* valid syntax, but invalid *values* in modern browsers */ /* - old browsers: red */ /* - new browsers: unset (black) */ --invalid-value: not a valid color value; color: var(--undefined-variable); color: var(--invalid-value); }
Bu, yazarlar olarak bizim için de iyidir, çünkü değişkenleri destekleyen tarayıcılar için daha karmaşık yedeklerle oynamamıza ve eski tarayıcılar için basit geri dönüşler sağlamamıza olanak tanır. Daha da iyisi, bu, gerekli parametreleri ayarlamak için null
/ undefined
durumu kullanmamıza izin verir. Bu, özellikle bir işlevi bir karışıma veya bileşene dönüştürmek istiyorsak önemli hale gelir.
Özel Mülk "Mixins"
Sass'ta işlevler ham değerler döndürürken, karışımlar genellikle özellik-değer çiftleriyle gerçek CSS çıktısını döndürür. Herhangi bir görsel çıktıya uygulamadan evrensel bir --stripes
özelliği tanımladığımızda, sonuç fonksiyona benzer. Çıktıyı evrensel olarak da tanımlayarak bunun bir karışım gibi davranmasını sağlayabiliriz:
* { --stripes: linear-gradient( var(--stripes-angle), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }
--stripes-angle
geçersiz veya tanımsız kaldığı sürece, mixin derlenemez ve hiçbir background-image
uygulanmaz. Herhangi bir elemana geçerli bir açı ayarlarsak, fonksiyon hesaplar ve bize bir arka plan verir:
div { --stripes-angle: 30deg; /* generates the background */ }
Ne yazık ki, bu parametre değeri devralacak , bu nedenle mevcut tanım div
ve tüm torunları üzerinde bir arka plan oluşturur. Bunu düzeltmek için, --stripes-angle
değerinin her öğede initial
değerine (veya herhangi bir geçersiz değere) dayandırarak miras almadığından emin olmalıyız. Bunu aynı evrensel seçicide yapabiliriz:
* { --stripes-angle: initial; --stripes: /* etc… */; background-image: var(--stripes); }
Güvenli Satır İçi Stiller
Bazı durumlarda, bir arka uç sunucusundan veya ön uç çerçevesinden gelen verilere dayalı olarak, parametrenin CSS dışından dinamik olarak ayarlanmasına ihtiyaç duyarız. Özel özelliklerle, olağan özgüllük sorunları hakkında endişelenmeden HTML'mizde değişkenleri güvenle tanımlayabiliriz:
<div>...</div>
Satır içi stillerin yüksek bir özgüllüğü vardır ve geçersiz kılınması çok zordur - ancak özel özelliklerle başka bir seçeneğimiz var: yok sayın. Div'i background-image: none
(örneğin) olarak ayarlarsak, bu satır içi değişkenin hiçbir etkisi olmaz. Daha da ileriye götürmek için bir ara değişken oluşturabiliriz:
* { --stripes-angle: var(--stripes-angle-dynamic, initial); }
Artık HTML'de --stripes-angle-dynamic
tanımlama veya yok sayma ve --stripes-angle
doğrudan stil sayfamızda ayarlama seçeneğine sahibiz.
Ön Ayar Değerleri
Daha karmaşık değerler veya yeniden kullanmak istediğimiz yaygın modeller için, aralarından seçim yapabileceğiniz birkaç önceden ayarlanmış değişken de sağlayabiliriz:
* { --tilt-down: 6deg; --tilt-up: -6deg; }
Değeri doğrudan ayarlamak yerine bu ön ayarları kullanın:
<div>...</div>
Bu, dinamik verilere dayalı çizelgeler ve grafikler oluşturmak veya hatta bir gün planlayıcısı hazırlamak için harikadır.
Bağlamsal Bileşenler
Ayrıca "miksimizi", onu açık bir seçiciye uygulayarak ve parametreleri isteğe bağlı hale getirerek bir "bileşen" olarak yeniden çerçeveleyebiliriz. Çıktımızı değiştirmek için --stripes-angle
varlığına veya yokluğuna güvenmek yerine, bir bileşen seçicinin varlığına veya yokluğuna güvenebiliriz. Bu, geri dönüş değerlerini güvenli bir şekilde ayarlamamızı sağlar:
[data-stripes] { --stripes: linear-gradient( var(--stripes-angle, to right), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }
Geri dönüşü var()
işlevinin içine koyarak, --stripes-angle
undefined ve bileşenin dışından bir değer devralmak için "serbest" bırakabiliriz. Bu, bir bileşen stilinin belirli yönlerini bağlamsal girdiye maruz bırakmanın harika bir yoludur. Bir JS çerçevesi tarafından oluşturulan (veya SVG simgeleri gibi gölge-DOM içinde kapsamlandırılan) "kapsamlı" stiller bile, dış etki için belirli parametreleri ortaya çıkarmak için bu yaklaşımı kullanabilir.
İzole Bileşenler
Parametreyi kalıtım için göstermek istemiyorsak, değişkeni varsayılan bir değerle tanımlayabiliriz:
[data-stripes] { --stripes-angle: to right; --stripes: linear-gradient( var(--stripes-angle, to right), powderblue 20%, pink 20% 40%, white 40% 60%, pink 60% 80%, powderblue 80% ); background-image: var(--stripes); }
Bu bileşenler aynı zamanda bir sınıfla veya başka herhangi bir geçerli seçiciyle de çalışır, ancak istediğimiz herhangi bir değiştirici için bir ad alanı oluşturmak için data-
niteliğini seçtim:
[data-stripes='vertical'] { --stripes-angle: to bottom; } [data-stripes='horizontal'] { --stripes-angle: to right; } [data-stripes='corners'] { --stripes-angle: to bottom right; }
Seçiciler ve Parametreler
Genellikle bir değişkeni ayarlamak için veri özniteliklerini kullanabilmeyi isterdim - CSS3 attr()
belirtimi tarafından desteklenen, ancak henüz herhangi bir tarayıcıda uygulanmayan bir özellik (her tarayıcıdaki bağlantılı sorunlar için kaynaklar sekmesine bakın). Bu, bir seçiciyi belirli bir parametreyle daha yakından ilişkilendirmemizi sağlar:
<div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); }
<div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); }
<div data-stripes="30deg">...</div> /* Part of the CSS3 spec, but not yet supported */ /* attr( , ) */ [data-stripes] { --stripes-angle: attr(data-stripes angle, to right); }
Bu arada, style
niteliğini kullanarak benzer bir şey elde edebiliriz:
<div>...</div> /* The `*=` atttribute selector will match a string anywhere in the attribute */ [style*='--stripes-angle'] { /* Only define the function where we want to call it */ --stripes: linear-gradient(…); }
Bu yaklaşım en çok, ayarlanan parametreye ek olarak diğer özellikleri dahil etmek istediğimizde kullanışlıdır. Örneğin, bir ızgara alanı ayarlamak, dolgu ve arka plan da ekleyebilir:
[style*='--grid-area'] { background-color: white; grid-area: var(--grid-area, auto / 1 / auto / -1); padding: 1em; }
Çözüm
Tüm bu parçaları bir araya getirmeye başladığımızda, özel özelliklerin aşina olduğumuz yaygın değişken kullanım durumlarının çok ötesine geçtiği anlaşılır. Yalnızca değerleri depolayıp kademeye göre kapsamlandırmakla kalmıyoruz, aynı zamanda bunları kademeyi yeni yöntemlerle manipüle etmek ve doğrudan CSS'de daha akıllı bileşenler oluşturmak için kullanabiliriz.
Bu, SMACSS ve BEM gibi adlandırma kurallarından "kapsamlı" stiller ve JS'de CSS'ye kadar geçmişte güvendiğimiz araçların çoğunu yeniden düşünmemizi gerektiriyor. Bu araçların çoğu, belirliliği gidermeye veya başka bir dilde dinamik stilleri yönetmeye yardımcı olur - artık doğrudan özel özelliklerle ele alabileceğimiz kullanım durumları. JS'de sıklıkla hesapladığımız dinamik stiller, artık ham verileri CSS'ye geçirerek işlenebilir.
İlk başta, bu değişiklikler "ek karmaşıklık" olarak görülebilir - çünkü CSS içinde mantık görmeye alışık değiliz. Ve tüm kodlarda olduğu gibi, aşırı mühendislik gerçek bir tehlike olabilir. Ancak birçok durumda, bu gücü karmaşıklık eklemek için değil, karmaşıklığı üçüncü taraf araçlarından ve sözleşmelerinden, web tasarımının ana diline ve (daha da önemlisi) yeniden tasarım diline taşımak için kullanabileceğimizi iddia ediyorum. tarayıcı. Stillerimiz hesaplama gerektiriyorsa, bu hesaplama CSS'imizin içinde yaşamalıdır.
Tüm bu fikirler çok daha ileri götürülebilir. Özel mülkler daha geniş çapta benimsenmeye başladı ve mümkün olanın yalnızca yüzeyini çizmeye başladık. Bunun nereye varacağını ve insanların başka neler bulabileceğini görmek beni heyecanlandırıyor. İyi eğlenceler!
Daha fazla okuma
- Serg Hospodarets “CSS Özel Özelliklerini Kullanmaya Başlama Zamanı”
- “CSS Özel Mülklerine Yönelik Bir Strateji Kılavuzu,” Michael Riethmuller