Что происходит при создании контейнера Flexbox?

https://www.smashingmagazine.com/2018/08/flexbox-display-flex-container/
  • Перевод

Это перевод статьи Rachel Andrew, являющейся одним из разработчиков спецификаций CSS.


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


В этой статье мы подробно рассмотрим, что на самом деле происходит при добавлении display: flex в вашу таблицу стилей.


Flex контейнер, пожалуйста!


Чтобы использовать Flexbox, вам нужен элемент, который будет flex контейнером. В вашем CSS вы используете display: flex:



Давайте немного поразмышляем о том, что на самом деле означает display: flex. В спецификации Display Module Level 3 каждое значение свойства display описывается как комбинация двух элементов: внутренней и внешней модели отображения.


Когда мы добавляем display: flex, мы в действительности определяем display: block flex. Внешний тип отображения нашего flex-контейнера блок; он действует как элемент уровня блока в стандартном потоке. Внутренний тип отображения flex, поэтому элементы непосредственно внутри нашего контейнера будут участвовать во flex-компоновке.


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


Мы также можем назначить нашему контейнеру значение inline-flex, использовав display: inline flex, т.е. flex-контейнер действует как элемент уровня строки с дочерними элементами, которые участвуют во Flex компоновке. Дочерние элементы нашего строчного flex-контейнера ведут себя так же, как и дочерние элементы нашего блочного flex-контейнера; разница заключается в том, как сам контейнер ведет себя в общем макете.



Эта концепция поведения элементов, имеющих внешний тип отображения, определенный как блок на странице (плюс внутренний тип отображения) диктует, как ведут себя их дети, весьма полезна. Вы можете применить эти рассуждения к любому блоку в CSS. Как действует этот элемент? Как действуют дети этого элемента? Ответы относятся к их внешним и внутренним моделям отображения.


Строки Или Столбцы?


Как только мы определили наш flex-контейнер, вступают в игру некоторые начальные значения. Без добавления дополнительных свойств flex-элементы отображаются в виде строки. Это происходит потому, что начальным значением свойства flex-direction является row. Если вы не измените его, то получите ряд.


Свойство flex-direction определяет направление главной оси. Оно может принимать и другие значения:


  • column;
  • row-reverse;
  • column-reverse.

Наши элементы в один ряд будут размещены, начиная со стартового ребра, в направлении строки и отображаться в порядке, в котором они появляются в источнике. В спецификации это ребро называется как main-start:


Рис_1
Рис_1. main-start — начало строчного направления


Если мы используем значение column, элементы начнут располагаться от стартового ребра в блочном направлении и, следовательно, образуют столбец.


Рис_2
Рис_2. main-start — начало блочного направления


Если мы используем row-reverse, расположение main-start и main-end поменяются местами; поэтому элементы будут выкладываются один за другим в обратном порядке.


Рис_3
Рис_3. main-start — от конца строчного направления


Значение column-reverse делает то же самое.


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


Также важно помнить, что, когда это происходит, это чисто визуальный эффект. Мы просим элементы отображать себя, начиная с конечного ребра; они все еще текут в том же порядке, и это тот порядок, который использует ваш экранный ридер, а также порядок, в котором они могут быть протабулированы. Вы никогда не должны использовать row-reverse, если вы действительно хотите изменить порядок элементов. Для этого внесите изменения в исходный документ.


Две оси flexbox


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


Мы уже сталкивались с главной осью, т. е. осью, которую вы определяете как значение flex-direction. Поперечная ось это другое направление. Если вы установили flex-direction: row, ваша главная ось находится вдоль строки, а поперечная — вниз по столбцу. С помощью flex-direction: column главная ось расположится вниз по столбцу, а поперечная — вдоль строки. Именно здесь нам нужно рассмотреть еще одну важную особенность Flexbox, и это тот факт, что он не привязан к физическим направлениям экрана. Мы не говорим о строке, идущей слева направо, или столбце сверху вниз, потому что это не всегда так.


РЕЖИМЫ ЗАПИСИ


Когда я выше описывала строку и столбец, я упомянула блочное и строчное направления. Эта статья написана на английском языке, который имеет горизонтальный режим записи. Это означает, что когда вы попросите Flexbox предоставить вам строку, вы получите горизонтальное отображение ваших flex элементов. В этом случае main-start находится слева — на месте, с которого начинаются предложения на английском языке.


