Gölge DOM v1 - Bağımsız Web Bileşenleri

Gölge DOM, web geliştiricilerinin bölümlere ayrılmış DOM ve web bileşenleri için CSS oluşturmasına olanak tanır

Özet

Gölge DOM, web uygulaması oluşturmanın kırılganlığını ortadan kaldırır. Kırılganlık HTML, CSS ve JS'nin küresel doğasından gelir. Yıllar içinde fahiş bir sayı icat etti / araçlar nasıl yardımcı olabileceğini açıklayacağız. Örneğin, yeni bir HTML kimliği/sınıfı kullandığınızda sayfa tarafından kullanılan mevcut adla çakışıp çakışmayacağı belli değildir. Küçük hatalar kayabilir, CSS spesifikliği büyük bir soruna dönüşüyor (!important her şey dahil!), stil seçiciler kontrolden çıkacaktır performans düşebilir. Liste bu şekilde devam eder.

Gölge DOM, CSS ve DOM'yi düzeltir. Web'de kapsamlı stiller sunar platformu. Araçlar veya adlandırma kuralları olmadan CSS'yi işaretleme, uygulama ayrıntılarını gizleme ve yazma kontrolü bileşenleri hakkında daha fazla bilgi edinin.

Giriş

Gölge DOM, üç Web Bileşeni standardından biridir: HTML Şablonları, Shadow DOM ve Özel öğeler. HTML İçe Aktarma İşlemleri eskiden listede yer alıyordu ancak artık kullanımdan kaldırıldı.

Gölge DOM kullanan web bileşenleri oluşturmanız gerekmez. Ancak böyle yaptığınızda avantajlarından (CSS kapsamı, DOM kapsülleme, bileşimi) ve tekrar kullanılabilir özel öğeleri kullanıyorsanız dayanıklı, yüksek düzeyde yapılandırılabilir ve son derece yeniden kullanılabilir. Özelse öğeleri yeni bir HTML (JS API ile) oluşturmak için kullanılır. Gölge DOM, nasıl kullandığınıza bağlı olarak değişir. İki API birleşerek bir bileşen oluşturur HTML, CSS ve JavaScript kodu kullanabilirsiniz.

Gölge DOM, bileşen tabanlı uygulamalar oluşturmak için bir araç olarak tasarlanmıştır. Dolayısıyla, web geliştirmede sık karşılaşılan sorunlar için çözümler sunar:

  • Yalıtılmış DOM: Bileşenin DOM'u bağımsızdır (ör. document.querySelector(), bileşenin gölge DOM'sindeki düğümleri döndürmez.
  • Kapsamlı CSS: Gölge DOM'de tanımlanan CSS'nin kapsamındadır. Stil kuralları ve sayfa stilleri taşmaz.
  • Yapı: Bileşeniniz için bildirim temelli, işaretleme tabanlı bir API tasarlayın.
  • CSS'yi basitleştirir: Kapsamlı DOM, basit CSS seçicileri ve daha fazlasını kullanabileceğiniz anlamına gelir genel kimlik/sınıf adlarına dikkat etmeniz ve adlandırma çakışmaları konusunda endişelenmenize gerek yoktur.
  • Üretkenlik: Uygulamaları tek bir büyük yerine DOM parçaları halinde düşünün (global) sayfasına gidin.
ziyaret edin.

fancy-tabs demosu

Bu makale boyunca, demo bileşeninden (<fancy-tabs>) bahsedeceğim. ve kod snippet'lerini referans göstermekle daha iyi olur. Tarayıcınız API'leri destekliyorsa hemen aşağıda bunun canlı bir demosunu göreceksiniz. Aksi takdirde tam kaynağa GitHub'dan bakın.

Kaynağı GitHub'da görüntüle

Gölge DOM nedir?

DOM arka planı

HTML, kullanımı kolay olduğu için web'i destekler. Birkaç etiket tanımlayarak hem sunum hem de yapı içeren bir sayfayı saniyeler içinde oluşturabilirsiniz. Ancak, tek başına HTML pek faydalı değildir. İnsanlar bir metni kolayca anlayabilir. makinelerin daha fazlasına ihtiyacı var. Belge Nesnesi'ni girin Model veya DOM.

Tarayıcı bir web sayfasını yüklediğinde birçok ilginç şey yapar. Şunlardan biri: yaptığı şey, yazarın HTML'sini canlı bir dokümana dönüştürmektir. Temel olarak, sayfanın yapısını anlamak için tarayıcı, HTML'yi (statik) metin dizeleri) veri modeline (nesneler/düğümler) dönüştürür. Tarayıcı, HTML'nin hiyerarşisi için şu düğümlerden bir ağaç oluşturun: DOM. Harika şey hakkında önemli olan, sayfanızın canlı bir temsili olmasıdır. Statik Bizim yazdığımız HTML, tarayıcı tarafından üretilen düğümler, özellikleri, yöntemleri ve en iyi programlar tarafından manipüle edilebildiğini unutmamak gerekir! Bu nedenle DOM oluşturabiliyoruz. öğelerini doğrudan JavaScript kullanarak oluşturabilirsiniz:

