Nadpisanie zmiennej CSS na elemencie potomnym nie powoduje ponownego zbudowania złożonej właściwości niestandardowej odziedziczonej po elemencie nadrzędnym. Element nadrzędny już rozwiązał zagnieżdżone odwołanie. Aby użyć wartości elementu potomnego, zadeklaruj właściwość złożoną na tym elemencie albo złóż tam końcową właściwość.

Ten poradnik wyjaśnia to zachowanie i zawiera możliwe do odtworzenia ćwiczenie na komputerze Mac w przeglądarce Chrome. Zadanie polega na tym, by obramowanie komponentu używało jego lokalnego nadpisania koloru, a jednocześnie zachowało wspólne wartości domyślne.

Dlaczego odziedziczona właściwość złożona zachowuje kolor elementu nadrzędnego?

Rozważmy poniższy fragment CSS. Zakłada on element .sample-tile wewnątrz elementu .sample-board:

.sample-board {
  --sample-color: teal;
  --sample-border: 6px solid var(--sample-color);
}

.sample-tile {
  --sample-color: darkorange;
  border: var(--sample-border);
}

Obramowanie elementu potomnego pozostaje turkusowe. Specyfikacja CSS Variables stanowi, że funkcje var() w zagnieżdżonych właściwościach niestandardowych są rozwiązywane na etapie obliczania wartości, przed dziedziczeniem. W efekcie element potomny dziedziczy właściwość złożoną równoważną 6px solid teal; nie dziedziczy wyrażenia, które podstawi wartość --sample-color elementu potomnego. CSS Custom Properties for Cascading Variables Module Level 1.

W tym przypadku chodzi o dwie odrębne właściwości. Ustawienie --sample-color na elemencie potomnym zmienia lokalną wartość tej właściwości. Nie deklaruje jednocześnie nowej wartości --sample-border. Dokumentacja MDN dotycząca właściwości niestandardowych opisuje zwykłe, niezarejestrowane właściwości niestandardowe jako dziedziczone, a ich wartości obliczone — jako te, w których zmienne zostały podstawione. Custom properties: CSS variables.

W tym przykładzie kolejność jest następująca:

  1. Element nadrzędny oblicza --sample-color jako teal.
  2. Jego --sample-border podstawia ten kolor, tworząc 6px solid teal.
  3. Element potomny dziedziczy tę właściwość złożoną i osobno ustawia własny token koloru na darkorange.
  4. Deklaracja border elementu potomnego używa odziedziczonej turkusowej właściwości złożonej.

Nie jest to trwały zapis stanu elementu nadrzędnego. Jeśli zmieni się jego kolor, właściwość złożona może zostać zaktualizowana i ponownie odziedziczona. Granicą jest element, na którym rozwiązywane jest zagnieżdżone odwołanie: nadpisanie w elemencie potomnym nie wpływa wstecz na to obliczenie. CSS Variables: resolving dependencies.

Odtwórz to zachowanie w Chrome na komputerze Mac

Użyj pustej strony, aby istniejące style strony nie zakłócały ćwiczenia. Otwórz nową kartę Chrome, wpisz about:blank w pasku adresu, a następnie naciśnij Command + Option + J, aby otworzyć Console. Google opisuje ten skrót dla komputerów Mac w przewodniku DevTools. Open Chrome DevTools.

Poniższy kompletny program do konsoli przeglądarki tworzy trzy podpisane abstrakcyjne kształty i ich arkusz stylów. Nie wymaga bibliotek, obrazów, żądań sieciowych ani istniejących elementów strony. Uruchom cały blok na pustej stronie. Oczekiwane wyniki poniżej wynikają z opisanych reguł CSS; nie są przedstawione jako pomiary zarejestrowane w przeglądarce.

(() => {
  const board = document.createElement("section");
  board.className = "sample-board";

  const style = document.createElement("style");
  style.textContent = `
    .sample-board {
      --sample-color: teal;
      --sample-border: 6px solid var(--sample-color);
      display: flex;
      flex-wrap: wrap;
      gap: 24px;
      padding: 32px;
      font: 16px system-ui;
    }

    .sample-board .sample-tile {
      --sample-color: darkorange;
      display: grid;
      place-items: center;
      width: 120px;
      height: 80px;
      border: var(--sample-border);
      border-radius: 24px;
      background: white;
      color: black;
    }

    .sample-board .sample-local {
      --sample-border: 6px solid var(--sample-color);
    }

    .sample-board .sample-direct {
      border: 6px solid var(--sample-color);
    }
  `;

  const samples = [
    ["Inherited", "sample-inherited"],
    ["Local", "sample-local"],
    ["Direct", "sample-direct"]
  ];

  const tiles = samples.map(([label, className]) => {
    const tile = document.createElement("div");
    tile.className = `sample-tile ${className}`;
    tile.textContent = label;
    board.append(tile);
    return tile;
  });

  document.head.append(style);
  document.body.append(board);

  function report(stage) {
    console.log(stage);
    console.table(tiles.map((tile) => {
      const computed = getComputedStyle(tile);
      return {
        sample: tile.textContent,
        localColor: computed
          .getPropertyValue("--sample-color").trim(),
        composite: computed
          .getPropertyValue("--sample-border").trim(),
        paintedBorder: computed.borderTopColor
      };
    }));
  }

  report("Initial values");
  board.style.setProperty("--sample-color", "rebeccapurple");
  report("After changing the parent color");
})();