Если бы я работала на языке справа налево, таком как арабский, то стартовое ребро было бы справа:



Начальные значения flexbox означают, что если все, что я делаю, это создаю контейнер flex, то мои элементы будут начинаться справа и отображаться, перемещаясь в левую сторону. Стартовое ребро в строчном направлении — это место, где предложения начинаются в используемом режиме записи.


Если вы окажетесь в вертикальном режиме записи и обратитесь к строке, ваша строка будет работать вертикально, потому что именно так строки текста выполняются на вертикальном языке. Это можно попробовать, добавив свойство режима записи в контейнер flex и установив для него значение vertical-lr. Теперь, когда вы устанавливаете flex-direction в row, вы получите вертикальный столбец элементов.



Таким образом, ряд может работать горизонтально, с main-start слева или справа, а также работать вертикально с main-start вверху. Это все еще flex-direction строка, даже если нашим умам, привычным к горизонтальному тексту, трудно думать о строке, работающей вертикально!


Чтобы элементы располагались в блочном направлении, мы задаем для свойства flex-direction значение column или column-reverse. На английском (или арабском) языке мы видим элементы, отображаемые один над другим вниз по странице, начиная с верха контейнера.


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



Однако независимо от того, в каком направлении отображаются блоки, если вы работаете со столбцом, вы работаете в блочном направлении.


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


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


В качестве резюме, запомните, что:


  • flex-direction: row
    • главная ось = рядное направление (inline dimension);
    • main-start будет там, где начинаются предложения в этом режиме записи;
    • поперечная ось = блочное направление (block dimension);
  • flex-direction: column
    • главная ось = блочное направление (block dimension);
    • main-start будет там, где блоки начинают выкладываться в этом режиме записи ;
    • поперечная ось = рядное направление (inline dimension).

Начальное выравнивание


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


Примечание: стоит отметить, что, хотя эти свойства выравнивания начали жизнь в спецификации Flexbox, спецификация Box Alignment в конечном счете заменит их, как указано в спецификации Flexbox.

ВЫРАВНИВАНИЕ ГЛАВНОЙ ОСИ


Начальное значение свойства justify-content установлено в flex-start. Это как бы наш CSS был таким:


.container {
    display: flex;
    justify-content: flex-start;
}

Это является причиной того, что наши flex элементы выстраиваются в строку от стартового ребра flex-контейнера. Это также причина того, почему, когда мы задаем row-reverse, они переключаются на конечное ребро, т.к. теперь оно становится началом главной оси.


Если вы видите свойство выравнивания, которое начинается с justify-, то оно применяется к главной оси Flexbox. Таким образом, justify-content выполняет выравнивание вдоль главной оси и располагает наши элементы в ее начале.


Другие возможные значения для justify-content:


  • flex-end;
  • center;
  • space-around;
  • space-between;
  • space-evenly (добавлено в Box Alignment).

Эти значения занимаются распределением свободного пространства flex-контейнера. Вот почему элементы располагаются рядом или отстоят друг от друга. Если вы добавите justify-content: space-between, то любое доступное пространство распределится между всеми элементами. Однако, это может произойти только если свободное пространство имеется. Если бы у вас был плотно упакованный flex-контейнер (без дополнительного пространства после того, как все элементы были выложены), то justify-content вообще ничего не сделало бы.


Вы можете увидеть это, если переключите flex-direction на столбец. Без высоты у flex-контейнера нет свободного места, поэтому установка параметра justify-content: space-between ничего не даст. Если добавить высоту и сделать так, чтобы контейнер был выше, чем требуется для отображения элементов, то свойство будет иметь эффект:



ВЫРАВНИВАНИЕ ВДОЛЬ ПОПЕРЕЧНОЙ ОСИ


Элементы также выравниваются в одну строку вдоль поперечной оси flex-контейнера. Выравнивание, которое мы выполняем, заключается в выравнивании блоков друг относительно друга в ряд. В следующем примере один из наших блоков имеет больше содержимого, чем все остальные. Что-то подсказывает другим блокам растянуться до той же высоты. Это свойство align-items, которое имеет начальное значение stretch:



Когда вы видите свойство выравнивания, которое начинается с align- и вы находитесь во flexbox, то вы имеете дело с выравниванием вдоль поперечной оси, и align-items выравнивает элементы вдоль flex строки. Другие возможные значения:


  • flex-start;
  • flex-end;
  • center;
  • baseline.