const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello DOM';
header.appendChild(h1);
document.body.appendChild(header);

aşağıdaki HTML işaretlemesini oluşturur:

<body>
    <header>
    <h1>Hello DOM</h1>
    </header>
</body>

Her şey yolunda ve güzel olsun. Sonra gölge DOM nedir?

DOM... gölgelerde

Gölge DOM, iki farkı olan normal bir DOM'dur: 1) Nasıl oluşturulduğu/kullanıldığı ve 2) sayfanın geri kalanına kıyasla nasıl davrandığı. Normalde, bir DOM bunları başka bir öğenin alt öğeleri olarak ekleyin. Gölge DOM ile öğeye bağlı olan, ancak öğeden ayrı olarak kapsamlı bir DOM ağacı oluşturun gerçek çocuklardır. Kapsamı belirlenmiş bu alt ağaç, gölge ağacı olarak adlandırılır. Element gölge ana makinesi olur. Gölgeye eklediğiniz her şey <style> dahil olmak üzere barındırma öğesinde yereldir. Gölge DOM bu şekildedir CSS stil kapsamı elde eder.

Gölge DOM oluşturuluyor

Gölge kökü, bir "ana makine" öğesine eklenen bir belge parçasıdır. Gölge kökü ekleme işlemi, öğenin gölge DOM'sini nasıl edindiğidir. Alıcı: bir öğe için gölge DOM'u oluşturun, element.attachShadow() çağrısı yapın:

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().

// header.shadowRoot === shadowRoot
// shadowRoot.host === header

Gölge kökünü doldurmak için .innerHTML kullanıyorum ancak başka DOM de kullanabilirsiniz API'ler. Bu web. Seçme şansımız var.

Bu spesifikasyon, bir öğe listesini tanımlar barındıramaz. Bir öğenin şu listede:

  • Tarayıcı, öğe için zaten kendi dahili gölge DOM'sini barındırıyor (<textarea>, <input>).
  • Öğenin bir gölge DOM'u (<img>) barındırması anlamlı değildir.

Örneğin, şu yöntem işe yaramaz:

    document.createElement('input').attachShadow({mode: 'open'});
    // Error. `<input>` cannot host shadow dom.

Özel bir öğe için gölge DOM'u oluşturma

Gölge DOM, özel öğelerle ilişkilidir. Bir öğenin HTML, CSS ve JS'sini bölümlere ayırmak için gölge DOM'yi kullanın. "web bileşeni" oluşturabilirsiniz.

Örnek - özel bir öğe kendisine gölge DOM'u ekler, DOM/CSS'yi kapsülleme:

// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to <fancy-tabs>.
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
        <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
        <div id="tabs">...</div>
        <div id="panels">...</div>
    `;
    }
    ...
});

Burada bazı ilginç şeyler oluyor. İlki, <fancy-tabs> örneği olduğunda özel öğe kendi gölge DOM'sini oluşturur ile başlar. Bu işlem constructor() içinde yapılabilir. İkincisi, yeni kullanıcılar için bir gölge kökü olursa <style> içindeki CSS kurallarının kapsamı <fancy-tabs> olur.

Beste ve alanlar

Kompozisyon, gölge DOM'un en az anlaşılan özelliklerinden biridir. Ancak muhtemelen en önemlisi.

Web geliştirme dünyamızda beste, uygulamaları oluşturma şeklimizdir. bildiriyor. Farklı yapı taşları (<div>'ler, <header>'lar, <form> ve <input> olmak üzere) bir araya gelerek uygulama oluşturabilir. Bu etiketlerin bazıları bir iletişim kurmaktır. Kompozisyonun nedeni, <select>, <details>, <form> ve <video> çok esnek. Bu etiketlerin her biri özel bir işlem gerçekleştirir. Örneğin, <select>, <option> ve <optgroup> öğelerini açılır menüde nasıl oluşturacağını biliyor ve widget'ları otomatik olarak devreye sokun. <details> öğesi <summary> öğesini genişletilebilir ok. <video> bile bazı çocuklarla nasıl başa çıkacağını biliyor: <source> öğeleri oluşturulmaz ancak videonun davranışını etkiler. Ne sihir!

Terminoloji: ışık DOM'u ve gölge DOM'u

Gölge DOM bileşimi, web'de birçok yeni temel ilkeyi kullanıma sunar bahsedeceğim. Konuya girmeden önce aynı dili konuşuyoruz.

Hafif DOM

Bileşeninizin bir kullanıcısının yazdığı işaretleme. Bu DOM, gölge DOM'sini eklemeniz gerekir. Öğenin gerçek alt öğeleridir.

<better-button>
    <!-- the image and span are better-button's light DOM -->
    <img src="gear.svg" slot="icon">
    <span>Settings</span>
</better-button>

Gölge DOM

Bir bileşen yazarının yazdığı DOM. Gölge DOM, bileşende yereldir ve dahili yapısını, kapsamlı CSS'sini tanımlar ve uygulamanızı içerir bolca fırsat sunuyor. Ayrıca, tüketici tarafından yazılan işaretlemenin nasıl oluşturulacağını da tanımlayabilir hakkında daha fazla bilgi edinin.

#shadow-root
    <style>...</style>
    <slot name="icon"></slot>
    <span id="wrapper">
    <slot>Button</slot>
    </span>

Düzleştirilmiş DOM ağacı

Tarayıcının, kullanıcının ışık DOM'sini gölgenize dağıtmasının sonucu DOM, nihai ürünü oluşturuyor. Düzülmüş ağaç, nihai olarak tepeden tırnağa ve sayfada nelerin oluşturulduğunu görebilirsiniz.

<better-button>
    #shadow-root
    <style>...</style>
    <slot name="icon">
        <img src="gear.svg" slot="icon">
    </slot>
    <span id="wrapper">
        <slot>
        <span>Settings</span>
        </slot>
    </span>
</better-button>

<slot> öğe

Gölge DOM, <slot> öğesini kullanarak farklı DOM ağaçları oluşturur. Alanlar, bileşeninizin içindeki, kullanıcıların kendi işaretlemenizi kullanın. Bir veya daha fazla alan tanımlayarak dış işaretlemeyi oluşturmaya davet edersiniz. API'yi kullanabilirsiniz. Sonuç olarak, "Kullanıcının işaretleyin".

Öğelerin "çapraz" olmasına izin verilir <slot> tarafından davet edildiğinde gölge DOM inceleyebilirsiniz. Bu öğelere dağıtılmış düğümler adı verilir. Kavramsal olarak dağıtılmış düğümler biraz tuhaf görünebilir. Slotlar DOM'yi fiziksel olarak taşımaz; onlar gölge DOM'nin içindeki başka bir konumda oluşturun.

Bir bileşen, gölge DOM'sinde sıfır veya daha fazla alan tanımlayabilir. Slot boş olabilir veya yedek içerik sağlayın. Kullanıcı hafif DOM sağlamazsa alan, yedek içeriği oluşturur.

<!-- Default slot. If there's more than one default slot, the first is used. -->
<slot></slot>

<slot>fallback content</slot> <!-- default slot with fallback content -->

<slot> <!-- default slot entire DOM tree as fallback -->
    <h2>Title</h2>
    <summary>Description text</summary>
</slot>

Ayrıca adlandırılmış alanlar da oluşturabilirsiniz. Adlandırılmış alanlar, kullanıcıların adıyla başvurduğu gölge DOM.

Örnek - <fancy-tabs> gölge DOM'sindeki alanlar:

#shadow-root
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot> <!-- named slot -->
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>

Bileşen kullanıcıları, <fancy-tabs> öğesini aşağıdaki şekilde beyan ediyor:

<fancy-tabs>
    <button slot="title">Title</button>
    <button slot="title" selected>Title 2</button>
    <button slot="title">Title 3</button>
    <section>content panel 1</section>
    <section>content panel 2</section>
    <section>content panel 3</section>
</fancy-tabs>

<!-- Using <h2>'s and changing the ordering would also work! -->
<fancy-tabs>
    <h2 slot="title">Title</h2>
    <section>content panel 1</section>
    <h2 slot="title" selected>Title 2</h2>
    <section>content panel 2</section>
    <h2 slot="title">Title 3</h2>
    <section>content panel 3</section>
</fancy-tabs>

Merak ediyorsanız, düzleştirilmiş ağaç aşağıdaki gibi görünür:

<fancy-tabs>
    #shadow-root
    <div id="tabs">
        <slot id="tabsSlot" name="title">
        <button slot="title">Title</button>
        <button slot="title" selected>Title 2</button>
        <button slot="title">Title 3</button>
        </slot>
    </div>
    <div id="panels">
        <slot id="panelsSlot">
        <section>content panel 1</section>
        <section>content panel 2</section>
        <section>content panel 3</section>
        </slot>
    </div>
</fancy-tabs>

Bileşenimizin farklı yapılandırmaları işleyebildiğine dikkat edin, ancak düzleştirilmiş DOM ağacı aynı kalır. İsterseniz <button> yerine <h2>. Bu bileşen, farklı alt öğeleri işlemek için geliştirilmiştir... aynı <select> gibi!

Stil

Web bileşenlerinin stil özelliklerini ayarlamak için pek çok seçenek vardır. Gölge kullanan bir bileşen DOM, ana sayfa tarafından stillandırılabilir, kendi stillerini tanımlayabilir veya kancalar ( CSS özel özellikleri) biçimindedir.

Bileşen tanımlı stiller

Gölge DOM'un en yararlı özelliği kapsamlı CSS'dir:

  • Dış sayfadaki CSS seçiciler, bileşeninizin içinde geçerli değildir.
  • İçeride tanımlanan stillere aktarılamaz. Barındırıcı öğesine ayarlanırlar.

Gölge DOM'sinde kullanılan CSS seçiciler bileşeninize yerel olarak uygulanır. İçinde Böylece, endişe duymadan yaygın kimlik/sınıf adlarını tekrar kullanabiliriz. çakışmalarla ilgili daha fazla bilgi verir. En iyi uygulama, daha basit CSS seçicileri kullanmaktır inceleyebilirsiniz. Ayrıca, performans için de iyidir.

Örnek: Bir gölge kökünde tanımlanan stiller yereldir

#shadow-root
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        ...
    }
    #tabs {
        display: inline-flex;
        ...
    }
    </style>
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

Stil sayfaları gölge ağacına da ayarlanır:

#shadow-root
    <link rel="stylesheet" href="styles.css">
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

<select> öğesinin çoklu seçimli bir widget'ı ( açılır menüden) multiple özelliğini eklediğinizde:

<select multiple>
  <option>Do</option>
  <option selected>Re</option>
  <option>Mi</option>
  <option>Fa</option>
  <option>So</option>
</select>

<select>, kullandığınız özelliklere göre kendisini farklı şekillerde biçimlendirebiliyor açıklamam gerekiyor. Web bileşenleri, :host öğesini kullanarak kendilerini de biçimlendirebilir. kullanabilirsiniz.

Örnek: Kendisine stil veren bir bileşen

<style>
:host {
    display: block; /* by default, custom elements are display: inline */
    contain: content; /* CSS containment FTW. */
}
</style>

:host ile elde edilen sorunlardan biri, üst sayfadaki kuralların daha belirgin olmasıdır. öğede tanımlanan :host kuraldan daha fazladır. Yani dış stiller kazanır. Bu kullanıcıların üst düzey stilinizi dışarıdan geçersiz kılmasına olanak tanır. Ayrıca :host yalnızca bir gölge kökü bağlamında çalıştığından bunu gölge DOM'yi belirtir.

:host(<selector>) işlevinin işlevsel biçimi, şu durumlarda ana makineyi hedeflemenize olanak tanır: <selector> ile eşleşiyor. Bu, bileşeninizin en iyi uygulamaları göstermesi için durumlara veya stil özelliklerine bağlı olarak dahili düğümlerin nasıl stil ana makinede.

<style>
:host {
    opacity: 0.4;
    will-change: opacity;
    transition: opacity 300ms ease-in-out;
}
:host(:hover) {
    opacity: 1;
}
:host([disabled]) { /* style when host has disabled attribute. */
    background: grey;
    pointer-events: none;
    opacity: 0.4;
}
:host(.blue) {
    color: blue; /* color host when it has class="blue" */
}
:host(.pink) > #tabs {
    color: pink; /* color internal #tabs node when host has class="pink". */
}
</style>

Bağlama göre stil belirleme

:host-context(<selector>), bileşen veya üst öğelerinden biri varsa bileşenle eşleşir <selector> ile eşleşiyor. Bunun yaygın bir kullanımı, bileşenin özelliklerine göre tema oluşturmak teşekkür ederiz. Örneğin, birçok kişi bir sınıfa uygulayarak <html> veya <body>:

<body class="darktheme">
    <fancy-tabs>
    ...
    </fancy-tabs>
</body>

:host-context(.darktheme), bir alt öğe olduğunda <fancy-tabs> stilini biçimlendirir / .darktheme:

:host-context(.darktheme) {
    color: white;
    background: black;
}

:host-context(), tema oluşturmak için faydalı olabilir ancak CSS özel özelliklerini kullanarak stil kancaları oluşturun.

Dağıtılmış düğümlerin stilini belirleme

::slotted(<compound-selector>), bir <slot>.

Bir ad rozeti bileşeni oluşturduğumuzu varsayalım:

<name-badge>
    <h2>Eric Bidelman</h2>
    <span class="title">
    Digital Jedi, <span class="company">Google</span>
    </span>
</name-badge>

Bileşenin gölge DOM'u, kullanıcının <h2> ve .title öğelerini biçimlendirebilir:

<style>
::slotted(h2) {
    margin: 0;
    font-weight: 300;
    color: red;
}
::slotted(.title) {
    color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
    text-transform: uppercase;
}
*/
</style>
<slot></slot>

Daha önce hatırlarsanız <slot>'ler kullanıcının ışık DOM'sini taşımaz. Zaman düğümler bir <slot> içine dağıtılırsa <slot> onların DOM'sini oluşturur, ancak düğümler fiziksel olarak yerinde kalır. Dağıtımdan önce uygulanan stiller geçerlidir. Bununla birlikte, ışık DOM'u dağıtıldığında tüm ek stiller (gölge DOM'si tarafından tanımlananlar) alır.

<fancy-tabs> sitesinden daha kapsamlı başka bir örnek:

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        border-radius: 3px;
        padding: 16px;
        height: 250px;
        overflow: auto;
    }
    #tabs {
        display: inline-flex;
        -webkit-user-select: none;
        user-select: none;
    }
    #tabsSlot::slotted(*) {
        font: 400 16px/22px 'Roboto';
        padding: 16px 8px;
        ...
    }
    #tabsSlot::slotted([aria-selected="true"]) {
        font-weight: 600;
        background: white;
        box-shadow: none;
    }
    #panelsSlot::slotted([aria-hidden="true"]) {
        display: none;
    }
    </style>
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot>
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>
`;