Program wyświetla dwie tabele. Oczekiwane kolory obramowania:

Próbka Początkowo Po zmianie elementu nadrzędnego Przyczyna
Inherited Turkusowy Fioletowy Używa obliczonej właściwości złożonej elementu nadrzędnego
Local Pomarańczowy Pomarańczowy Deklaruje właściwość złożoną na elemencie potomnym
Direct Pomarańczowy Pomarańczowy Podstawia lokalny kolor bezpośrednio w border

Wszystkie trzy elementy potomne zachowują darkorange jako lokalny token koloru. Przydatna diagnostycznie jest próbka Direct: jej odziedziczona właściwość złożona nadal zawiera kolor elementu nadrzędnego, ale obramowanie jest wyświetlane w lokalnym kolorze, ponieważ deklaracja obramowania nie używa właściwości złożonej.

getComputedStyle() odczytuje rozwiązane wartości właściwości po zastosowaniu aktywnych arkuszy stylów. Może więc pokazać, jaką wartość element rzeczywiście otrzymuje, a nie tylko zapisane wyrażenie. Serializacja koloru może zwrócić wartość rgb() zamiast pierwotnej nazwy koloru. Window: getComputedStyle() method.

Odśwież pustą kartę, aby wyczyścić ćwiczenie. W przypadku własnej strony umieść wybraną poprawkę CSS w jej arkuszu stylów; zmiany w konsoli pomagają w diagnostyce, ale nie edytują plików źródłowych.

Którą poprawkę wybrać?

Zadeklaruj właściwość złożoną na granicy komponentu

Próbka Local zachowuje wielokrotnego użytku token obramowania i deklaruje jego regułę na tym samym elemencie, na którym podano nadpisanie. Oto odpowiedni fragment CSS z programu:

.sample-board .sample-local {
  --sample-color: darkorange;
  --sample-border: 6px solid var(--sample-color);
  border: var(--sample-border);
}

Wybierz ten wzorzec, gdy kilka deklaracji potrzebuje tej samej złożonej wartości. Umieść podstawowe wartości domyślne na elemencie nadrzędnym, a deklarację właściwości złożonej — na komponencie, który odpowiada za dostosowanie. Właściwości niestandardowe można deklarować na konkretnych elementach; dziedziczą je ich elementy potomne, więc nie muszą znajdować się na :root. Using CSS custom properties.

Położenie deklaracji ma znaczenie również niżej w drzewie. Jeśli element wnuka ponownie nadpisze podstawową wartość, odziedziczona właściwość złożona z tego komponentu nadal będzie odzwierciedlać obliczenie wykonane przez ten komponent. Ponownie zadeklaruj regułę na każdej granicy elementu potomnego, który ma złożyć inną wartość.

Złóż końcową właściwość w miejscu jej użycia

Próbka Direct usuwa pośredni token z deklaracji obramowania. Oto kluczowa reguła tego fragmentu CSS:

.sample-board .sample-direct {
  --sample-color: darkorange;
  border: 6px solid var(--sample-color);
}

Wybierz to rozwiązanie, gdy połączenia potrzebuje tylko jedna deklaracja. Nadal możesz współdzielić osobne tokeny szerokości, stylu i koloru, a następnie złożyć je w deklaracji border komponentu. MDN wyjaśnia, że var() może podstawiać całą wartość innej właściwości lub jej część. Using CSS custom properties.

Nadpisz całą właściwość złożoną, jeśli to ona jest publicznym parametrem

Czasami zamierzonym sposobem dostosowania komponentu jest zmiana całego obramowania. W takim przypadku podaj lokalną wartość zastępczą --sample-border, na przykład 6px solid darkorange, zamiast zmieniać tylko wcześniejsze dane wejściowe.

Przy wyborze kontraktu komponentu skorzystaj z tej podpowiedzi:

