Как создать фон над неродственными DOM-узлами?

#html #css

#HTML #css

Вопрос:

Итак, у меня есть веб-страница, хорошо организованная как таковая:

 <html>
  <head>...</head>
  <body>
    <header>header</header>
    <main style="max-width: 80vw; margin: 0 auto;">
      <section>section 1</section>
      <section>section 2</section>
      <section>section 3</section>
      <section>section 4</section>
    </main>
    <footer>footer</footer>
  </body>
</html>
 

Я хотел бы иметь фон с линейным градиентом, охватывающий заголовок и первый раздел.
Это вызывает две проблемы:

  • как создать элемент, который охватывает неродственные DOM-узлы?
  • как создать элемент, который охватывает ограниченную ширину первого раздела?

В идеале я хотел бы сохранить свою организацию HTML, потому что она семантическая.

Редактировать безошибочно-тишина-wcjkr

Комментарии:

1. Постоянны ли высоты элементов? (В этом случае 30vh или в случае медиа-запросов любое другое постоянное значение)

2. @Louis Coulet: Я придумал одно решение для вашей проблемы. Мне также понравилось работать над этим. Если мой ответ поможет вам, примите его и проголосуйте за него, чтобы другие разработчики могли получить помощь. 🙂 Спасибо

Ответ №1:

как создать элемент, который охватывает неродственные DOM-узлы?

Вы не можете. Действительный HTML требует иерархии только с одним прямым родителем (в вашем гипотетическом примере у первого section будет два: main и новый с градиентом).

Если вы хотите добиться этого эффекта, у вас есть несколько вариантов:

Измените свою иерархию

Вы могли бы сгладить свою иерархию в

 <div class="gradient-wrapper">
  <header>header</header>
  <section>section 1</section>
</div>
<section>section 2</section>
<section>section 3</section>
<section>section 4</section>
 

и наложите оригинальный стиль main на разделы, используя

 section {
  max-width: 80vw;
  margin: 0 auto;
}

.gradient-wrapper {
  background: linear-gradient(...);
}
 

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

Редактировать zealous-sound-7jvy4

Постоянная высота с помощью CSS

Если высота и расстояние до вашего первого узла section известны и постоянны, вы можете развернуть CSS-хак, чтобы сделать фон вашего header узла точно таким же, как ваш section (без изменения исходной иерархии).

 header {
  position: relative;
}

header::before {
  display: block;
  width: 100%;
  content: "";
  position: absolute;
  z-index: -1;

  --first-section-height: (1px   30vh   2px);
  height: calc(100%   var(--first-section-height));

  background: linear-gradient(...);
}
 

Для ясности я создал --first-section-height переменную, которая представляет общую высоту от нижней части вашего header до нижней части вашего section .

1px является border-bottom ли header ,
30vh является ли постоянная высота вашего section и
2px является ли его border-top и border-bottom .

Редактировать vibrant-liskov-uw2zh

Javascript

Если вы хотите сохранить исходную иерархию и хотите section , чтобы она имела переменный размер или интервал, вы можете прибегнуть к javascript, чтобы отслеживать изменения в размере окна и позиционировать a div в объединенных ограничивающих рамках двух элементов. Минимальный рабочий пример такого поведения:

 window.addEventListener("load", updateGradientBox);
window.addEventListener("resize", updateGradientBox);
updateGradientBox();

function updateGradientBox() {
  let header = document.querySelector("header");
  let section = document.querySelector("main section:first-child");
  let gradient = document.querySelector("#gradient");
  if (header === null || section === null || gradient === null) return;

  let rects = [header, section].map((el) => el.getBoundingClientRect());
  let rect = getCombinedBoundingRect(rects);

  gradient.style.top = `${rect.top}px`;
  gradient.style.left = `${rect.left}px`;
  gradient.style.width = `${rect.width}px`;
  gradient.style.height = `${rect.height}px`;
}

function getCombinedBoundingRect(rects) {
  const r = {
    top: Math.min(...rects.map((r) => r.top)),
    left: Math.min(...rects.map((r) => r.left)),
    bottom: Math.max(...rects.map((r) => r.bottom)),
    right: Math.max(...rects.map((r) => r.right))
  };
  return {
    ...r,
    width: r.right - r.left,
    height: r.bottom - r.top
  };
} 
 #gradient {
  position: absolute;
  display: block;
  z-index: -1;
  background: linear-gradient(rgba(217, 217, 217, 1) 0%, rgba(85, 85, 85, 1) 100%);
} 
 <div id="gradient"></div>
<header>header</header>
<main>
  <section>section 1</section>
  <section>section 2</section>
  <section>section 3</section>
  <section>section 4</section>
</main>
<footer>footer</footer> 

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

Редактировать naughty-fermi-0i2l5

(Пожалуйста, имейте в виду, что в моем примере я использую новейший синтаксис javascript и API; в зависимости от желаемой поддержки старых браузеров вы можете захотеть заполнить или скомпилировать его с помощью Babel)

Комментарии:

1. Спасибо, ваши 3 решения работают, вы четко объясняете различные подходы. Теперь у всех 3 есть недостатки: первый портит семантическую иерархию, второй требует CSS-вычислимых высот, третий вводит javascript для стилизации. Мне нравится ваша функция getCombinedBoundingRect!

Ответ №2:

Я придумал это решение, используя javascript. Надеюсь, я правильно понял проблему 🙂

Основная идея состоит в том, чтобы динамически получать точные высоты первого header и первого section и устанавливать градиент на теле до этой высоты.

Codepen: https: //codesandbox.io/s/the-gradient-stuff-h3yx1

 const header = document.querySelector("header");
const section1 = document.querySelector("main section:first-child");

const headerHeight = header.getBoundingClientRect().height;
const section1Height = section1.getBoundingClientRect().height;

let totalHeight = headerHeight   section1Height;

console.log(totalHeight);

const body = document.querySelector("body");
console.log(body);
body.style.backgroundImage = `linear-gradient(to bottom, #33ccff 0%, #ff99cc ${totalHeight}px)`;
body.style.backgroundSize = `100% ${totalHeight}`;
body.style.backgroundRepeat = "no-repeat";
body.style.backgroundSize = `100% ${totalHeight}px`; 
 <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Static Template</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      header,
      footer,
      section {
        height: 30vh;
        border: 1px solid black;
      }
    </style>

    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <header>header</header>
    <main style="max-width: 80vw; margin: 0 auto;">
      <section>section 1</section>
      <section>section 2</section>
      <section>section 3</section>
      <section>section 4</section>
    </main>
    <footer>footer</footer>

    <script src="./script.js"></script>
  </body>
</html> 

Комментарии:

1. Спасибо, ваше решение работает! Хорошая идея использовать getBoundingClientRect в javascript.

2. Хотя вы были самым быстрым, и ваш ответ точен, я должен принять ответ @soimon, поскольку он предоставляет 3 альтернативы. Большое вам спасибо за уделенное время, до встречи.

3. @LouisCoulet : Дорогой друг ! Я здесь, чтобы помочь… Вы можете даже проголосовать против… Вряд ли это имеет значение. Но это всего лишь совет… Позиция: абсолютная; и заголовок: псевдо-решение не является хорошим.. Избегайте этого… Остальное в порядке 🙂 Это может что-то испортить позже в вашем коде.. Когда вы говорите, что нужна панель навигации и подменю, то управление z-индексами — это дополнительная нагрузка 🙂