Если вы не хотите, чтобы все блоки растягивались на высоту самых высоких, то установка align-items: flex-start приведет к выравниванию их всех по начальному краю поперечной оси.



Начальные значения для элементов Flex


Наконец, сами flex элементы также имеют начальные значения, они установлены в:


  • flex-grow: 0;
  • flex-shrink: 1;
  • flex-basis: auto.

Это означает, что наши элементы не будут расти по умолчанию, чтобы заполнить доступное пространство на главной оси. Если же для параметра flex-grow задать положительное значение, элементы будут расти и занимать любое доступное пространство.


Однако блоки могут сжиматься, так как flex-shrink имеет положительное значение 1. Это означает, что если у нас есть очень узкий flex-контейнер, то элементы будут настолько малы, насколько это возможно, до того, как произойдет переполнение. Это разумное поведение; в общем, мы хотим, чтобы они оставались внутри своих блоков и не переполнялись, если есть место для их отображения.


Чтобы получить наилучший макет, свойство flex-basis по умолчанию имеет значение auto. Мы поймем, что это означает, в следующей статье этой серии, однако, большую часть времени вы можете думать об auto как о “достаточно большом значении, чтобы вместить содержимое”. Вы увидите, что, если у вас есть гибкие элементы, которые заполняют контейнер, и один из них имеет больший объем содержимого, чем другие, то ему будет предоставлено больше места.



Это гибкость Flexbox в действии. При flex-basis в auto и отсутствии указания размеров элементов, они имеют базовый размер max-content. Это был бы размер, когда бы они растянулись и не выполняли перенос строки. В этом случае свободное пространство делится между элементами в пропорции, описанной в следующем примечании спецификации flexbox.


"Примечание: при распределении недостающего пространства коэффициент сжатия flex умножается на базовый размер flex. Это распределяет недостающее пространство пропорционально тому, как элемент может сжаться, так что, например, небольшой элемент не будет сжиматься до нуля, пока более крупный не будет заметно уменьшен.”

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


Рис_4
Рис_4. Элементы переносятся для придания большему элементу больше места


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


Я затронула здесь лишь некоторые детали, но мы рассмотрим эти алгоритмы позже в этой серии.


Заключение


В этой статье я взяла начальные значения Flexbox, чтобы объяснить, что на самом деле происходит, когда вы указываете display: flex. Это удивительная сумма, как только вы начинаете ее распаковывать, и, содержащиеся в ней несколько свойств оказываются многими ключевыми характеристиками гибких макетов.


Flex макеты гибки: они стараются сделать по умолчанию правильный выбор о вашем содержимом — сжимаются и растягиваются, чтобы получить лучшую читаемость. Гибкие макеты поддерживают режим записи: направления строк и столбцов связаны с используемым режимом записи. Гибкие макеты позволяют выравнивать элементы как группу по главной оси, выбирая способ распределения пространства. Они позволяют выравнивать элементы вдоль своей гибкой линии, перемещать элементы вдоль поперечной оси друг относительно друга. Важно отметить, что гибкие макеты понимают объем вашего контента, и попытаются принять правильные основные решения, чтобы отобразить его.


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

