Пользовательский стиль для веб-компонента vanilla JS

У меня есть этот компонент:

index.html

<html>

<head>
    <title></title>
    <meta charset="UTF-8">
    <script src="clock.js"></script>
    <style>
        clock-digital div {
            background-color: green;
        }
    </style>
</head>

<body>
    <clock-digital></clock-digital>
</body>

</html>

часы.js

customElements.define('clock-digital', class extends HTMLElement {

    constructor() {
        super();
        var shadowRoot = this.attachShadow({
            mode: 'open'
        });
        this._clockID = setInterval(function () {
            var currentdate = new Date();
            var hours = ( (currentdate.getHours() < 10) ? '0' : '') + currentdate.getHours();
            var minutes = ( (currentdate.getMinutes() < 10) ? '0' : '') + currentdate.getMinutes();
            var seconds = ( (currentdate.getSeconds() < 10) ? '0' : '') + currentdate.getSeconds();
            shadowRoot.innerHTML = `
            <style>
                div {
                    display: inline-block;
                    width: 65px;
                    text-align: center;
                    background-color: whitesmoke;
                    font-style: italic;
                    border: 1px solid lightgray;
                    border-radius: 3px;
                    box-shadow: 2px 2px 3px;
                }
            </style>
            <div>
                ${hours}:${minutes}:${seconds}
            </div>`;
        }, 500);
    }

});

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

<style>
        clock-digital div {
            background-color: green;
        }
    </style>

но это не работает. Должен ли я использовать тег слота где-то в теневом корне? Какова наилучшая практика для достижения этого?


person asv    schedule 03.01.2017    source источник


Ответы (2)


Вы можете указать свойства CSS в своем пользовательском элементе, которые могут быть установлены внешне.

В вашем примере ваш элемент может определить --clock-background-color, который устанавливает цвет фона div:

shadowRoot.innerHTML = 
  `<style>
     div {
       background-color: var(--clock-background-color, whitesmoke);
       /* ... */
     }
   </style>
   <div>
     ${hours}:${minutes}:${seconds}
   </div>`;

Затем пользователи вашего элемента могут изменить цвет фона на зеленый следующим образом:

<style>
  clock-digital {
    --clock-background-color: green;
  }
</style>
<clock-digital></clock-digital>

codepen

Обратите внимание, что в демо-версии Codepen используются полифиллы веб-компонентов для браузеров, отличных от Chrome, но вы можете закомментировать его, чтобы увидеть, что он по-прежнему работает в Chrome.

person tony19    schedule 03.01.2017
comment
Это очень элегантное решение, я не знаю о пользовательских свойствах CSS. Спасибо. - person asv; 03.01.2017
comment
@asv Нет проблем :) - person tony19; 03.01.2017

Вы также можете воспользоваться преимуществом определяемых пользователем правил CSS над селектором :host, который представляет корень Shadow DOM (то есть сам пользовательский элемент).

В своем пользовательском элементе используйте :host для стилизации содержимого:

shadowRoot.innerHTML = `
    <style>
        :host {
            display: inline-block;
            width: 65px;
            text-align: center;
            background-color: whitesmoke;
            font-style: italic;
            border: 1px solid lightgray;
            border-radius: 3px;
            box-shadow: 2px 2px 3px;
        }
    </style>
    <div>
        ${hours}:${minutes}:${seconds}
    </div>`

Теперь пользователь может определять свои собственные стили со стандартной нотацией CSS, как и для любых элементов HTML. Это переопределит стили, определенные правилом :host.

<style>
     clock-digital {
         background-color: green;
         color: white;
     }
</style>

customElements.define('clock-digital', class extends HTMLElement {
    constructor() {
        super();
        var shadowRoot = this.attachShadow({ mode: 'open' });
        this._clockID = setInterval(function () {
            var currentdate = new Date();
            var hours = ('0'+currentdate.getHours()).substr(-2,2)
            var minutes = ('0'+currentdate.getMinutes()).substr(-2,2)
            var seconds = ('0'+currentdate.getSeconds()).substr(-2,2) 
            shadowRoot.innerHTML = `
            <style>
                :host {
                    display: inline-block;
                    width: 65px;
                    text-align: center;
                    background-color: whitesmoke;
                    font-style: italic;
                    border: 1px solid lightgray;
                    border-radius: 3px;
                    box-shadow: 2px 2px 3px;
                }
            </style>           
            ${hours}:${minutes}:${seconds}`;
        }, 500);
    }
});
clock-digital  {
  background-color: green;
  color: white;
  font-weight: bold;
}
<clock-digital></clock-digital>

person Supersharp    schedule 03.01.2017
comment
Хорошо, это тоже хорошо. Есть лучшая практика? :host или пользовательские свойства css? Если я хочу, чтобы пользователь мог получить доступ не только к корню тени, но и к некоторой части дочерних элементов корня, как я могу этого добиться? - person asv; 04.01.2017
comment
Нет лучшей практики, только адаптированные решения :-) Я бы сказал: host лучше для простого пользовательского элемента (как в вашем вопросе), а пользовательские свойства могут решать более сложные случаи (например, в вашем комментарии). Вы также можете использовать другие решения, такие как атрибуты или импорт стилей. Это также зависит от целевой платформы (Edge пока не поддерживает пользовательские свойства). - person Supersharp; 04.01.2017
comment
Обратите внимание: если вы хотите, чтобы пользователи стилизовали все содержимое вашего пользовательского элемента, вы можете использовать обычный DOM вместо Shadow DOM. Это также допустимо для ванильных веб-компонентов. - person Supersharp; 04.01.2017
comment
Да, но в этом случае мой стиль может изменить стиль тегов других пользователей, у меня больше нет свойства области теневого DOM. если я не ошибаюсь. - person asv; 04.01.2017
comment
Нет, если все ваши правила стиля начинаются с имени пользовательского элемента. - person Supersharp; 04.01.2017
comment
Правильно, и если я использую ссылку с импортом html (следовательно, не использую строку шаблона), я могу поместить стиль из тега шаблона, и поэтому стиль не будет повторяться в DOM. - person asv; 04.01.2017
comment
Есть ли гибридное решение? Такой компонент: component.html ‹style› tag-name {..... } ‹/style› ‹template› .......... ‹/template› ‹script› ...... .. клонировать шаблон и прикрепить в shadowRoot ‹/script› - person asv; 04.01.2017
comment
Давайте продолжим обсуждение в чате. - person Supersharp; 04.01.2017