Bildirimsel Gölge DOM, Chrome'un 90 sürümünden itibaren desteklenen bir standart web platformu özelliğidir. Bu özelliğin spesifikasyonunun 2023'te değiştiğini (shadowroot
'ün shadowrootmode
olarak yeniden adlandırılması dahil) ve özelliğin tüm bölümlerinin en güncel standartlaştırılmış sürümlerinin Chrome 124 sürümünde kullanıma sunulduğunu unutmayın.
Gölge DOM, HTML şablonları ve Özel Öğeler ile birlikte üç Web Bileşenleri standardından biridir. Gölge DOM, CSS stillerini belirli bir DOM alt ağacı için kapsama alma ve bu alt ağacı belgenin geri kalanından izole etme yöntemi sağlar. <slot>
öğesi, bir özel öğenin alt öğelerinin Gölge Ağacı'na nereye ekleneceğini kontrol etmemizi sağlar. Bu özelliklerin bir arada kullanılması, yerleşik bir HTML öğesi gibi mevcut uygulamalara sorunsuz bir şekilde entegre olan, kendi kendine yeten, yeniden kullanılabilir bileşenler oluşturmayı sağlar.
Gölge DOM'u kullanmanın tek yolu, JavaScript kullanarak bir gölge kökü oluşturmaktı:
const host = document.getElementById('host');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';
Bu gibi zorunlu API'ler istemci tarafı oluşturma için iyi çalışır: Özel öğelerimizi tanımlayan JavaScript modülleri, gölge köklerini oluşturur ve içeriklerini ayarlar. Ancak birçok web uygulamasının, derleme sırasında içeriği sunucu tarafında veya statik HTML olarak oluşturması gerekir. Bu, JavaScript çalıştıramayan ziyaretçilere makul bir deneyim sunmanın önemli bir parçası olabilir.
Sunucu Tarafında Oluşturma (SSR) için gerekçeler projeden projeye değişir. Bazı web siteleri, erişilebilirlik yönergelerini karşılamak için sunucu tarafından oluşturulan, tam işlevli HTML sağlamalıdır. Diğerleri ise yavaş bağlantılarda veya cihazlarda iyi performans sağlamak için JavaScript içermeyen temel bir deneyim sunmayı tercih eder.
Geçmişte, sunucu tarafından oluşturulan HTML'de Gölge Kökleri'ni ifade etmenin yerleşik bir yolu olmadığından Gölge DOM'u sunucu tarafı oluşturma ile birlikte kullanmak zordu. Gölge kökleri, daha önce bunlar olmadan oluşturulmuş DOM öğelerine eklendiğinde de performansla ilgili sonuçlar ortaya çıkar. Bu durum, sayfa yüklendikten sonra düzenin kaymasına neden olabilir veya gölge kökün stil sayfaları yüklenirken geçici olarak stil uygulanmamış içeriğin yanıp sönmesine ("FOUC") yol açabilir.
Bildirimsel Gölge DOM (DSD), bu sınırlamayı kaldırarak Gölge DOM'u sunucuya getirir.
Açıklayıcı gölge kökü oluşturma
Açıklayıcı gölge kökü, shadowrootmode
özelliğine sahip bir <template>
öğesidir:
<host-element>
<template shadowrootmode="open">
<slot></slot>
</template>
<h2>Light content</h2>
</host-element>
shadowrootmode
özelliğine sahip bir şablon öğesi, HTML ayrıştırıcısı tarafından algılanır ve hemen üst öğesinin gölge kökü olarak uygulanır. Yukarıdaki örnekteki saf HTML işaretlemesini yüklediğinizde aşağıdaki DOM ağacı oluşur:
<host-element>
#shadow-root (open)
<slot>
↳
<h2>Light content</h2>
</slot>
</host-element>
Bu kod örneği, Chrome DevTools Öğeler panelinin Gölge DOM içeriğini görüntülemeyle ilgili kurallarına uyar. Örneğin, ↳
karakteri, ışık DOM'u içeriğini temsil eder.
Bu sayede, statik HTML'de Gölge DOM'un kapsayıcı özelliğinden ve slot projeksiyonundan yararlanabiliriz. Gölge kökü dahil olmak üzere ağacın tamamının oluşturulması için JavaScript gerekmez.
Bileşenlerin nemlendirilmesi
Beyanî Gölge DOM, stilleri kapsayacak veya alt öğe yerleşimini özelleştirecek bir yöntem olarak tek başına kullanılabilir ancak en güçlü performansı Özel Öğeler ile birlikte kullanıldığında gösterir. Özel Öğeler kullanılarak oluşturulan bileşenler, statik HTML'den otomatik olarak yükseltilir. Açıklayıcı Gölge DOM'un kullanıma sunulmasıyla birlikte, özel öğelerin yükseltilmeden önce gölge kökü olması artık mümkün.
HTML'den yükseltilen ve açıklayıcı gölge kökü içeren bir özel öğeye bu gölge kökü zaten eklenmiştir. Bu, öğenin, kodunuz açıkça oluşturmadan, örneklendiğinde zaten mevcut bir shadowRoot
mülküne sahip olacağı anlamına gelir. Öğenizin kurucusundaki mevcut gölge kökü için this.shadowRoot
değerini kontrol etmeniz önerilir. Zaten bir değer varsa bu bileşenin HTML'sinde bir Declarative Shadow Root bulunur. Değer null ise HTML'de beyansal gölge kökü bulunmaz veya tarayıcı beyansal gölge DOM'u desteklemez.
<menu-toggle>
<template shadowrootmode="open">
<button>
<slot></slot>
</button>
</template>
Open Menu
</menu-toggle>
<script>
class MenuToggle extends HTMLElement {
constructor() {
super();
// Detect whether we have SSR content already:
if (this.shadowRoot) {
// A Declarative Shadow Root exists!
// wire up event listeners, references, etc.:
const button = this.shadowRoot.firstElementChild;
button.addEventListener('click', toggle);
} else {
// A Declarative Shadow Root doesn't exist.
// Create a new shadow root and populate it:
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = `<button><slot></slot></button>`;
shadow.firstChild.addEventListener('click', toggle);
}
}
}
customElements.define('menu-toggle', MenuToggle);
</script>
Özel Öğeler bir süredir kullanılıyor ve şimdiye kadar attachShadow()
kullanarak yeni bir gölge kök oluşturmadan önce mevcut bir gölge kök olup olmadığını kontrol etmeye gerek yoktu. Açıklayıcı Gölge DOM, mevcut bileşenlerin buna rağmen çalışmasını sağlayan küçük bir değişiklik içerir: Mevcut bir açıklayıcı gölge kökü olan bir öğede attachShadow()
yönteminin çağrılması hata oluşturmaz. Bunun yerine, beyan dilinde gölge kökü boşaltılır ve döndürülür. Zorunlu bir değişim oluşturulana kadar beyan kökleri korunduğundan, beyan DOM'u için oluşturulmamış eski bileşenlerin çalışmaya devam etmesine olanak tanır.
Yeni oluşturulan özel öğeler için yeni bir ElementInternals.shadowRoot
mülkü, bir öğenin hem açık hem de kapalı olan mevcut açıklayıcı gölge köküne referans almanın açık bir yolunu sağlar. Bu, herhangi bir Açıklayıcı Gölge Kökü olup olmadığını kontrol etmek ve kullanmak için kullanılabilir. Bu durumda, sağlanmayan durumlarda attachShadow()
değerine geri dönülebilir.
class MenuToggle extends HTMLElement {
constructor() {
super();
const internals = this.attachInternals();
// check for a Declarative Shadow Root:
let shadow = internals.shadowRoot;
if (!shadow) {
// there wasn't one. create a new Shadow Root:
shadow = this.attachShadow({
mode: 'open'
});
shadow.innerHTML = `<button><slot></slot></button>`;
}
// in either case, wire up our event listener:
shadow.firstChild.addEventListener('click', toggle);
}
}
customElements.define('menu-toggle', MenuToggle);
Kök başına bir gölge
Açıklayıcı gölge kökü yalnızca üst öğesiyle ilişkilendirilir. Bu, gölge köklerin her zaman ilişkili öğeleriyle birlikte yerleştirildiği anlamına gelir. Bu tasarım kararı, gölge köklerin bir HTML dokümanının geri kalanı gibi aktarılabilir olmasını sağlar. Bir öğeye gölge kök eklemek, mevcut gölge köklerin bir kayıt defterini tutmayı gerektirmediğinden, içerik oluşturma ve oluşturma için de kullanışlıdır.
Gölge kökleri üst öğeleriyle ilişkilendirmenin dezavantajı, aynı deklaratif gölge kökünden <template>
birden fazla öğenin başlatılamamasıdır. Ancak her gölge kökünün içeriği nadiren aynı olduğundan, beyansal gölge DOM'un kullanıldığı çoğu durumda bu durumun önemli olması olası değildir. Sunucu tarafından oluşturulan HTML genellikle yinelenen öğe yapıları içerir ancak içerikleri genellikle farklıdır (ör. metin veya özelliklerde küçük farklılıklar). Serileştirilmiş bir beyansal gölge kökünün içeriği tamamen statik olduğundan, tek bir beyansal gölge kökünden birden fazla öğeyi yükseltmek yalnızca öğeler aynıysa işe yarar. Son olarak, sıkıştırmanın etkileri nedeniyle, benzer gölge köklerin tekrarlanmasının ağ aktarımı boyutu üzerindeki etkisi nispeten küçüktür.
Gelecekte, paylaşılan gölge kökleri tekrar ziyaret edilebilir. DOM, yerleşik şablonlama desteği kazanırsa beyansal gölge kökleri, belirli bir öğenin gölge kökünü oluşturmak için örneklendirilen şablonlar olarak değerlendirilebilir. Mevcut beyansal gölge DOM tasarımı, gölge kök ilişkilendirmesini tek bir öğeyle sınırlayarak bu olasılığın gelecekte var olmasına olanak tanır.
Canlı yayın yapmak çok havalı
Açıklayıcı Gölge Kökleri'ni doğrudan üst öğeleriyle ilişkilendirmek, bunları yükseltme ve ilgili öğeye ekleme sürecini basitleştirir. Açıklayıcı Gölge Kökleri, HTML ayrıştırma sırasında algılanır ve açılış <template>
etiketiyle karşılaşıldığında hemen eklenir. <template>
içindeki ayrıştırılmış HTML doğrudan gölge köke ayrıştırılır. Bu sayede "akış şeklinde" oynatılabilir: Alındığı şekilde oluşturulur.
<div id="el">
<script>
el.shadowRoot; // null
</script>
<template shadowrootmode="open">
<!-- shadow realm -->
</template>
<script>
el.shadowRoot; // ShadowRoot
</script>
</div>
Yalnızca ayrıştırıcı
Bildirimsel gölge DOM, HTML ayrıştırıcısının bir özelliğidir. Bu, açıklayıcı gölge kökün yalnızca HTML ayrıştırma sırasında mevcut olan bir shadowrootmode
özelliğine sahip <template>
etiketleri için ayrıştırılacağı ve ekleneceği anlamına gelir. Diğer bir deyişle, beyansal gölge kökler ilk HTML ayrıştırma işlemi sırasında oluşturulabilir:
<some-element>
<template shadowrootmode="open">
shadow root content for some-element
</template>
</some-element>
<template>
öğesinin shadowrootmode
özelliğini ayarlamak hiçbir şey yapmaz ve şablon normal bir şablon öğesi olarak kalır:
const div = document.createElement('div');
const template = document.createElement('template');
template.setAttribute('shadowrootmode', 'open'); // this does nothing
div.appendChild(template);
div.shadowRoot; // null
Bazı önemli güvenlik hususlarını önlemek için innerHTML
veya insertAdjacentHTML()
gibi parça ayrıştırma API'leri kullanılarak da beyan biçiminde gölge kökler oluşturulamaz. Açıklayıcı Gölge Kökleri uygulanmış HTML'yi ayrıştırmanın tek yolu setHTMLUnsafe()
veya parseHTMLUnsafe()
kullanmaktır:
<script>
const html = `
<div>
<template shadowrootmode="open"></template>
</div>
`;
const div = document.createElement('div');
div.innerHTML = html; // No shadow root here
div.setHTMLUnsafe(html); // Shadow roots included
const newDocument = Document.parseHTMLUnsafe(html); // Also here
</script>
Stil sahibi sunucu tarafı oluşturma
Satır içi ve harici stil sayfaları, standart <style>
ve <link>
etiketleri kullanılarak beyansal gölge kökleri içinde tamamen desteklenir:
<nineties-button>
<template shadowrootmode="open">
<style>
button {
color: seagreen;
}
</style>
<link rel="stylesheet" href="/comicsans.css" />
<button>
<slot></slot>
</button>
</template>
I'm Blue
</nineties-button>
Bu şekilde belirtilen stiller de son derece optimize edilmiştir: Aynı stil sayfası birden fazla açıklayıcı gölge kökünde varsa yalnızca bir kez yüklenir ve ayrıştırılır. Tarayıcı, tüm gölge kökler tarafından paylaşılan tek bir destek CSSStyleSheet
kullanır ve böylece yinelenen bellek yükü ortadan kaldırılır.
Oluşturulabilir stil sayfaları, beyansal gölge DOM'da desteklenmez. Bunun nedeni, şu anda HTML'de oluşturulabilir stil sayfalarını serileştirmenin ve adoptedStyleSheets
öğesini doldururken bunlara referans vermenin mümkün olmamasıdır.
Stili olmayan içeriğin yanıp sönmesini önleme
Henüz beyansal gölge DOM'u desteklemeyen tarayıcılarda karşılaşılabilecek bir sorun, henüz yükseltilmemiş özel öğeler için ham içeriklerin gösterildiği "stil uygulanmamış içeriklerin yanıp sönmesi" (FOUC) sorununu önlemektir. Bildirimsel Gölge DOM'den önce, FOUC'den kaçınmak için yaygın olarak kullanılan bir teknik, henüz yüklenmemiş özel öğelere display:none
stil kuralı uygulamaktı. Bu öğelerin gölge kökü eklenip doldurulmadığı için bu teknik kullanılıyordu. Bu sayede, içerik "hazır" olana kadar gösterilmez:
<style>
x-foo:not(:defined) > * {
display: none;
}
</style>
Açıklayıcı Gölge DOM'un kullanıma sunulmasıyla birlikte Özel Öğeler, gölge içeriklerinin istemci tarafı bileşen uygulaması yüklenmeden önce hazır ve yerinde olacak şekilde HTML'de oluşturulabilir veya oluşturulabilir:
<x-foo>
<template shadowrootmode="open">
<style>h2 { color: blue; }</style>
<h2>shadow content</h2>
</template>
</x-foo>
Bu durumda, display:none
"FOUC" kuralı, açıklayıcı gölge kökün içeriğinin gösterilmesini engeller. Ancak bu kuralın kaldırılması, Bildirimsel Gölge DOM desteği olmayan tarayıcıların, Bildirimsel Gölge DOM polyfill yüklenip gölge kök şablonunu gerçek bir gölge köke dönüştürene kadar yanlış veya stilsiz içerik göstermesine neden olur.
Neyse ki bu sorun, CSS'de FOUC stil kuralı değiştirilerek çözülebilir. Bildirimsel Gölge DOM'yi destekleyen tarayıcılarda <template shadowrootmode>
öğesi hemen bir gölge köküne dönüştürülür ve DOM ağacında <template>
öğesi kalmaz. Bildirimsel gölge DOM'u desteklemeyen tarayıcılar, FOUC'yi önlemek için kullanabileceğimiz <template>
öğesini korur:
<style>
x-foo:not(:defined) > template[shadowrootmode] ~ * {
display: none;
}
</style>
Düzeltilen "FOUC" kuralı, henüz tanımlanmamış özel öğeyi gizlemek yerine, <template shadowrootmode>
öğesini takip eden alt öğelerini gizler. Özel öğe tanımlandıktan sonra kural artık eşleşmez. <template shadowrootmode>
alt öğesi HTML ayrıştırma sırasında kaldırıldığı için kural, beyansal gölge DOM'u destekleyen tarayıcılarda yoksayılır.
Özellik algılama ve tarayıcı desteği
Bildirimsel gölge DOM, Chrome 90 ve Edge 91'den beri kullanılabiliyordu ancak standart shadowrootmode
özelliği yerine shadowroot
adlı eski bir standart olmayan özellik kullanılıyordu. Daha yeni shadowrootmode
özelliği ve akış davranışı, Chrome 111 ve Edge 111'de kullanılabilir.
Yeni bir web platformu API'si olan Declarative Shadow DOM henüz tüm tarayıcılarda yaygın olarak desteklenmemektedir. Tarayıcı desteği, HTMLTemplateElement
prototipinde shadowRootMode
mülkünün varlığı kontrol edilerek algılanabilir:
function supportsDeclarativeShadowDOM() {
return HTMLTemplateElement.prototype.hasOwnProperty('shadowRootMode');
}
Çoklu dolgu
Bir polyfill'in, tarayıcı uygulamasının ilgilendiği zamanlama semantiklerini veya yalnızca ayrıştırıcı özelliklerini mükemmel şekilde kopyalamasına gerek olmadığından, beyansal gölge DOM için basitleştirilmiş bir polyfill oluşturmak nispeten kolaydır. Beyan Edilmiş Gölge DOM'u çoklu dolgulamak için tüm <template shadowrootmode>
öğelerini bulmak üzere DOM'u tarayabilir ve ardından bunları üst öğelerinde ekli Gölge Köklerine dönüştürebiliriz. Bu işlem, doküman hazır olduğunda yapılabilir veya Özel Öğe yaşam döngüsü gibi daha spesifik etkinlikler tarafından tetiklenebilir.
(function attachShadowRoots(root) {
root.querySelectorAll("template[shadowrootmode]").forEach(template => {
const mode = template.getAttribute("shadowrootmode");
const shadowRoot = template.parentNode.attachShadow({ mode });
shadowRoot.appendChild(template.content);
template.remove();
attachShadowRoots(shadowRoot);
});
})(document);