Bu örnekte, sekme başlıkları için adlandırılmış bir alan ve sekme paneli içeriği için bir alan. Bir kullanıcı bir sekme seçtiğinde seçimini kalın hale getiririz panelini açın. Bu, aynı boyuta sahip dağıtılmış düğümler seçerek selected özelliği için de kullanılmaktadır. Özel öğenin JS'si (burada gösterilmez), .

Bir bileşeni dışarıdan şekillendirme

Dışarıdan bir bileşene stil eklemenin birkaç yolu vardır. En kolay yöntemi, etiket adını seçici olarak kullanmaktır:

fancy-tabs {
    width: 500px;
    color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
    box-shadow: 0 3px 3px #ccc;
}

Dış stiller her zaman gölge DOM'de tanımlanan stillere karşı kazanır. Örneğin, kullanıcı fancy-tabs { width: 500px; } seçiciyi yazarsa bu seçici öne çıkar bileşenin kuralı: :host { width: 650px;}.

Bileşenin kendisine stil kazandırmak yeterli olacaktır. Peki ya bir bileşenin dahili bileşenlerini mi ayarlamak istiyorsunuz? Bunun için CSS'deki özel özellikler.

CSS özel özelliklerini kullanarak stil kancaları oluşturma

Bileşenin yazarı stil özellikleri sağlarsa kullanıcılar dahili stilleri düzenleyebilir CSS özel özelliklerini kullanarak. Kavramsal olarak, <slot> "Stil yer tutucuları" oluşturursunuz kullanıcıların geçersiz kılmasına izin verir.