Zamierzone dostosowanie Odpowiedni wzorzec
Jeden lokalny kolor steruje jednym obramowaniem Złóż bezpośrednio w border
Kilka deklaracji współdzieli jedną złożoną wartość Zadeklaruj właściwość złożoną na komponencie
Wywołujący podaje całe obramowanie Nadpisz samą właściwość złożoną
Elementy potomne mają używać dokładnie tej wartości złożonej, którą utworzył element nadrzędny Zachowaj dziedziczoną właściwość złożoną

Dlaczego swoistość, wartość awaryjna ani słowa kluczowe dziedziczenia jej nie przebudowują?

Najpierw sprawdź, czy nadpisanie podstawowej wartości rzeczywiście zadziałało. Jeśli rozwiązana wartość --sample-color na elemencie potomnym to już darkorange, nadanie tej samej deklaracji większej swoistości nie zmieni odrębnej, odziedziczonej właściwości --sample-border. Obie właściwości mają już wartości; problem polega na tym, gdzie złożono właściwość złożoną.

Podobnie --sample-border: inherit pobiera obliczoną wartość elementu nadrzędnego. Zwykłe dziedziczenie przekazuje obliczone wartości, zamiast ponownie uruchamiać zapisaną deklarację elementu nadrzędnego w kontekście potomka. Inheritance.

Wartość awaryjna, taka jak var(--sample-border, 6px solid darkorange), również nie zmienia prawidłowej, odziedziczonej turkusowej wartości obramowania. Wartość awaryjna var() obsługuje właściwość niestandardową bez użytecznej wartości; nie porównuje jej z preferowanymi danymi wejściowymi potomka. var() CSS function.

Nie myl nieprawidłowych wartości z tym problemem dziedziczenia. Na przykład niezarejestrowany token zawierający 12px może istnieć jako wartość właściwości niestandardowej, ale po podstawieniu do border-color dać nieprawidłowy wynik. Wartość awaryjna wewnątrz wywołania var() nie jest uniwersalnym mechanizmem sprawdzania typów. MDN wyjaśnia, dlaczego podstawienie właściwości niestandardowej może tworzyć deklaracje, które stają się nieprawidłowe na etapie obliczania wartości. Using CSS custom properties.

Czy rejestracja zmiennej za pomocą @property rozwiązuje problem?

@property pozwala określić składnię właściwości niestandardowej, jej wartość początkową i sposób dziedziczenia. Nie sprawia jednak, że odziedziczona właściwość złożona elementu nadrzędnego jest ponownie obliczana na podstawie nadpisanych danych wejściowych elementu potomnego. Zarejestrowanie podstawowej właściwości z inherits: false zmieniłoby sposób uzyskiwania jej wartości, a zarejestrowanie w ten sposób właściwości złożonej zatrzymałoby jej zwykłe dziedziczenie. Żadne z tych rozwiązań nie dostarcza brakującej lokalnej reguły. @property CSS at-rule.

Rejestracja ma też inne ograniczenia zgodności. MDN oznacza zwykłe właściwości niestandardowe jako szeroko obsługiwane przez przeglądarki od kwietnia 2017 r., podczas gdy @property osiągnęło poziom Baseline w lipcu 2024 r. i może nie być dostępne w starszych przeglądarkach. Żadna z dwóch pokazanych poprawek nie wymaga rejestracji. Custom properties: CSS variables, @property CSS at-rule.

Jak diagnozować ten sam problem w swoim arkuszu stylów

Zastosuj trzykOLUMNowe porównanie z ćwiczenia do elementu, którego wygląd jest nieprawidłowy:

  1. Odczytaj token podstawowy, taki jak --sample-color.
  2. Odczytaj token złożony, taki jak --sample-border.
  3. Odczytaj wyświetlaną właściwość, taką jak border-top-color.
  4. Znajdź element, do którego stosuje się deklaracja właściwości złożonej.
  5. Przenieś tę deklarację lub powtórz ją na zamierzonej granicy dostosowania albo złóż wartość bezpośrednio w końcowej właściwości.

Aby znaleźć odpowiednie reguły w Chrome, kliknij kształt prawym przyciskiem myszy i wybierz Inspect. DevTools zaznaczy element na karcie Elements, a karta Styles pokaże zastosowane reguły CSS. Open Chrome DevTools.

Jeśli podstawowy token ma oczekiwaną lokalną wartość, ale właściwość złożona nadal zawiera wartość przodka, najpierw sprawdź miejsce deklaracji właściwości złożonej. Zdefiniuj regułę tam, gdzie powinny być odczytywane jej dane wejściowe, a następnie zweryfikuj zarówno rozwiązaną wartość tokenu, jak i wyświetlony wynik. Dzięki temu komponent ma jasno określoną i przewidywalną granicę dostosowania.

Źródła