Проблемы переполнения в CSS и способы их решения

alexei15/06/2021 - 09:48
Проблемы переполнения в CSS и способы их решения

В этой статье мы рассмотрим причины возникновения проблем с переполнением в CSS и способы их решения. Мы также рассмотрим, как современные инструменты разработчика могут облегчить процесс выявления этих проблем и отладки кода.

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

Что такое проблема переполнения?

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

Это может произойти из-за непредвиденно широкого содержимого или элемента фиксированной ширины, который шире окна просмотра. Мы рассмотрим все причины в этой статье.

Как выявить переполнение

Важная часть решения этой проблемы заключается в том, чтобы заметить ее в первую очередь. Если мы знаем, когда и где это происходит, мы можем сосредоточиться на изучении этой части веб-страницы. Существуют различные способы обнаружения переполнения: от прокрутки вручную влево или вправо или с помощью JavaScript.

Давайте рассмотрим способы выявления переполнения.

1. Прокрутка влево или вправо

Первый способ обнаружить проблему переполнения – попробовать прокрутить страницу по горизонтали. Если у вас это получилось, то это явное предупреждение о том, что со страницей что-то не так.

Пример веб-страницы, которую можно прокручивать горизонтально
Если вы можете прокручивать страницу влево или вправо, то здесь действует переполнение

2. Использование JavaScript, чтобы найти элемент, который шире тела страницы

Можно добавить сниппет кода в консоль браузера, чтобы выявить любые элементы шире тела страницы. Это удобно для страниц с большим количеством элементов.


var docWidth = document.documentElement.offsetWidth;

[].forEach.call(
  document.querySelectorAll('*'),
  function(el) {
    if (el.offsetWidth > docWidth) {
      console.log(el);
    }
  }
);

3. CSS свойство outline

Применив CSS свойство outline ко всем элементам на странице, мы получим подсветку элементов, которые выходят за границы тела страницы.


* {
    outline: solid 1px red;
}

Предыдущий пример, где выделен элемент, вышедший за пределы области просмотра
Полоса прокрутки появляется, когда элемент выходит за пределы области просмотра

Можно сделать даже лучше. Есть скрипт, придуманный Эдди Османи (Addy Osmani), который добавляет к каждому элементу свойство outline случайного цвета.


[].forEach.call($$("*"),function(a){a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)})

4. Пометка "OVERFLOW" в браузере Firefox

У браузера Firefox есть полезная функция, которая сигнализирует о том, что данный элемент испытывает проблему переполнения. Хочется надеяться, что другие браузеры тоже добавят такую функцию!

Тот же схематичный пример, где показана полезная функция в Firefox
Метка "Overflow" в инструментах разработчика в браузере Firefox

5. Удаление элементов веб-страницы

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

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

Частые причины возникновения переполнения

Элементы фиксированной ширины

Одной из наиболее распространенных причин переполнения являются элементы фиксированной ширины. Строго говоря, не следует задавать фиксированную ширину тем элементам, которые должны отображаться в условиях изменяющегося размера окна просмотра.


.element {
    /* Не делайте так */
    width: 400px;
}

Cхематичный пример вывода на мобильном устройстве, где элемент с фиксированной шириной выходит за границы окна просмотра
Cхематичный пример вывода на мобильном устройстве, где элемент с фиксированной шириной выходит за границы окна просмотра

Использование flexbox без переноса

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


.parent {
    display: flex;
}

Здесь флекс элементы могут вызвать горизонтальное переполнение, если для них будет мало места для размещения в одной строке:
Флекс элементы, вызывающие горизонтальное переполнение и появляющиеся за пределами окна просмотра
Флекс элементы, вызывающие горизонтальное переполнение и появляющиеся за пределами окна просмотра

Обязательно используйте свойство flex-wrap: wrap, если предполагается, что флекс элемент будет отображаться в окне просмотра с изменяющимся размером.


.parent {
    display: flex;
    /* Делайте так */
    flex-wrap: wrap;
}