Örnek - <fancy-tabs>, kullanıcıların arka plan rengini geçersiz kılmasına izin verir:

<!-- main page -->
<style>
    fancy-tabs {
    margin-bottom: 32px;
    --fancy-tabs-bg: black;
    }
</style>
<fancy-tabs background>...</fancy-tabs>

Gölge DOM'sinin içinde:

:host([background]) {
    background: var(--fancy-tabs-bg, #9E9E9E);
    border-radius: 10px;
    padding: 10px;
}

Bu durumda, bileşen arka plan değeri olarak black kullanacaktır. tarafından sağlanmıştı. Aksi takdirde varsayılan olarak #9E9E9E kullanılır.

İleri düzey konular

Kapalı gölge kökleri oluşturma (kaçınılması gerekir)

Gölge DOM'nin "closed" adlı başka bir türü daha vardır. yatırım yapmanız önemlidir. Bir JavaScript'in dahili DOM'ye erişmesi mümkün olmadığı sürece, hakkında daha fazla bilgi edinin. Bu, <video> gibi yerel öğelerin çalışma şekline benzer. JavaScript, <video> gölge DOM'sine erişemiyor çünkü tarayıcı kapalı mod gölge kökü kullanarak uygular.

Örnek: Kapalı gölge ağacı oluşturma:

const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'closed'}); // close shadow tree
// div.shadowRoot === null
// shadowRoot.host === div

Kapalı moddan diğer API'ler de etkilenir:

  • Element.assignedSlot / TextNode.assignedSlot, null tarihinde iade ediliyor
  • Gölge içindeki öğelerle ilişkili etkinlikler için Event.composedPath() DOM, şunu döndürür: []
ziyaret edin.

