- Notifications
You must be signed in to change notification settings - Fork179
Shadow DOM and events#549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes fromall commits
Commits
Show all changes
2 commits Select commitHold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
104 changes: 52 additions & 52 deletions8-web-components/7-shadow-dom-events/article.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,14 @@ | ||
#Тіньовий DOMта події | ||
Головна мета створення тіньового дерева -- це інкапсуляція внутрішньої реалізації компоненту. | ||
Уявімо, користувач клікнув на якийсь елемент всередені тіньового DOMкомпоненту `<user-card>`, і відбулася подія click. Але ж скріпти в головному документі і гадки не мають про внутрішню будову тіньового DOM, особливо, якщо компонент походить зі сторонньої бібліотеки. | ||
Отже, для збереження інкапсуляції вмісту, браузер *змінює у цієї події цільовий(target) елемент*. | ||
**Події, що відбуваються у тіньовому DOM, мають його "host" у властивості `target` об'єкту події, якщо подія обробляється за межами компоненту.** | ||
Розглянемо простий приклад: | ||
```html run autorun="no-epub" untrusted height=60 | ||
<user-card></user-card> | ||
@@ -21,30 +21,30 @@ customElements.define('user-card', class extends HTMLElement { | ||
<button>Click me</button> | ||
</p>`; | ||
this.shadowRoot.firstElementChild.onclick = | ||
e => alert("target зсередини: " + e.target.tagName); | ||
} | ||
}); | ||
document.onclick = | ||
e => alert("target ззовні: " + e.target.tagName); | ||
</script> | ||
``` | ||
Клікнувши на кнопку, отримаємо наступні повідомлення: | ||
1. target зсередини: `BUTTON` --внутрішній обробник подій отримує правильнийtarget -- елемент всередині тіньового DOM. | ||
2. target ззовні: `USER-CARD` --обробник подій документу отримує тіньовий хост в якості target події. | ||
dolgachio marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
Зміна target події -- чудова річ, бо зовнішній документ не повинен знати про внутрішній вміст компоненту. З цієї точки зору, подія відбулась в `<user-card>`. | ||
**Зміна target не відбувається, якщо подія починається з елементу зі слоту, що фактично знаходиться в звичайному світлому DOM.** | ||
Наприклад, якщо користувач клікає на`<span slot="username">`у прикладі, наведеному нижче, цільовим елементом є саме цей елемент `span`, для обох обробників -- звичайного (світлого) та тіньового: | ||
```html run autorun="no-epub" untrusted height=60 | ||
<user-card id="userCard"> | ||
*!* | ||
<span slot="username">Іван Коваль</span> | ||
*/!* | ||
</user-card> | ||
@@ -57,80 +57,80 @@ customElements.define('user-card', class extends HTMLElement { | ||
</div>`; | ||
this.shadowRoot.firstElementChild.onclick = | ||
e => alert("target зсередини: " + e.target.tagName); | ||
} | ||
}); | ||
userCard.onclick = e => alert(`target ззовні: ${e.target.tagName}`); | ||
</script> | ||
``` | ||
Якщо клік відбувся на `"Іван Коваль"`,для обох -- внутрішнього та зовнішнього -- обробників уtargetбуде елемент`<span slot="username">`.Це елемент зі світлогоDOM,тому зміна target не відбувається. | ||
З іншого боку, якщо клік відбувся на елементі з тіньовогоDOM,напр. на `<b>Name</b>`,то коли він вспливає з тіньовогоDOM,його `event.target`стає `<user-card>`. | ||
##Спливання, event.composedPath() | ||
Для реалізації спливання подій (бульбашковий механізм) використовується підхід розгорнутого DOM. | ||
Отже, якщо у нас є елемент у слоті, і подія відбувається десь всередині цього елементу, тоді вона підіймається до`<slot>`і вище. | ||
Повний шлях до справжнього target елементу цієї події, включаючи всі тіньові елементи, можна отримати за допомогою `event.composedPath()`.Як видно з назви методу, він повертає шлях після складання всіх його елементів. | ||
У наведеному вище прикладі зведенийDOMвиглядає так: | ||
```html | ||
<user-card id="userCard"> | ||
#shadow-root | ||
<div> | ||
<b>Name:</b> | ||
<slot name="username"> | ||
<span slot="username">Іван Коваль</span> | ||
</slot> | ||
</div> | ||
</user-card> | ||
``` | ||
Отже, для кліку по`<span slot="username">` виклик`event.composedPath()`повертає масив: [`span`, `slot`, `div`, `shadow-root`, `user-card`, `body`, `html`, `document`, `window`], що цілковито відображає батьківський ланцюжок, починаючи з targetелемента у зведеномуDOM після складання. | ||
```warn header="Деталі тіньового дерева надаються лише для дерев з`{mode:'open'}`" | ||
Якщо тіньове дерево було створено з`{mode: 'closed'}`,то тоді складений (composed) шлях починається від хоста: `user-card`і вище. | ||
Це той самий принцип, що і для інших методів, які працюють із тіньовимDOM.Внутрішні частини закритих дерев повністю приховані. | ||
``` | ||
##Властивістьevent.composed | ||
Більшість подій успішно проходять через тіньову межу DOM. Є кілька подій, які нездатні на це. | ||
Це регулюється властивістю об’єкта події `composed`. Якщо вона `true`,то подія дійсно може перетнути межу. В іншому випадку її можна буде перехопити лише зсередини тіньового DOM. | ||
Якщо ви подивитесь на[UI Events specification](https://www.w3.org/TR/uievents),більшість подій мають `composed: true`: | ||
- `blur`, `focus`, `focusin`, `focusout`, | ||
- `click`, `dblclick`, | ||
- `mousedown`, `mouseup` `mousemove`, `mouseout`, `mouseover`, | ||
- `wheel`, | ||
- `beforeinput`, `input`, `keydown`, `keyup`. | ||
Усі сенсорні події та події курсору також мають `composed: true`. | ||
Та існують деякі події, що мають `composed: false`: | ||
- `mouseenter`, `mouseleave` (ці події взагалі не вспливають), | ||
- `load`, `unload`, `abort`, `error`, | ||
- `select`, | ||
- `slotchange`. | ||
Ці події можна перехопити лише на елементах у межах того ж самогоDOM,де знаходитьсяtargetелемент події. | ||
##Генерація подій (Custom events) | ||
Коли ми генеруємо користувацькі події, нам потрібно встановити для властивостей `bubbles`і `composed`значення`true`, щоб вони вспливали та виходили за межі компонента. | ||
Наприклад, тут ми створюємо `div#inner`у тіньовомуDOM `div#outer`і запускаємо дві події для нього. Лише та, що має `composed: true`, виходить за межі документа: | ||
```html run untrusted height=0 | ||
<div id="outer"></div> | ||
@@ -167,26 +167,26 @@ inner.dispatchEvent(new CustomEvent('test', { | ||
</script> | ||
``` | ||
##Підсумки | ||
Лише ті події перетинають тіньові межі DOM, у прапорці`composed`яких задано значення `true`. | ||
Вбудовані події здебільшого мають `composed: true`,як описано у відповідних специфікаціях: | ||
-Події інтерфейсу користувача (UI Events) <https://www.w3.org/TR/uievents>. | ||
-Сенсорні події (Touch Events) <https://w3c.github.io/touch-events>. | ||
-Події вказівника (Pointer Events) <https://www.w3.org/TR/pointerevents>. | ||
- ...тощо. | ||
Деякі вбудовані події, що мають `composed: false`: | ||
- `mouseenter`, `mouseleave` (зовсім не спливають), | ||
- `load`, `unload`, `abort`, `error`, | ||
- `select`, | ||
- `slotchange`. | ||
Ці події можуть бути перехоплені тільки на елементах у межах того самого DOM. | ||
Якщо ми генеруємо`CustomEvent`,тоді нам слід явно встановити `composed: true`. | ||
Зверніть увагу, що у випадку вкладених компонентів один тіньовийDOMможе бути вкладений в інший. У цьому випадку складені події проходять через усі тіньові межі DOM. Отже, якщо подія призначена лише для безпосередньо найближчого зовнішнього батьківського компонента, ми також можемо ініціювати її на тіньовому хості і встановити`composed: false`.В такому разі подія виходить із тіньової DOM компонента, але не переходить до DOM вищого рівня. |
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.