CSS Grid

Когда вы используете разметку CSS Grid, то очень важно учитывать адаптивность макета. Возьмем следующую разметку:


.wrapper {
    display: grid;
    grid-template-columns: 1fr 300px 1fr;
    grid-gap: 1rem;
}

Данный код отлично работает, но только до тех пор, пока ширина окна просмотра не станет меньше 300px. Когда это случится, возникнет переполнение.

Демонстрация переполнения, когда у грид элемента ширина 300px
Демонстрация переполнения, когда у грид элемента ширина 300px

Чтобы избежать возникновения подобной ситуации, используйте гриды только тогда, когда достаточно места. Также, можно использовать CSS медиа запросы следующим образом:


.wrapper {
    display: grid;
    grid-template-columns: 1fr;
    grid-gap: 1rem;
}

@media (min-width: 400px) {
    .wrapper {
        grid-template-columns: 1fr 300px 1fr;
    }
}

Длинные слова

Еще одной распространенной причиной переполнения становятся длинные слова, которые не помещаются в окне просмотра. Чаще всего это происходит на мобильных устройствах из-за их малой ширины окна просмотра.

Пример использования длинного слова, которое не помещается и не может поместиться в окно просмотра
Пример использования длинного слова, которое не помещается и не может поместиться в окно просмотра

Чтобы исправить это, нужно использовать CSS свойство overflow-wrap.


.article-content p {
  overflow-wrap: break-word;
}

Это особенно полезно, когда контент вводит пользователь. Отличным примером такого контента являются комментарии. Пользователь может вставить длинный URL-адрес в свой комментарий, и это должно обрабатываться свойством overflow-wrap.

Минимальный размер контента в CSS Flexbox

Еще одной интересной причиной переполнения является минимальный размер содержимого в Flexbox. Что это значит?

Еще один пример использования длинных слов, которые не помещаются в окно просмотра
Еще один пример использования длинных слов, которые не помещаются в окно просмотра

Согласно CSS спецификации:

По умолчанию флекс элементы не будут уменьшаться ниже минимального размера содержимого (длина самого длинного слова или элемента фиксированного размера). Чтобы изменить это, установите свойство min-width или min-height.

Это означает, что флекс элемент с длинным словом не будет уменьшаться ниже минимального размера содержимого.

Чтобы исправить это, для флекс элемента можно либо установить свойству overflow любое значение кроме visible, либо установить свойство min-width: 0.


.card__name {
    min-width: 0;
    overflow-wrap: break-word;
}

Длинное слово переносится на новую строку, чтобы оставаться в окне просмотра
Длинное слово переносится на новую строку, чтобы оставаться в окне просмотра

Минимальный размер контента в CSS Grid

Как и в случае с CSS Flexbox, у CSS Grid действует та же концепция минимального размера контента. При этом решение проблема переполнения несколько другое.

Пример "прорыва" гридов, когда контент не помещается в заданную ширину
Пример "прорыва" гридов, когда контент не помещается в заданную ширину

Давайте исследуем проблему. Предположим, что у нас есть боковая и основная секции, разметка которых построена на CSS гридах.


.wrapper {
    display: grid;
    grid-template-columns: 248px 1fr;
    grid-gap: 40px;
}

Также, у нас есть полоса с прокручиваемым контентом, расположенная в основной секции и с разметкой на флексбокс.


.section {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
}

Обратите внимание, что мы не используем свойство flex-wrap, потому что нам нужно, чтобы флекс элементы оставались на одной линии. Тем не менее, это не работает и вызывает переполнение.

Чтобы это исправить, нужно использовать minmax() вместо 1fr. В этом случае минимальный размер контента основного элемента не будет рассчитываться автоматически.


.wrapper {
  display: grid;
  grid-template-columns: 248px minmax(0, 1fr);
  grid-gap: 40px;
}

Отрицательные отступы

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

В следующем примере у нас есть элемент с отрицательным отступом, в документе с ориентацией текста слева направо.