İşte size neden {mode: 'closed'}:

  1. Yapay güvenlik duygusu. Bir saldırganı hiçbir şey Element.prototype.attachShadow hesabının ele geçirilmesi.

  2. Kapalı mod, özel öğe kodunuzun kendi koduna erişmesini engeller gölge DOM'yi eklemeniz gerekir. Tamamen başarısız oldu. Bunun yerine referans saklamanız gerekir querySelector() gibi araçları kullanmak istiyorsanız bundan daha sonra). Bu tamamen kapalı modun asıl amacına aykırıdır.

        customElements.define('x-element', class extends HTMLElement {
        constructor() {
        super(); // always call super() first in the constructor.
        this._shadowRoot = this.attachShadow({mode: 'closed'});
        this._shadowRoot.innerHTML = '<div class="wrapper"></div>';
        }
        connectedCallback() {
        // When creating closed shadow trees, you'll need to stash the shadow root
        // for later if you want to use it again. Kinda pointless.
        const wrapper = this._shadowRoot.querySelector('.wrapper');
        }
        ...
    });
    
  3. Kapalı mod, bileşeninizi son kullanıcılar için daha az esnek hale getirir. Siz bir kod eklemeyi unuttuğunuzda, özelliğini kullanabilirsiniz. Yapılandırma seçeneğidir. Kullanıcının istediği bir kullanım alanı. Yaygın bir dahili düğümler için yeterli biçimlendirme kancası eklemeyi unutmak olabilir. Kapalı modda, kullanıcıların varsayılanları geçersiz kılıp stillerini ayarlayın. Bileşenin dahili bilgilerine erişebilmek son derece faydalıdır. Sonuç olarak, kullanıcılar bileşeninizi çatallanır, başka bir bileşen bulur veya kendi bileşenlerini istediklerini yapmazsa sahip olur :(

JS'deki alanlarla çalışma

Gölge DOM API'si, slotlarla ve dağıtılmış özelliklerle çalışma düğüm. Özel öğe yazarken bunlar kullanışlıdır.

zaman aralığı değişikliği etkinliği

Bir alanın dağıtılmış düğümleri değiştiğinde slotchange etkinliği tetiklenir. Örneğin, Örneğin, kullanıcı ışık DOM'sine alt öğe ekler/kaldırırsa.

const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
    console.log('light dom children changed!');
});
.

Işık DOM'de yapılan diğer değişiklik türlerini izlemek için MutationObserver oluşturmanız gerekir.

Bir alanda hangi öğeler oluşturuluyor?

Bazen bir alanla hangi öğelerin ilişkilendirildiğini bilmek yararlı olur. Telefonla arama Alanın hangi öğeleri oluşturduğunu bulmak için slot.assignedNodes() tuşlarına basın. İlgili içeriği oluşturmak için kullanılan {flatten: true} seçeneği, bir alanın yedek içeriğini de döndürür (hiçbir düğüm yoksa) dağıtılıyor).

Örnek olarak, gölge DOM'nizin aşağıdaki gibi göründüğünü varsayalım:

<slot><b>fallback content</b></slot>
KullanımTelefonSonuç
<my-component>bileşen metni</my-component> slot.assignedNodes(); [component text]
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes(); []
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes({flatten: true}); [<b>fallback content</b>]

Bir öğe hangi alana atanır?

Ters soruyu yanıtlamak da mümkündür. element.assignedSlot anlatıyor öğenizin hangi bileşen alanlarına atandığını görebilirsiniz.

Gölge DOM etkinlik modeli

Bir etkinlik gölge DOM'de dışarı çıktığında hedefi, gölge DOM'un sağladığı kapsülleme. Yani etkinlikler, daha iyi performans elde etmek için ve Google Analytics 4'teki dahili öğeler yerine bileşenden gölge DOM'yi belirtir. Bazı etkinlikler gölge DOM'den dışarı yayılmaz bile.

Gölge sınırını geçen etkinlikler şunlardır:

  • Odaklanılan Etkinlikler: blur, focus, focusin, focusout
  • Fare Etkinlikleri: click, dblclick, mousedown, mouseenter, mousemove vb.
  • Çark Etkinlikleri: wheel
  • Giriş Etkinlikleri: beforeinput, input
  • Klavye Etkinlikleri: keydown, keyup
  • Beste Etkinlikleri: compositionstart, compositionupdate, compositionend
  • DragEvent: dragstart, drag, dragend, drop vb.

İpuçları

Gölge ağacı açıksa event.composedPath() işlevinin çağrılması bir dizi döndürür düğümlerin bulunduğu bir URL'dir.

Özel etkinlikleri kullanma

Bir gölge ağacındaki dahili düğümlerde tetiklenen özel DOM etkinlikleri etkinlik composed: true işareti:

// Inside <fancy-tab> custom element class definition:
selectTab() {
    const tabs = this.shadowRoot.querySelector('#tabs');
    tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
}

composed: false (varsayılan) ise tüketiciler etkinliği dinleyemez gölge kökünüzün dışına çıkar.

<fancy-tabs></fancy-tabs>
<script>
    const tabs = document.querySelector('fancy-tabs');
    tabs.addEventListener('tab-select', e => {
    // won't fire if `tab-select` wasn't created with `composed: true`.
    });
</script>

Odaklanma

Gölge DOM'nin etkinlik modelinden hatırlarsanız, tetiklenen etkinlikler içlerindeki gölge DOM'u, barındırma öğesinden geliyormuş gibi görünecek şekilde ayarlanır. Örneğin, gölge kökü içindeki bir <input> öğesini tıkladığını varsayalım:

<x-focus>
    #shadow-root
    <input type="text" placeholder="Input inside shadow dom">

focus etkinliği <input> alanından değil, <x-focus> alanından gelmiş gibi görünecek. Benzer şekilde, document.activeElement <x-focus> olacak. Gölge kökü mode:'open' ile oluşturulduysa (kapalı mod konusuna bakın) odaklanılan dahili düğüme erişebilir:

document.activeElement.shadowRoot.activeElement // only works with open mode.

