Как изменить значение rem в рамках Shadow DOM?

Я разрабатываю расширение для браузера Chrome и столкнулся с проблемой определения размера шрифтов в компонентах, использующих библиотеки UI, такие как Vuetify, PrimeVue или MUI. Обратите внимание, что размеры шрифтов в этих библиотеках задаются в единицах rem. При использовании библиотек в shadow DOM размер rem, установленный в HTML в пределах shadow DOM, игнорируется, и вместо этого берется значение rem из корневого HTML документа. В результате, на различных сайтах размер шрифта может различаться в зависимости от font-size в корневом элементе. Какой лучший способ решить эту проблему: нужно ли переопределять стили в используемой UI-библиотеке, или существует другой способ заставить UI библиотеки использовать значение rem, установленное в shadow DOM, а не в корневом документе?
  • 14 апреля 2025 г. 19:55
Ответы на вопрос 2
Работа с `rem` в контексте Shadow DOM действительно может быть затруднительной, поскольку единицы `rem` вычисляются на основе размера шрифта корневого элемента (`<html>` в документе). При этом Shadow DOM изолирует стили и контент, но не переопределяет базовые вычисления CSS, которые продолжают ссылаться на корневый документ.

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

### 1. Переопределение корневого размера шрифта

В Shadow DOM вы можете изменить размер шрифта для данного компонента, установив свойство `font-size` в корневом `<style>` Shadow DOM. Это приведет к тому, что все значения `rem` будут пересчитываться относительно нового размера шрифта.

```javascript
const shadowRoot = document.createElement('div').attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
  :host {
    font-size: 16px; /* Ваш размер шрифта */
  }
`;
shadowRoot.appendChild(style);
// Добавьте остальные элементы в shadowRoot
```

### 2. Использование `em` вместо `rem`

Если возможно, попробуйте использовать единицы измерения `em` вместо `rem`. `em` вычисляются относительно размера шрифта родительского элемента. Таким образом, если вы установите размер шрифта для Shadow DOM, значения `em` будут соответствовать этому размеру.

### 3. Применение CSS-переменных

Вы можете использовать CSS-переменные для управления размером шрифта и использовать эти переменные в стилях компонент. Например:

```css
:host {
  --font-size: 16px;
  font-size: var(--font-size);
}

.some-class {
  font-size: calc(var(--font-size) * 1rem); /* Используйте переменную */
}
```

### 4. Переопределение стилей UI-библиотек

Если метод с настройкой размера шрифта не подходит, вы можете переопределить стили конкретных компонентов UI-библиотек в вашем стилизованном `shadow DOM`. Это можно сделать, указав стиль для каждого необходимого элемента.

### 5. Использование встроенных стилей

При создании компонентов убедитесь, что вы устанавливаете необходимые стили в самих компонентах. Возможно, вам придется использовать встроенные стили или создавать CSS-переменные, чтобы обеспечить совместимость с размером шрифта.

### Заключение

Наиболее подходящий способ зависит от вашего конкретного приложения и требований. Используйте методы, описанные выше, чтобы обеспечить необходимую гибкость при работе с `rem` в Shadow DOM. Если ваши компоненты требуют полной изоляции и вы планируете их использовать в разных контекстах, лучше всего переопределить базовый размер шрифта в самом Shadow DOM.
Не знаю, насколько это решение правильное, но вдохновившись этим решением, я написал аналогичный плагин для Vite для замены rem на em во всём CSS во Vuetify. 
import { type Plugin } from "vite";
import postcss, { Root } from "postcss";

/**
 * @see https://github.com/vuetifyjs/vuetify/issues/12532#issuecomment-2755171940
 */
export default function vuetifyRemToEmPlugin(): Plugin {
  return {
    name: "vite-vuetify-rem-to-em",
    transform(css: string, id: string) {

      // Обрабатываем только файлы, связанные с Vuetify
      if (!id.includes("vuetify") || !id.includes(".css")) {
        return;
      }

      return postcss([
        (root: Root) => {
          root.walkDecls((decl) => {
            if (decl.value.includes("rem")) {
              decl.value = convertRemToEm(decl.value);
            }
          });
        },
      ])
        .process(css, { from: id })
        .then((result) => {
          return {
            code: result.css,
          };
        });
    },
  };
}

function convertRemToEm(value: string): string {
  return value.replace(/(\d*\.?\d+)rem/g, (match, p1) => {
    return `${parseFloat(p1).toFixed(4)}em`;
  });
}


Подключаем плагин к Vite
...
plugins: [
      vuetify(),
      vuetifyRemToEmPlugin()
    ],
...


Теперь можно задать стили глобально в shadow DOM и em будет подхватываться:

html, body, :host, :root {
    font-family: Roboto, sans-serif;
    font-size: 16px;
}
Похожие вопросы