.element {
    position: absolute;
    right: -100px;
}

Элемент, расположенный за пределами экрана, показан справа
Элемент, расположенный за пределами экрана, показан справа

Интересно, что если разместить элемент с другой стороны, то переполнения не будет. В чем же дело?

Элемент, расположенный за пределами экрана, показан слева
Элемент, расположенный за пределами экрана, показан слева

Вот что говорится в CSS спецификации:

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

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

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

Изображения без свойства max-width

Если не позаботиться о больших изображениях заранее, то вы обязательно встретитесь с переполнением. Чтобы этого не происходило, убедитесь, что для всех изображений установлено свойство max-width: 100%.


img {
    max-width: 100%;
}

Единицы измерения относительно окна браузера

Использование единиц измерения ширины относительно окна браузера в 100vw имеет оборотную сторону, которая выражается в возникновении переполнения, когда видна полоса прокрутки. На macOS значение 100vw отработает вполне нормально и не вызовет горизонтальную прокрутку.

На Mac OS, где полосы прокрутки скрыты, все работает нормально
На Mac OS, где полосы прокрутки скрыты, все работает нормально

В Windows полосы прокрутки выводятся по умолчанию, поэтому возникает переполнение.

На Windows при прокрутке есть горизонтальное переполнение
На Windows есть горизонтальное переполнение (Горизонтальная прокрутка)

Причина этого заключается в том, что при значении 100vw не учитывается ширина вертикальной полосы прокрутки браузера. В результате ширина будет равна 100vw плюс ширина полосы прокрутки. К сожалению, исправить это средствами CSS невозможно.

Пример страницы с окном просмотра в 100vw и 14px справа, зарезервированными под полосу прокрутки
Пример страницы с окном просмотра в 100vw и 14px справа, зарезервированными под полосу прокрутки

Тем не менее, при помощи JavaScript мы можем измерить ширину окна просмотра, исключая полосу прокрутки.


function handleFullWidthSizing() {
  const scrollbarWidth = window.innerWidth - document.body.clientWidth

  document.querySelector('myElement').style.width = `calc(100vw - ${scrollbarWidth}px)`
}

Добавленные рекламные блоки

Добавленные на страницу рекламные блоки могут стать причиной переполнения, если их ширина больше ширины их родительского элемента. Чтобы предотвратить это, следует установить свойство overflow-x: hidden у родительского элемента рекламного блока.

Область просмотра мобильного устройства с переполнением, вызванным рекламным блоком, ширина которого больше ширины самой области просмотра
Область просмотра мобильного устройства с переполнением, вызванным рекламным блоком, ширина которого больше ширины самой области просмотра

Тщательно проверяйте каждый рекламный блок на веб-сайте, чтобы он не стал причиной переполнения.

А хорошая ли идея устанавливать свойство overflow-x: hidden для тега body?

Установка свойства overflow-x: hidden, подобна наложению повязки без решения проблемы. Если у вас есть переполнение, то лучше решить саму проблему.

Кроме того, применение overflow-x: hidden к элементу body не является хорошей идеей, потому что тогда не будет работать свойство position: sticky.

Как избежать переполнения в CSS

Ниже представлены некоторые вещи, на которые следует обращать внимание, чтобы уменьшить вероятность возникновения проблемы переполнения в CSS.

Тестируйте с реальным контентом

Ничто не сравнится по эффективности с тестированием с реальным контентом. При этом вы сможете убедиться, что разметка сайта будет корректно работать с самыми различными типами контента.

Учитывайте пользовательский контент

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

Осторожно используйте CSS Grid и Flexbox

Какими бы полезными ни были CSS grid и flexbox, при неправильном использовании они могут легко вызвать переполнение. Как мы уже обсуждали выше, если не использовать свойство flex-wrap: wrap, то можно легко получить проблему переполнение. То же случится и при использовании grid-template-columns: 1fr 350px, когда ширина экрана меньше 350px.