$ sudo teach IT
Урок 5.1 — Позиционирование

Урок 5.1: Позиционирование

Модуль 5 — Продвинутый CSS

position: relative — сдвиг относительно своего места

Ты уже умеешь располагать элементы с помощью flexbox и margin'ов. Но что, если нужно сдвинуть элемент чуть-чуть в сторону, не влияя на соседей? Для этого существует position: relative.

Когда ты задаёшь элементу position: relative, он остаётся в потоке документа (то есть занимает своё обычное место), но ты можешь сдвинуть его относительно этого места с помощью свойств top, bottom, left, right.

.box {
  position: relative;
  top: 20px;    /* сдвиг вниз на 20px */
  left: 30px;   /* сдвиг вправо на 30px */
  /* или */
  bottom: 10px; /* сдвиг вверх (от низа) */
  right: 15px;  /* сдвиг влево (от правого края) */
}

Важно: original место элемента остаётся занятым. Соседние элементы не «замечают» сдвига — они стоят так, будто элемент на своём месте. Он просто рисуется со смещением.

Обычный блок
relative: top 20px, left 30px
Я не заметил сдвига

Видишь, средний блок съехал вниз и вправо, а остальные стоят как ни в чём не бывало. Они даже не знают, что средний блок сдвинулся — он по-прежнему занимает своё исходное место в потоке.

Но главная суперсила position: relative не в этом. Она в том, что он становится точкой отсчёта для абсолютно позиционированных детей. Об этом — в следующем разделе.

💡 Совет: Используй position: relative без сдвигов (просто position: relative) на родителе, если хочешь, чтобы дочерний position: absolute считал координаты от этого родителя. Это самый частый паттерн.

position: absolute — привязать к родителю

Абсолютное позиционирование — это когда элемент вырывается из потока документа и «приклеивается» к ближайшему position: relative (или к окну браузера, если такого родителя нет).

.parent {
  position: relative; /* точка отсчёта */
}

.child {
  position: absolute;
  top: 20px;    /* 20px от верха родителя */
  right: 10px;  /* 10px от правого края родителя */
}

Когда элемент получает position: absolute, он:

  • Исчезает из потока — соседние элементы его не видят и занимают его место
  • Привязывается к ближайшему родителю с position: relative (или absolute, или fixed)
  • Если такого родителя нет — привязывается к окну браузера
  • Его ширина по умолчанию сжимается под содержимое
  • Ты можешь указывать top, bottom, left, right
Я — родитель с position: relative (серый контейнер)
Обычный блок
absolute:
top 20px, right 20px
Я не вижу absolute — он выпал из потока

Обрати внимание: абсолютный блок находится в правом верхнем углу контейнера. Второй обычный блок встал прямо под первый, будто абсолютного и не было — потому что его нет в потоке.

Классический пример использования absolute — это бейдж «скидка» на карточке товара:

<div class="product-card">
  <div class="badge">-20%</div>
  <img src="product.jpg" alt="Товар">
  <h3>Название</h3>
  <p>Цена: 1000 ₽</p>
</div>

<style>
  .product-card {
    position: relative;
    /* ... стили карточки ... */
  }
  .badge {
    position: absolute;
    top: 10px;
    left: 10px;
    background: #e94560;
    color: #fff;
    padding: 4px 12px;
    border-radius: 6px;
    font-weight: bold;
  }
</style>

Бейдж приклеивается к левому верхнему углу карточки независимо от содержимого. Хоть картинка, хоть текст — бейдж всегда будет на своём месте.

Ещё один частый паттерн — центрирование абсолютного элемента. Если задать top: 50%; left: 50% и сдвинуть обратно на половину своей ширины/высоты через transform:

.centered-absolute {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

Это старый способ центрирования, который работал задолго до flexbox. Сейчас его используют реже, но иногда без него не обойтись (например, когда элемент должен быть поверх всего с position: absolute).

Блок выше отцентрирован внутри серого полупрозрачного overlay. Родитель имеет position: relative, а блок — position: absolute с top: 50%; left: 50%; transform: translate(-50%, -50%).

position: fixed — привязать к окну браузера

position: fixed похож на absolute, но с одним важным отличием: элемент привязывается к окну браузера, а не к родителю. И он не двигается при прокрутке страницы.

.header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px;
  background: #333;
  color: #fff;
  z-index: 100;
}

