как разрешить light dom css применяться к элементам shadow dom?

Я определяю свои собственные настраиваемые элементы и считаю удобным добавлять его дочерние элементы в теневой дом, в основном потому, что я могу это сделать в конструкторе настраиваемых элементов, в то время как тот же конструктор не позволяет мне добавлять «обычных» дочерних элементов.

Конструктор пользовательских элементов не позволяет добавлять потомков, потому что новый элемент должен быть пустым сразу после создания.

Интересно, есть ли простой способ CSS и стили в документе могут влиять на элементы в теневом домене. Я хочу, чтобы селекторы из light dom достигли этих элементов в shadow dom.

Один из моих настраиваемых элементов - это «предмет». Я создам их много, чтобы заполнить список. Мне не нравится идея повторения одного и того же тега стиля в теневом домене каждого экземпляра элемента, поэтому я ищу место для размещения этого тега стиля для всех моих элементов.

Я прочитал много статей о теневом мире и о том, как внутренние стили не влияют на элементы вне границ, но любой из них ответил на мой вопрос.

Есть предположения? Спасибо!


person Alejandro Silvestri    schedule 17.04.2019    source источник


Ответы (2)


Текущая версия shadowDOM требует, чтобы вы разместили свой CSS внутри shadowDOM.

Большинство CSS довольно малы и добавляют всего несколько байтов к нескольким сотням байтов в каждом элементе. Некоторые из самых крупных, которые я видел, добавляют около 2 КБ CSS в каждую копию. Но это все еще очень мало по сравнению с данными, представленными в структуре DOM.

Есть несколько вещей, которые могут просочиться извне, например информация о шрифте, но их не так много.

Вот несколько способов повлиять на внешность:

1. Переменные CSS

CSS-переменная позволяет вам установить для переменной значение, которое используется в CSS, будь то в shadowDOM или нет.

2. Атрибуты

Атрибуты могут быть захвачены и перенесены в shadowDOM CSS. У меня есть несколько компонентов, которые используют атрибут для определения темы.

3. Свойства

Свойства также могут быть взяты и применены к внутреннему CSS.

Есть и другие способы обсуждения, но их придется подождать до V2.