Birden fazla gölge DOM'u seviyesi varsa (ör. bir özel öğe varsa) gölge köklerini yinelemeli olarak ayrıntılı şekilde activeElement öğesini bul:

function deepActiveElement() {
    let a = document.activeElement;
    while (a && a.shadowRoot && a.shadowRoot.activeElement) {
    a = a.shadowRoot.activeElement;
    }
    return a;
}

Odaklanmak için kullanabileceğiniz bir diğer seçenek de delegatesFocus: true seçeneğidir. Bu seçenek gölge ağacındaki öğenin odak davranışı:

  • Gölge DOM'nin içindeki bir düğümü tıklarsanız ve düğüm odaklanılabilir bir alan değilse odaklanılabilir hale gelir.
  • Gölge DOM'sinin içindeki bir düğüm odağını elde ettiğinde :focus, odaklanılan öğedir.

Örnek - delegatesFocus: true ürününün odak davranışını nasıl değiştirdiği

<style>
    :focus {
    outline: 2px solid red;
    }
</style>

<x-focus></x-focus>

<script>
customElements.define('x-focus', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    const root = this.attachShadow({mode: 'open', delegatesFocus: true});
    root.innerHTML = `
        <style>
        :host {
            display: flex;
            border: 1px dotted black;
            padding: 16px;
        }
        :focus {
            outline: 2px solid blue;
        }
        </style>
        <div>Clickable Shadow DOM text</div>
        <input type="text" placeholder="Input inside shadow dom">`;

    // Know the focused element inside shadow DOM:
    this.addEventListener('focus', function(e) {
        console.log('Active element (inside shadow dom):',
                    this.shadowRoot.activeElement);
    });
    }
});
</script>

Sonuç

delegatesFocus: gerçek davranış.

Yukarıda <x-focus> odaklanıldığında sonuç gösterilir (kullanıcı tıklaması, sekmeyle focus() vb.), "Tıklanabilir Gölge DOM metni" veya dahili <input> odaklanılmış (autofocus dahil).

delegatesFocus: false özelliğini ayarlamış olsaydınız bunun yerine şunu görecektiniz:

delegatesFocus: false ve dahili girişe odaklanılır.
delegatesFocus: false ve dahili <input> odaklanıldı.
ziyaret edin.
'nı inceleyin.
delegatesFocus: false ve x-focus
    odaklanacaktır (ör. tabindex=&#39;0&#39; öğesine sahiptir).
delegatesFocus: false ve <x-focus> odaklanılır (ör. tabindex="0" olan).
delegatesFocus: false ve &quot;Clickable Shadow DOM text&quot; (Tıklanabilir Gölge DOM metni) :
    tıklandığında (veya öğenin gölge DOM&#39;si içindeki başka bir boş alan tıklandığında).
delegatesFocus: false ve "Tıklanabilir Gölge DOM metni" : tıklandığında (veya öğenin gölge DOM'si içindeki başka bir boş alan tıklandığında).

İpuçları ve Püf Noktaları

Yıllar içinde web bileşenleri yazma hakkında birkaç şey öğrendim. İ bu ipuçlarından bazılarını bileşen yazma konusunda faydalı bulacağınızı düşünüyorum gölge DOM'de hata ayıklama.

CSS kapsamı kullanma

Genelde, bir web bileşeninin düzeni/stil/boyası yeterince bağımsızdır. Tekliflerinizi otomatikleştirmek ve optimize etmek için Performans için :host bölgesinde CSS kapsamı kazan:

<style>
:host {
    display: block;
    contain: content; /* Boom. CSS containment FTW. */
}
</style>

Devralınabilir stiller sıfırlanıyor

Devralınabilir stiller (background, color, font, line-height vb.) devam ediyor gölge DOM'den devralmak için de kullanılabilir. Yani gölge DOM sınırını varsayılandır. Yeni bir seçenek listesiyle başlamak istiyorsanız sıfırlamak için all: initial; komutunu kullanın devralınabilir stilleri gölge sınırını geçtiklerinde başlangıç değerlerine ekler.

<style>
    div {
    padding: 10px;
    background: red;
    font-size: 25px;
    text-transform: uppercase;
    color: white;
    }
</style>

<div>
    <p>I'm outside the element (big/white)</p>
    <my-element>Light DOM content is also affected.</my-element>
    <p>I'm outside the element (big/white)</p>
</div>

<script>
const el = document.querySelector('my-element');
el.attachShadow({mode: 'open'}).innerHTML = `
    <style>
    :host {
        all: initial; /* 1st rule so subsequent properties are reset. */
        display: block;
        background: white;
    }
    </style>
    <p>my-element: all CSS properties are reset to their
        initial value using <code>all: initial</code>.</p>
    <slot></slot>
`;
</script>

Bir sayfanın kullandığı tüm özel öğeleri bulma

Bazen sayfada kullanılan özel öğeleri bulmak yararlı olur. Bunun için, sayfada kullanılan tüm öğelerin gölge DOM'sini yinelemeli olarak çekmesi gerekir.

const allCustomElements = [];

function isCustomElement(el) {
    const isAttr = el.getAttribute('is');
    // Check for <super-button> and <button is="super-button">.
    return el.localName.includes('-') || isAttr && isAttr.includes('-');
}

function findAllCustomElements(nodes) {
    for (let i = 0, el; el = nodes[i]; ++i) {
    if (isCustomElement(el)) {
        allCustomElements.push(el);
    }
    // If the element has shadow DOM, dig deeper.
    if (el.shadowRoot) {
        findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
    }
    }
}

findAllCustomElements(document.querySelectorAll('*'));

<template> şablonundan öğe oluşturma

Bir gölge kökünü .innerHTML kullanarak doldurmak yerine bildirim tabanlı kullanabiliriz <template>. Şablonlar, projenin yapısını açıklamak için ideal bir yer tutucudur bir web bileşenidir.

Aşağıdaki örnekte "Özel öğeler: Yeniden kullanılabilir web bileşenleri oluşturma".

Tarih ve tarayıcı desteği

Son birkaç yıldır web bileşenlerini takip ediyorsanız Chrome 35 ve sonraki sürümlerin/Opera'nın Windows 1.0 sürümü için gölge DOM'nin eski bir sürümünü gönderdiğini biraz zaman alabilir. Blink, bazı uygulamalar için her iki sürümü de paralel olarak desteklemeye devam edecektir. gerekir. v0 spesifikasyonu, gölge kökü oluşturmak için farklı bir yöntem sağladı (v1'in element.attachShadow yerine element.createShadowRoot). eski yöntem, v0 semantiğiyle bir gölge kökü oluşturmaya devam ediyor. Dolayısıyla, mevcut v0 zaman harcıyor.

Eski v0 spesifikasyonuyla ilgileniyorsanız html5rocks'a göz atın. makaleler: 1, 2, 3. Ayrıca, burada görmeye alışkın olduğumuz gölge DOM v0 ile v1 arasındaki farklar hakkında daha fazla bilgi edinin.

Tarayıcı desteği

Gölge DOM v1, Chrome 53'te (durum) gönderilir Opera 40, Safari 10 ve Firefox 63. Kenar geliştirmeye başladı.

Gölge DOM'u özellik algılamak için attachShadow olup olmadığını kontrol edin:

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

Çoklu Dolgu

Tarayıcı desteği yaygın olarak kullanıma sunulana kadar shadydom ve shadycss polyfill'leri size v1'i verir özelliğini kullanabilirsiniz. Gölgeli DOM, Gölge DOM ve shadycss polyfill'lerinin DOM kapsamını taklit eder CSS özel özellikleri ve yerel API'nin sağladığı stil kapsamı.

Polyfill'leri takın:

bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss

Çoklu dolguları kullanın:

function loadScript(src) {
    return new Promise(function(resolve, reject) {
    const script = document.createElement('script');
    script.async = true;
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
    });
}

// Lazy load the polyfill if necessary.
if (!supportsShadowDOMV1) {
    loadScript('/bower_components/shadydom/shadydom.min.js')
    .then(e => loadScript('/bower_components/shadycss/shadycss.min.js'))
    .then(e => {
        // Polyfills loaded.
    });
} else {
    // Native shadow dom v1 support. Go to go!
}

https://github.com/webcomponents/shadycss#usage adresine göz atın inceleyin.

Sonuç

İlk kez, doğru CSS kapsamı oluşturan bir API temel bileşenimiz var, DOM kapsamı ve gerçek bir bileşimi vardır. Diğer web bileşeni API'leriyle birleştirildi gibi gölge DOM'yi de kullanabilirsiniz. Gölge DOM, veya <iframe> gibi daha eski bagajlar kullanan eski bagajlar olabilir.

Yanlış anlamayın. Gölge DOM gerçekten karmaşık bir canavar. Ama canavar öğrenmeye değer. Biraz vakit geçirin. Öğrenin ve soru sorun!

Daha fazla bilgi

SSS

Gölge DOM v1'i bugün kullanabilir miyim?

Çoklu dolgu ile evet. Tarayıcı desteği sayfasına göz atın.

Gölge DOM hangi güvenlik özelliklerini sağlar?

Gölge DOM bir güvenlik özelliği değildir. CSS'nin kapsamını belirlemek için kullanılan hafif bir araçtır ve DOM ağaçlarını bileşende gizlemeyi deneyin. Gerçek bir güvenlik sınırı istiyorsanız <iframe> kullanın.

Bir web bileşeninin gölge DOM'yi kullanması gerekiyor mu?

Hayır. Gölge DOM kullanan web bileşenleri oluşturmanız gerekmez. Ancak, Gölge DOM kullanan özel öğeler yazmak, ve kompozisyon (ör. CSS kapsamı, DOM kapsülleme) ve

Açık ve kapalı gölge kökleri arasındaki fark nedir?

Kapalı gölge kökleri konusuna bakın.