Этот хедер будет всегда сверху, даже если прокрутить страницу вниз на 10000 пикселей.

Fixed-элементы используются для:

  • Шапка сайта (header) — всегда видна при прокрутке
  • Кнопка «Наверх» — плавает в углу экрана
  • Cookie-уведомления — приклеиваются к низу экрана
  • Боковые панели — всегда на месте
🏠 Главная 📦 Товары 📞 Контакты 🛒 Корзина

Это имитация фиксированного хедера. Настоящий fixed-хедер остаётся на месте при скролле. Попробуй представить, что прокручиваешь страницу — хедер всегда сверху.

Чтобы хедер не перекрывал контент, добавляют padding-top для body, равный высоте хедера.

Пример: если хедер 60px, то body { padding-top: 60px; }. Иначе первый блок страницы спрячется под хедером.

body {
  padding-top: 60px; /* чтобы контент не прятался под fixed-хедером */
}

header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px;
  background: #1a1a2e;
  color: #fff;
  z-index: 1000;
  display: flex;
  align-items: center;
  padding: 0 24px;
}

Fixed-элемент не занимает места в потоке, поэтому контент «залезает» под него. Чтобы это исправить, добавляют padding-top или margin-top на следующий элемент.

💡 Совет: Не забывай про z-index для fixed-элементов. Хедер должен быть выше всего остального, иначе какой-нибудь другой элемент может перекрыть его. z-index: 1000 — стандартное значение для хедера.

z-index — кто выше

Когда элементы начинают накладываться друг на друга (absolute, fixed, relative со сдвигом), возникает вопрос: кто из них сверху? За это отвечает z-index.

.element {
  position: relative;
  z-index: 10; /* чем больше, тем выше */
}

z-index работает только на элементах с position: relative, absolute, fixed или sticky. На статичных элементах он игнорируется.

Представь, что элементы лежат стопкой. Чем выше z-index, тем выше элемент в стопке. По умолчанию z-index равен auto (или 0). Элементы с одинаковым z-index накладываются в порядке появления в HTML (тот, что позже — выше).

z-index: 1
z-index: 2
z-index: 3

Красный блок (z-index: 1) — самый нижний. Зелёный (z-index: 2) — поверх него. Фиолетовый (z-index: 3) — самый верхний. Если поменять им z-index местами, изменится и порядок наложения.

Важный нюанс: контекст наложения (stacking context). Если у родителя есть z-index, его дети не могут «перепрыгнуть» через другой родительский элемент с более высоким z-index. Иерархия контекстов:

<!-- Пример контекста наложения -->
<div style="position:relative; z-index:10;">
  <div style="position:absolute; z-index:9999;">
    Этот элемент НЕ может быть выше родителя другого контекста
  </div>
</div>

<div style="position:relative; z-index:5;">
  <div style="position:absolute; z-index:1;">
    Этот элемент ниже, хотя у его ребёнка z-index больше
  </div>
</div>

На практике это означает: если ты задал z-index: 9999 на элементе, а он всё равно под чем-то, — проверь, не находится ли он внутри контейнера с меньшим z-index, чем у того, под чем он оказался.

💡 Совет: Не используй z-index: 999999 в надежде, что это перебьёт всё. Проблема обычно не в числе, а в контексте наложения. Организуй z-index по смыслу: хедер — 100, модалка — 200, тултип — 300.

Правильная организация z-index может выглядеть так:

/* Уровни z-index */
--z-dropdown: 100;
--z-sticky: 200;
--z-fixed: 300;
--z-modal-overlay: 400;
--z-modal: 500;
--z-tooltip: 600;
--z-toast: 700;

Это CSS-переменные (кастомные свойства), которые задают системные уровни. Так ты всегда знаешь, какие значения используются, и не гадаешь, почему z-index: 9999 не работает.

Практика: модальное окно по центру, фиксированный хедер

Давай соберём всё вместе и сделаем страницу с фиксированным хедером и модальным окном по центру. Это два самых частых паттерна позиционирования в вебе.