class MyEL extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'}).innerHTML = `
    <style>
    .outer {
      color: var(--myColor, 'black');
    </style>
    <div class="outer">
      <h4>Title</h4>
      <slot></slot>
    </div>`;
  }

  static get observedAttributes() {
    return ['bgcolor'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      this.shadowRoot.querySelector('.outer').style.backgroundColor = newVal;
    }
  }

  get border() {
    return this.shadowRoot.querySelector('.outer').style.border;
  }
  set border(value) {
    this.shadowRoot.querySelector('.outer').style.border = value;
  }
}

customElements.define('my-el', MyEL);

setTimeout(() => {
  document.querySelector('my-el').border = '2px dashed blue';
},1000);

const btn = document.getElementById('toggle');
let color = '';
btn.addEventListener('click', () => {
  color = color === '' ? 'white' : '';
  document.querySelector('my-el').style.setProperty('--myColor', color);
});
<my-el bgcolor="red"></my-el>
<hr/>
<button id="toggle">toggle</button>

ОБНОВЛЕНО

Как указано в моем комментарии ниже, есть несколько правил CSS, которые проникнут в shadowDOM, если они не будут перезаписаны. Вещи, которые проникают, - это color, background, font и другие вещи, связанные с ними.

Этот код показывает пример того, как они проникают:

customElements.define('css-test', class extends HTMLElement {
  constructor() {
    super();
    var shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `<h1>Header</h1>
    <p>This is a <strong>strong</strong> string</p>
    <p>This is <em>emphasis<em>.`;
  }
});

const styles = document.createElement('style');
styles.textContent = `
* {
  background-color: #900;
  border: 1px solid #9876;
  border-radius: 5px;
  box-sizing: border-box;
  box-shadow: 0 0 4px #0005;
  color: #fff;
  font: 24px Tahoma;
  margin: 20px;
  padding 20px;
  text-align: center;
  text-decoration: #ffd wavy underline;
  text-shadow: 3px 3px 3px #0008;
  transform: rotate(30deg);
}`;

function toggleCss(evt) {
  if(styles.parentElement) {
    styles.remove();
  }
  else {
    document.body.appendChild(styles);
  }
}
const toggleEl = document.getElementById('toggle');
toggleEl.addEventListener('click', toggleCss);
  <css-test></css-test>
  <hr/>
  <button id="toggle">Toggle CSS</button>

Когда вы нажмете на кнопку-переключатель, вы увидите, что все меняет цвет, цвет фона, шрифты и т. Д. Но что-то вроде поворота происходит только с компонентом, а не с подчиненными элементами внутри компонента. И это хорошо. Представьте, что пользователь может вращать элементы внутри вашего компонента ... Весь ваш компонент сломается.

Я искал хорошую статью, в которой рассказывалось бы обо всех вещах, которые проникают, но не мог ее найти. Я видел это раньше, но забыл добавить его в закладки. Так что, если кто-то найдет эту статью, добавьте ее в комментарии ниже.

Вот тот же код, приведенный выше, с несколькими переопределенными элементами, чтобы вы могли видеть, как ваш компонент может переопределить то, что делается снаружи:

customElements.define('css-test', class extends HTMLElement {
  constructor() {
    super();
    var shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `<style>
    :host * {
      background: white;
      color: yellow;
      font-size: 14px;
    }
    </style><h1>Header</h1>
    <p>This is a <strong>strong</strong> string</p>
    <p>This is <em>emphasis<em>.`;
  }
});

const styles = document.createElement('style');
styles.textContent = `
* {
  background-color: #900;
  border: 1px solid #9876;
  border-radius: 5px;
  box-sizing: border-box;
  box-shadow: 0 0 4px #0005;
  color: #fff;
  font: 24px Tahoma;
  margin: 20px;
  padding 20px;
  text-align: center;
  text-decoration: #ffd wavy underline;
  text-shadow: 3px 3px 3px #0008;
  transform: rotate(30deg);
}`;

function toggleCss(evt) {
  if(styles.parentElement) {
    styles.remove();
  }
  else {
    document.body.appendChild(styles);
  }
}
const toggleEl = document.getElementById('toggle');
toggleEl.addEventListener('click', toggleCss);
<css-test></css-test>
  <hr/>
  <button id="toggle">Toggle CSS</button>

person Intervalia    schedule 17.04.2019
comment
Что делать, если я не владею кодом компонента и поэтому не могу редактировать его для интерпретации переменных, свойств или атрибутов css? Неужели невозможно написать легкий dom css, который взламывает элементы Shadow DOM? - person johncorser; 30.09.2019
comment
Есть несколько свойств CSS, которые будут унаследованы ShadowDOM, если он не будет перезаписан. font, color. См. Раздел ОБНОВЛЕНО в моих примерах выше. - person Intervalia; 30.09.2019

Прежде всего, я не знаю, что вы имеете в виду, говоря о том, что пользовательский компонент должен быть пустым ... Я не думаю, что это правда, поскольку я не могу найти никакой документации, в которой говорится об этом требовании как таковом. Кроме того, для меня отлично работает следующее:

customElements.define("my-element", class extends HTMLElement {
  constructor() {
    super();

    // Add a `span` element to the component's light-DOM.
    const span = document.createElement("span");
    span.innerHTML = "Hello, World!";
    this.appendChild(span);

    // Add a `slot` element to the component's shadow-DOM (so that the light-DOM gets rendered).
    this.attachShadow({ mode: "open" }).innerHTML = "<slot></slot>";
  }
});
<my-element></my-element>

Если вы имеете в виду, что содержимое Light-DOM не будет отображаться, если его нельзя назначить slot элементу в shadow-DOM, то, конечно, вы правы.

В любом случае, это был всего лишь побочный узел. Вернемся к вашему вопросу о стилизации теневой модели DOM извне веб-компонента ...

Помимо подробного ответа @ Intervalia, вы также можете взглянуть на псевдоэлементы ::part и ::theme веб-компонентов.

Что-то вроде этого:

customElements.define("my-element", class extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" }).innerHTML = `
      <style>
        * {
          position: relative;
          box-sizing: border-box;
          margin: 0;
          padding: 0;
        }
        :host {
          display: inline-block;
          height: 150px;
          width: 200px;
        }
        #outer {
          height: 100%;
          padding: 20px;
          background: lightblue;
          border: 5px solid blue;
        }
        #inner {
          height: 100%;
          background: lightgreen;
          border: 5px solid green;
        }
      </style>
      <div id="outer" part="foo">
        <div id="inner" part="bar">
        </div>
      </div>
    `;
  }
});
#second {
  width: 300px;
}

#second::part(bar) {
  background: lightcoral;
  border: 10px dotted red;
}
<my-element id="first"></my-element>
<my-element id="second"></my-element>

Для подробного объяснения я хотел бы сослаться на:

Обратите внимание, что псевдоэлемент ::part и соответствующий атрибут part в настоящее время работают только в Chrome и Opera. FireFox будет поддерживать его в версии 69, но сначала вам нужно установить layout.css.shadow-parts.enabled на true на странице about:config (что заставляет меня сомневаться, что он будет очень полезен для обычных пользователей, использующих Firefox, поэтому я надеюсь, что Mozilla включит его по умолчанию очень скоро).

person Bart Hofland    schedule 30.08.2019