Поделиться публикацией

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

    +1
    Может, я просто старый стал, но мне так хочется, чтобы наступил чудесный момент, когда разработчики браузеров соберутся, и разработают с нуля новую спецификацию (желательно не взяв в команду тех ребят, которые являются членами современного W3C), полностью несовместимую со старой, в которой не будет ни таблиц, ни блоков, ни флексов, ни гридов. А будет одна простенькая схема с точками привязки, режимом заполнения и режимом выравнивания. Как это делалось двадцать лет назад в десктопных приложениях. И в которой не нужно будет лепить костыли, чтобы получились одинаковые поля слева и справа, чтобы сделать колонки одинаковой высоты и т.д.
    И да, ещё чтобы при манипуляциях с DOM в JS можно было использовать две простенькие функции: «BeginUpdate», «EndUpdate», которые соответственно блокируют визуальную перестройку страницы перед изменениями и применяют изменения после того, как скрипт закончил изменять DOM.
      +2
      Увы, деградировать может только человеческое сознание… Поэтому не ждите невыполнимого события.

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

      С другой стороны мы с вами просто сфера обслуживания, как дворники, работники столовой или швея-мотористка: делаем все, чего захочет массовый пользователь. Раньше хватало и блокнота для создания сайта, а теперь, вот видите, Flexbox и Grid'ы…

      Вижу, что перевод понравился, попробую продолжить работу в этом направлении.
        +1

        DrPass если пример DisplayObject во flash никого не вдохновил и не собрал, то уже и не соберёт. Многим видимо нравится заниматься мазохизмом. И даже выгодно. Потому что CSS сродни жречеству. Своя каста — свои правила.

          +3
          Действительно! Ведь насколько красивее, функциональнее и богаче были десктопные приложения 20 лет назд. Современному вебу до них еще расти и расти.
            +2
            Вы зря иронизируете. Что касается красоты, десктопные приложения 20 лет назад не имели анимашечек не из-за недостатков архитектуры, а просто из-за слабости компьютеров тех лет и просто потому, что мода на это не вошла. Добавить всякие эффекты/трансформации и стили современного дизайна в библиотеки виджетов нет особой проблемы.
            А что касается функциональности/насыщенности фичами, дык вы, сами того не зная, попали прям в точку — современному вебу до них ещё расти и расти. Разве может, например, Google Docs потягаться по функциональности с Microsoft Word 7 как раз двадцатилетней давности? А Gmail с Outlook, да или даже The Bat?
              +4
              Не ради спора, но в качестве заметки на полях: не совсем корректно считать браузерное ПО и десктопное аналогами. Это примерно как в quake2 играть на прохождение и в мультиплеере: формально игра одна, а по сути — две разные.

              MS Word 20-летней давности был заточен под глубокую работу с текстом силами одного пользователя. Поэтому основная функциональность связывалась именно с текстом. И положа руку на сердце, средний офисный клерк осваивал ее от силы процентов на двадцать, потому что для повседневной работы она была явно избыточной.

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

              По особенностям интерфейсов есть ещё нюанс. Браузерное ПО сталкивается с целым рядом проблем, с которыми десктопное ПО вообще толком не имело дел. Даже простое разнообразие устройств сказывается: одно дело потянуть ширину контейнера с 640 до 800 px, другое — на лету перекомпоновать все внутренности под отображение на утюге :) Вся эргономика меняется. А есть ещё подтяжка чужого контента и др. У современного ПО вообще динамика изменений в интерфейсе выше намного. Утрирую, но просто для иллюстрации мысли: представьте себе задачу собрать интерфейс «Вконтакика» силами какого-нибудь Borland Delphi 7 :) С деталями, чтобы в реальном времени видеть как люди печатают сообщения, ставят лайки, уходят оффлайн и вот это вот всё. Дело не в красотах и анимациях. Динамика другая. «Физика» другая. Эргономика другая.

              Словом, вряд ли дело вообще в ПО или в том, что раньше деревья были зеленее. Просто жизнь идёт и мир меняется.

              P.S. Есть на этот счёт хорошая байка про учебник пилотов сверхзвуковых истребителей: «полет — это непрерывная цепочка неправильных решений». Со спецификацей примерно так же.
                0
                современному вебу до них ещё расти и расти

                Никому ни до кого расти не надо, каждый продукт, в свое время реализует то, что нужно его аудитории, или перестает развиваться и умирает. Gmail не конкурент Outlook в офисе, но практически никому и не нужна вся эта мощь дома, подтверждение тому — популярность Gmail.


                Разве может, например, Google Docs потягаться по функциональности с Microsoft Word 7

                А что скажете про Office 360?


                Вообще, достаточно взглянуть на проекты вроде VSCode и Atom, чтобы перестать волноваться о том, насколько ограничена платформа.

              +2
              1. Есть работающий стандарт, но с некоторыми хаками и неудобствами
              2. Давайте сделаем новый стандарт с нуля, чтобы все было проще и понятнее!
              3. В процессе всплывают граничные случаи и всякие неочевидности, кое-как решаются
              4. Приходим в пункт 1
                0
                И называется он QML
                  +2
                  А будет одна простенькая схема с точками привязки, режимом заполнения и режимом выравнивания.

                  Флекс как раз и является простенькой схемой укладки. Даже еще более простой (но нисколько не менее полной), чем ваши идеалы 20-летней давности.
                    –1

                    БегинАпдейт говорите? Это ж джаваскрипт, который без RAII, даже урезанного, вроде шарповского using или java-вого try-with-resource, одно исключение и страница зависла. Более того, это javascript, в котором всё асинхронно, и многие функции, которые могут отдать результат синхронно, сделаны асинхронными либо потому, что первый вызов требует получить какие-то данные или же просто потому, что где-то ниже по стеку необходимо (либо может понадобиться в будущем) вызвать асинхронную функцию. Ваш ЕндАпдейт либо будет выполнен через неизвестное время, либо не будет поддерживать асинхронность, т.е. будет

                      0

                      На самом деле пересчет и перерисовка объектов происходит лениво. Достаточно просто не спрашивать о размерах объекта в цикле обновлений. Вот пример:


                      var div = document.createElement('div');
                      document.body.appendChild(div);
                      
                      // здесь нет обращения к размерам объекта в цикле
                      console.time('no read');
                      for(var i = 0; i < 1000; i++) {
                        div.style.width = i + 'px';
                      }
                      console.log(div.offsetWidth);
                      console.timeEnd('no read'); // ~3ms
                      
                      // а здесь есть
                      console.time('read');
                      var savedWidth;
                      for(var i = 0; i < 1000; i++) {
                        div.style.width = i + 'px';
                        savedWidth = div.offsetWidth; // читаем размер элемента, вызывается перерисовка
                      }
                      console.log(savedWidth);
                      console.timeEnd('read'); // ~ 50ms

                      Первое демо отрабатывет быстрее, потому что не делает пересчет лейаута на каждый шаг цикла.

                        0
                        Хм, для меня это открытие. Я думал, что до завершения обработки текущего эвента браузер вообще не будет лэйаут трогать, поэтому beginUpdate/endUpdate, работающие только на синхронном участке, бессмысленны. Оказывается можно заставить пересчитать. Ну, в таком варианте можно придумать рабочий beginUpdate/endUpdate, если вызывается что-то библиотечное, которое внутри может лэйаут дёрнуть. Ну и естественно, чтоб по завершению обработки текущего эвента открытые beginUpdate закрывались. Хотя, с таким же успехом можно просто убрать возможность вызвать пересчёт, с современным JS с его async/await не проблема отложить получение информации до следующего эвента.
                    +9
                    Я уж думал, в статье будет низкоуровневое описание того, что браузер делает, когда встречает Flex-контейнер, какие вычисления проводит…
                      +5
                      Аналогично. Уже предвкушал описание внутренних алгоритмов, а тут очередная вариация… Жаль.
                        0
                        Автор статьи — член группы редакторов спецификации CSS. Вот она и объясняет принципы работы элементов CSS. Да и теги, указанные внизу, ну никак не содержат даже ссылок на браузеры. Прошу извинить меня, что не оправдал ваших ожиданий.
                          +1
                          Я тоже подумал, что статья будет о производительности flex, как там под капотом что происходит, быстрее ли reflow во flex, нежели у block, inline-block.
                          Вся суть в заголовке: «Что происходит при создании...» — читатель ожидает раскрытие этого процесса, а оказался очередной туториал, как работает flexbox.
                          Был бы рад, если бы вы перевели статью, касающуюся самых темных и неосвещенных моментах flexbox.
                          Спасибо за старания!
                      +1
                      Режимы записи
                      Возможно, в этом контексте лучше перевести wriring modes как «направления письма» или «направления текста».
                        0
                        Вы абсолютно правы, лучше эти свойства называть как 'направление письма'. Но программирование и прилегающие области знаний пришли к нам из англоязычных стран, поэтому нам только остается, для совместимости, оставить названия от первоисточника. Вот комментировать можно и по-русски
                          0

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


                          Согласно CSS Writing Modes Level 4 свойство "writing-mode" определяет направление не только вывода текста в конкретном элементе, но и расположения самих элементов уровня блока во flex-контейнере.


                          Поэтому это свойство нельзя переводить как "направление текста" или "направление письма", т.к. такой перевод ОГРАНИЧИТ область действия свойства. Соответственно, т.к. мы в блок пишем (записываем) текст и выводим блоки в контейнер, то правильнее, наверно, будет "режим записи" или "режим вывода".

                          +1
                          В спецификации Display Module Level 3 каждое значение просмотра описывается как комбинация двух элементов: внутренней и внешней модели отображения.

                          Каждое значение display, может быть?
                            0

                            Конечно, вы правы.
                            Спасибо за внимательное чтение.

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

                          Самое читаемое