Вот полный код. Скопируй, сохрани как HTML и открой в браузере:

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Модальное окно + хедер</title>
<style>
  *, *::before, *::after {
    box-sizing: border-box;
  }
  body {
    font-family: 'Segoe UI', system-ui, sans-serif;
    margin: 0;
    padding-top: 64px;
    background: #f5f5f5;
    color: #333;
  }

  /* Фиксированный хедер */
  .header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 64px;
    background: linear-gradient(135deg, #1a1a2e, #16213e);
    color: #fff;
    display: flex;
    align-items: center;
    padding: 0 24px;
    z-index: 1000;
    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
  }
  .header .logo {
    font-size: 1.3em;
    font-weight: bold;
    margin-right: 32px;
  }
  .header nav a {
    color: #ccc;
    text-decoration: none;
    margin-right: 20px;
    font-size: 0.95em;
  }
  .header nav a:hover {
    color: #fff;
  }

  /* Контент */
  .content {
    max-width: 700px;
    margin: 0 auto;
    padding: 40px 20px;
  }
  .content h1 {
    font-size: 2em;
    margin-bottom: 20px;
  }
  .content p {
    line-height: 1.8;
    margin-bottom: 16px;
    color: #555;
  }
  .open-modal-btn {
    display: inline-block;
    padding: 14px 32px;
    background: linear-gradient(135deg, #e94560, #c23152);
    color: #fff;
    border: none;
    border-radius: 10px;
    font-size: 1em;
    cursor: pointer;
    transition: transform 0.2s;
  }
  .open-modal-btn:hover {
    transform: scale(1.03);
  }

  /* Оверлей модалки */
  .modal-overlay {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.5);
    z-index: 2000;
    justify-content: center;
    align-items: center;
  }
  .modal-overlay.active {
    display: flex;
  }

  /* Модальное окно */
  .modal {
    background: #fff;
    border-radius: 16px;
    padding: 36px 32px;
    max-width: 420px;
    width: 90%;
    position: relative;
    box-shadow: 0 12px 40px rgba(0,0,0,0.2);
    animation: modal-in 0.25s ease-out;
  }
  @keyframes modal-in {
    from {
      opacity: 0;
      transform: scale(0.9) translateY(10px);
    }
    to {
      opacity: 1;
      transform: scale(1) translateY(0);
    }
  }
  .modal h2 {
    margin: 0 0 12px;
    font-size: 1.5em;
    color: #333;
  }
  .modal p {
    color: #666;
    line-height: 1.6;
    margin: 0 0 24px;
  }
  .modal-close {
    position: absolute;
    top: 14px;
    right: 18px;
    background: none;
    border: none;
    font-size: 1.4em;
    color: #999;
    cursor: pointer;
    transition: color 0.2s;
  }
  .modal-close:hover {
    color: #e94560;
  }
  .modal-actions {
    display: flex;
    gap: 12px;
  }
  .modal-actions button {
    flex: 1;
    padding: 12px;
    border-radius: 8px;
    border: none;
    font-size: 0.95em;
    cursor: pointer;
  }
  .modal-actions .btn-primary {
    background: linear-gradient(135deg, #e94560, #c23152);
    color: #fff;
  }
  .modal-actions .btn-secondary {
    background: #f0f0f0;
    color: #555;
  }
</style>
</head>
<body>

  <!-- Fixed Header -->
  <header class="header">
    <div class="logo">⭐ MySite</div>
    <nav>
      <a href="#">Главная</a>
      <a href="#">О нас</a>
      <a href="#">Услуги</a>
      <a href="#">Контакты</a>
    </nav>
  </header>

  <!-- Content -->
  <main class="content">
    <h1>Добро пожаловать!</h1>
    <p>
      Этот демо-проект показывает, как работает фиксированный хедер и модальное окно. Хедер приклеен к верху страницы с помощью position: fixed. Он не двигается при прокрутке.
    </p>
    <p>
      Модальное окно открывается по клику на кнопку. Оно использует position: fixed для оверлея (чтобы он был на весь экран) и центрирование через flexbox.
    </p>
    <p>
      Обрати внимание: body имеет padding-top: 64px, чтобы контент не прятался под хедером. Если убрать этот padding, первый абзац окажется под хедером.
    </p>
    <button class="open-modal-btn" onclick="document.querySelector('.modal-overlay').classList.add('active')">
      🖱️ Открыть модалку
    </button>
    <p style="margin-top:24px;">
      Попробуй проскроллить страницу вниз. Хедер остаётся на месте. Это и есть магия position: fixed.
    </p>
    <p style="height:800px; color:#ccc; text-align:center; display:flex; align-items:center; justify-content:center;">
      ↓ Скролль вниз — хедер всё ещё на месте ↓
    </p>
  </main>

  <!-- Modal Overlay -->
  <div class="modal-overlay" onclick="if(event.target===this)this.classList.remove('active')">
    <div class="modal">
      <button class="modal-close" onclick="this.closest('.modal-overlay').classList.remove('active')">✕</button>
      <h2>💬 Привет!</h2>
      <p>
        Это модальное окно. Оно отцентрировано по центру экрана с помощью flexbox на оверлее (display: flex; align-items: center; justify-content: center). Модалка имеет position: relative (по умолчанию для flex-детей).
      </p>
      <div class="modal-actions">
        <button class="btn-primary" onclick="alert('Действие выполнено!')">Подтвердить</button>
        <button class="btn-secondary" onclick="this.closest('.modal-overlay').classList.remove('active')">Отмена</button>
      </div>
    </div>
  </div>

</body>
</html>

Давай разберём, что мы используем из позиционирования:

  • Хедер: position: fixed; top: 0; left: 0; width: 100%; z-index: 1000 — всегда сверху, не прокручивается
  • Body: padding-top: 64px — чтобы контент был виден под хедером
  • Оверлей: position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 2000 — перекрывает всю страницу, включая хедер
  • Модалка: не имеет position, но центрируется через display: flex на оверлее с align-items: center; justify-content: center
  • Кнопка закрытия: position: absolute; top: 14px; right: 18px — в правом верхнем углу модалки

Обрати внимание на z-index: хедер 1000, оверлей 2000. Если бы оверлей был меньше (например, 500), хедер был бы поверх оверлея. Всегда проверяй z-index, когда что-то перекрывает то, что не должно.

А вот ещё один пример — плавающая кнопка «Наверх» (back to top). Она очень удобна на длинных страницах:

<button class="back-to-top" onclick="window.scrollTo({top:0,behavior:'smooth'})">
  ↑ Наверх
</button>

<style>
  .back-to-top {
    position: fixed;
    bottom: 30px;
    right: 30px;
    background: #e94560;
    color: #fff;
    border: none;
    padding: 14px 20px;
    border-radius: 50%;
    width: 56px;
    height: 56px;
    font-size: 1.2em;
    cursor: pointer;
    box-shadow: 0 4px 12px rgba(233,69,96,0.4);
    z-index: 999;
    transition: transform 0.2s, opacity 0.2s;
  }
  .back-to-top:hover {
    transform: scale(1.1);
  }
</style>

Эта кнопка всегда в правом нижнем углу экрана. position: fixed удерживает её на месте при прокрутке. z-index: 999 гарантирует, что она поверх контента, но под модалкой (у которой z-index 2000). Иерархия соблюдена.

Давай подведём итог. Вот шпаргалка по позиционированию:

/* ПОЗИЦИОНИРОВАНИЕ — ШПАРГАЛКА */

/* Относительное — сдвиг относительно своего места */
.relative {
  position: relative;
  top: 10px;
  left: 20px;
}

/* Абсолютное — привязка к родителю с position */
.absolute {
  position: absolute;
  top: 0;
  right: 0;
}

/* Фиксированное — привязка к окну */
.fixed {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
}

/* Центрирование absolute */
.center-absolute {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* Кто выше */
.z-index {
  z-index: 100;
}

Главное, что нужно запомнить:

  • position: relative — сдвигает элемент, не вынимая из потока, и служит точкой отсчёта для absolute
  • position: absolute — вынимает из потока, привязывается к ближайшему relative-родителю
  • position: fixed — привязывается к окну, не двигается при прокрутке
  • z-index — кто выше (работает только с position)
  • Не забывай padding-top для body при фиксированном хедере
  • Организуй z-index системно: хедер 100, оверлей 200, модалка 300

Позиционирование — мощный инструмент. С ним ты можешь создавать сложные интерфейсы: выпадающие меню, тултипы, модалки, фиксированные шапки. В следующем уроке мы разберём анимации — сделаем эти интерфейсы живыми и плавными.

💡 Задание: Создай карточку товара с position: relative и добавь на неё бейдж «Новинка» с position: absolute. Потом добавь фиксированный хедер на страницу. Посмотри, как бейдж «прилипает» к карточке, а хедер — к верху экрана.
Урок 5.1 — Позиционирование | Модуль 5: Продвинутый CSS

Урок 5.1: Позиционирование

5 вопросов