Пользователь
0,0
рейтинг
6 августа 2013 в 14:25

Разработка → Проблемы CSS. Часть 1 перевод

CSS*
От переводчика
Статья большая решил разбить на две части.

Впервые css был представлен примерно в 1995 году, и был предназначен для стилизации простых текстовых документов. Не веб сайтов. Не приложений. А именно текстовых документов. С тех пор, css, прошел долгий путь. Возможно слишком долгий.

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

В лучшем случае — работу с css можно назвать веселым занятием. И это то, благодаря чему мы имеем работу. Потому что, как я считаю, генерация эффективных и кроссбраузерных css стилей невозможна и не будет возможна в ближайшее время.



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

Я старался выбрать несколько действительно очень распространенных:

  • Очистка float`ов.
  • Как победить отступы между элементами с inline-block?
  • Понимание абсолютного позиционирования.
  • Когда использовать width / height равный 100% (часть 2)
  • Как не облажаться с z-index. (часть 2)
  • Что такое свертывание границ(margin collapsing)? (часть 2)


Очистка float`ов.


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

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



Есть несколько способов для решения этой проблемы. Раньше мы использовали пустой div со стилем «clear: both» в самом низу контейнера. Затем, мы заменили его на тег hr, что не на много лучше.

И наконец Nicolas Gallagher предложил новый путь очистки float`ов без необходимости трогать разметку совсем. После продолжительных дискуссий и тестов, мы получили минимально необходимый, для его работы, набор стилей, вот его последняя версия:

.clearfix:after {
    content: "";
    display: table;
    clear: both;
}

На самом деле я солгал, она не последняя, она самая короткая. Но если вам нужна поддержка IE 6/7, то вам нужно добавить еще вот это:

.clearfix {
    *zoom: 1;
}

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

Как победить отступы между элементами с inline-block?


Продолжим с размещением элементов в строку, в этот раз не с помощью float, а с помощью inline-blocks. display: inline-block долгое время был недооценен, и все же мы разобрались, как он работает и почему это круто. Сегодня, все больше и больше front-end разработчиков предпочитают использовать inline-block взамен float`ов, когда у них есть такая возможность.

Главный плюс inline-block в том, что нам не приходится отчищать float`ы и мы не сталкиваемся с другими проблемами которые могут возникнуть из-за элементов спозиционированных с помощью float`ов. Просто установив свойство элемента display в значение inline-block получим гибрид строчного элемента и блока. Они могут иметь размер, отступы, но их ширина, по-умолчанию, зависит от контента, а не занимает всю ширину родителького элемента. Таким образом они размещаются горизонтально, а не вертикально.

Вы можете спросить: «В чем же тогда проблема?». А проблема в том, что они на половину строчные, а значит имеют отступ друг от друга размером равным пробелу. Со стандартным шрифтом размером 16px, он составляет 4px. В большинстве случаев размер отступа можно рассчитать, как 25% от размера шрифта. Так или иначе, это может помешать нормальному расположению элементов. Например, возьмем контейнер размером в 600px с тремя элементами внутри, размер которых 200px и задано свойством display: inline-block. Если не убрать отступ, то нам не удастся разместить их в одну линию (200 *3 + 4 * 2 = 608).

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

Уровень разметки: удаление пробелов

Для всех наших тестов воспользуемся следующей разметкой.

<div class="parent"> <!-- 600px -->
    <div class="child">I'm a child!</div> <!-- inline-block 200px -->
    <div class="child">I'm a child!</div> <!-- inline-block 200px -->
    <div class="child">I'm a child!</div> <!-- inline-block 200px -->
</div>

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

<div class="parent">
    <div class="child">I'm a child!</div><div class="child">I'm a child!</div><div class="child">I'm a child!</div>
</div>

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

<div class="parent">
    <div class="child">
        I'm a child!</div><div class="child">
        I'm a child!</div><div class="child">
        I'm a child!</div>
</div>

И если быть уж совсем крутым, можно сделать и так:

<div class="parent">
    <div class="child">I'm a child!</div
  ><div class="child">I'm a child!</div
  ><div class="child">I'm a child!</div>
</div>

Да — это работает! Конечно я не рекомендую такой подход, потому что он не интуитивен и делает код уродливым. Давайте, лучше, попробуем, что нибудь еще.

Уровень разметки: комментируем пробелы

Что если закомментировать пробелы вместо того, что бы удалить их?

<div class="parent"> <!-- 600px -->
     <div class="child">I'm a child!</div><!-- 
--><div class="child">I'm a child!</div><!--
--><div class="child">I'm a child!</div>
</div>

Да, так гораздо лучше! Код читаем и работает. Да, способ выглядит не привычно на первый взгляд, но не так и сложно к нему привыкнуть. Я и сам использую такой способ, когда мне надо удалить пробелы между элементами с inline-block.

Конечно, кто-то скажет, что это не идеальное решение, так как оно работает на стороне разметки, а проблема должна быть решена на уровне css. Действительно. Большинство из нас на самом деле используют css решения.

Уровень css: расстояние между символами

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

.parent {
    letter-spacing: -0.3em;
}
 
.child {
    letter-spacing: normal;
}

Эта техика используется в Griddle — основанной на Sass системе сеток за авторством Nicolas Gallagher, так что, как вы видите, это достаточно серьезное решение. Хотя, честно говоря, мне не нравится тот факт, что мы полагаемся на магичиские числа в стилях. Плюс с некоторыми шрифтами, это число может меняться например на 0.31em и т.д. То есть, его необходимо подгонять под каждый конкретный случай.

Уровень css: отрицательный margin

Еще один подход к решению задачи, очень похож на предыдущий, но с использование отрицательного отступа. Главный его недостаток он не работает в IE 6/7. Плюс нам необходимо убрать отступ с первого элемента, что бы они ровно встали внутри нашего контейнера.

.child {
    margin-left: -0.25em;
}
 
.child:first-of-type {
    margin-left: 0;
}

Если вам не требуется поддержка IE 6/7, я считаю что это достаточно неплохе решение.

Уровень css: font-size

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

.parent {
    font-size: 0;
}
 
.child {
    font-size: 16px;
}

У этого решения есть несколько своих проблем и ограничений:



Так что, это не лучшее решение. Как я уже говорил ранее, скорее всего буду использовать путь с комментирование пробелов. Если для вас он выглядит неудобным, вы можете вернуться к float`ам или же вообще использовать flexbox.

Понимание абсолютного позиционирования.


Позиционирование элементов — каверзный процесс и всегда им был. Позиционирование, начинающим, дается с большим трудом. Они часто (не)используют свойство position. Это свойство определяет как элемент может перемещаться с помощью смещений (top, right, bottom and left). И принимает следующие значения:

  • static — по-умолчанию, смещения не действуют
  • relative — смещения двигают визуальный слой, но не сам элемент
  • absolute — смещения двигают элемент внутри контекста (первый не static элемент)
  • fixed — смещения позиционируют элемент внутри viewport`a и не важно где он расположен в документе


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

element {
    position: absolute;
    top: 0;
    right: 0;
}

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

Код выше просто говорит: «Я хочу что бы мой элемент был спозиционирован в верхнем правом углу контекста». Так что же такое контекст? Это первый элемент со свойством position не равным static. Это может быть непосредственно родительский элемент, или родитель родителя, или родитель родителя родителя. И так до первого элемента с position != static.

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

Небольшая демка иллюстрирующая вышесказанное. Два родителя, в каждом по одному дочернему элементу спозиционированному абсолютно со смещение top: 0 и right: 0. Слева правильный родитель с position: relative, справа неправильный с position: static.

jsFiddle

Продолжение «Проблемы CSS. Часть 2».
Перевод: Hugo Giraudel
Станислав @d4rkr00t
карма
33,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (53)

  • 0
    Придёт flexbox, clearing с inline-block элементами умрут сами по себе.
    А реализация варианта с комментариями пробелов на к.-н. шаблонизаторе та ещё задача, это в голом html всё красиво
    • 0
      Так в шаблонизаторе можно и просто в строку вывести, как первый предложенный вариант, и тогда не будет такой проблемы. Flexbox ждем, пока ie8 и ie9 поддерживаем не получится.
    • 0
      Придёт flexbox, clearing с inline-block элементами умрут сами по себе.
      Не вижу связи.

      Flexbox не позволяет решить задачу «поместить по горизонтали столько элементов, сколько влезет», а именно для этого чаще всего употребляется inline-block (приведённый в статье пример «контейнер размером 600px с тремя элементами внутри, размер каждого из которых 200px и задано свойство display: inline-block» — редчайший частный случай; чаще же всего ширина такого контейнера произвольно меняется по мере изменения размера окна).
      • 0
        Хм, вроде бы позволяет: codepen.io/d4rkr00t/pen/Cqzvo
        • +1
          пока видимо только webkit браузеры так могут
          • +1
            И FF, и ещё кое-кто. Вот хорошая презентация от pepelsbey на эту темку http://pepelsbey.net/2013/05/flexbox-gotcha/
            • +1
              Я про flex-wrap и flex-flow
              • 0
                Да; согласно сведениям на сайте «Mozilla Developer Network», Firefox не поддерживает flex-wrap.
          • 0
            да, да понял, не только webkit
      • +3
        flex-wrap: wrap позволяет
    • 0
      Ну незнаю, с дополнительными комментариями это проще. Главное чтобы кодер не посчитал их лишними
      <div class="parent"><!-- 600px
            --><div class="child">I'm a child!</div><!-- 
      --><div class="child">I'm a child!</div><!--
      --><div class="child">I'm a child!</div><!--
       --></div>
      
      • 0
        В идеале в html не должно быть ничего, относящегося к отображению. Хотя вопрос конкретно пробелов между тегами достаточно спорный, так как сложно различить ситуации где пробел является частью данных, которые нужно отобразить, а где используется лишь для форматирования кода. По идее это должен быть атрибут родительского тега. Либо свойство CSS по произвольному селектору, позволяющее включать и отключать игнорирование пробелов или, в более общем случае, текстовые ноды внутри контейнера. В крайнем случае какая-то новая относительная единица измерения для обозначения ширины пробела в текущем шрифте. В общем имея в виду, что в SGML-based (по крайней мере популярнех HTML и XML) языках к пробельным символам особое отношение, нужно и в css ввести особое к ним отношение.
        • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Обычно проблему с float'ами решаю применяя overflow: hidden для родительского элемента: jsfiddle.net/6PDmf/
    • +4
      Да, известный способ.
      Но как только внутри появляется абсолютно-позиционируемый элемент, вылезающий за границы родителя, приходится отказываться.
      • 0
        А в чем проблема? jsfiddle.net/alright/dPjND/1/
        • +2
          вот в чем jsfiddle.net/2T7AY/
          • 0
            на этот случай есть более изящное решение от Ильи Стрельцына
            Пример:
            jsfiddle.net/mdss/64eyb/
            • 0
              Chrome 28. Mac. Баг остался.

              • 0
                забыл из вашего примера удалить у родителя overflow:hidden;
                jsfiddle.net/mdss/64eyb/1/
                теперь все нормально
                • 0
                  Да так работает, но сопосб с clearfix мне нравится больше
                  • 0
                    этот способ хорошо применять в «резиновых» макетах, где один из блоков фиксированный по ширине, а другой занимает всю оставшуюся ширину
            • НЛО прилетело и опубликовало эту надпись здесь
        • +1
          вот в чём: jsfiddle.net/dPjND/3/
          • +1
            Возможно, я зря это сюда пишу, т.к. сам противник очистки потока через overflow:hidden;, но с помощью дополнительной обёртки проблема решаема: jsfiddle.net/GruZZ/bXbe5/1/
            • +3
              «Дополнительная обёртка» первый друг верстальщика, простите.
  • –6
    Это на какой язык перевод?
    • +6
      Настолько плох?
      • 0
        Вы бы сами хоть перечитали.
  • 0
  • 0
    SASS плюс Compass легко решают все эти и многие другие проблемы.

    Не совсем в тему пример: responsive галерея (попробуйте поиграть с шириной окна). sassbin.com/gist/5670191/
    • 0
      Каким образом решают?
      • +1
        Понятно, что SASS для браузера не может предложить ничего, на что не способен CSS. Но существующие решения, которые на чистом CSS являются занозой в заднице, с SASS применяются легко и непринужденно.

        Так, для очистки float'ов етсь целый арсенал решений. Я предпочитаю Toolkit:

        #some-container
          @extend %clearfix-micro
        


         
        Что касается проблемы пробелов между inline-block'ами, удалить пробелы можно средствами Haml (раз, два), если, конечно, вам посчастливилось использовать этот язык для разметки. Но по сути проблема пробелов существует, только покуда вы применяете inline-block'и для решения задач, для которых они изначально не предназначены. Я считаю, этот прием — из категории «голь на выдумки хитра». Для выстраивания элементов в ряд есть и другие способы, делать их обзор в этом комментарии считаю излишним.

         

        Проблема понимания контекста — это вообще смешно, танцору мешают собственные шнурки. Но и тут SASS может предложить удобные инструменты.

        Вот, к примеру, intrinsic ratio из того же Toolkit. Этот mixin позволяет одной строчкой (например, +intrinsic-ratio(4/3, 50%)) задать пропорции для контейнера (в данном примере 4 к 3), имеющего динамическую ширину (в данном примере 50% от ширины родителя). Содержимое контейнера при этом растягивается на весь контейнер при помощи абсолютного позиционирования.

        Этот прием очень удобен для вставки изображений, видео и iframe'ов в responsive дизайн. Демонстрация: sassbin.com/gist/6172280/
  • –1
    Обсуждаемую проблему с inline-block можно решить иначе: у родителя ставится font-size: 0px, но у детей нужно проставить первоначальный размер шрифта родителя.
    • +1
      в статье есть такой способ и рассказаны его минусы
      • 0
        Кстати, некоторые проблемы webkit'ов можно решить с помощью нулевого svg-шрифта и подключать его как font:0/0 'null', a;.

        Пример самого шрифта:
        7days.ru/bitrix/templates/7dn-main/css/fonts/w-webfont.svg
        @font-face {
        	font-family: "null";
        	font-style: normal;
        	font-weight: 400;
        	src: url('fonts/w-webfont.svg#null') format("svg");
        }
        


        Но это, к сожалению, просто еще один не самый лучший вариант решения проблемы.
      • +3
        Я буду читать статью до конца, прежде чем написать комментарий
        Я буду читать статью до конца, прежде чем написать комментарий
        Я буду читать статью до конца, прежде чем написать комментарий
  • НЛО прилетело и опубликовало эту надпись здесь
    • +4
      Текстовых документов, если быть точнее1. Только сравнительно недавно Рабочая группа по CSS обратила внимание на задачи оформления веб-приложений.

      1 В частности, каскадность полезна именно для этого случая. А в остальных случаях гораздо удобней, когда блоки независимы.
  • –2
    Не являюсь frond-end экспертом, хотя стараюсь прокачиваться в этом направлении.
    Взял за привычку в кастомных стилях ставить следующий css-селектор

    * {
      margin: 0;
      padding: 0;
      position: relative;
      box-sizing: border-box;
    }
    


    Волнует как раз position. Логика такая, что relative элемент без смещения находится на том же месте, что и static, но при этом допускает абсолютное позиционирование дочерних элементов, да и, если появляется такая необходимость, сам может быть смещен, что, в общем-то, очень удобно.

    Внимание, вопрос: есть ли здесь какой-либо подводный камень, и надо ли мне дать по рукам за то, что так делаю?
    • +2
      Ну вы рано или поздно просто огребете, что ваш абсолютно спозиционированный элемент начнет вычислять свои координаты относительно первого же предка. Это не так чтобы проблема (на первый взгляд), но неочевидно.
      Еще понаогребаете с z-index'ами, их у вас будет чуть ли не на каждый элемент, который следует за родительским блоком абсолютно спозиционированной ноды.
      Еще селекторы * — очень медленные и ускорить их вообще нечем.
      Вдобавок ко всему это совсем неочевидно. Все равно как если бы в вашем проекте все классы реализовывали бы некий интерфейс IMyInteface, в котором есть какой-то метод, вообще мало что имеющий общего с большей частью реализаций.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Окей, уяснил. Спасибо.
      • 0
        You might get up in arms about the universal * selector. Apparently you’ve heard its slow. Firstly, it’s not. It is as fast as h1 as a selector. It can be slow when you specifically use it like .foo > *, so don’t do that. Aside from that, you are not allowed to care about the performance of * unless you concatenate all your javascript, have it at the bottom, minify your css and js, gzip all your assets, and losslessly compress all your images.

        www.paulirish.com/2012/box-sizing-border-box-ftw/
        • 0
          unless you concatenate all your javascript, have it at the bottom, minify your css and js, gzip all your assets, and losslessly compress all your images.

          Круто, что именно этим я и промышляю.
    • 0
      Минуса за вопрос, куда катится этот мир?
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Курение, знаете ли, тоже вредно, но я не видел, чтобы в курильщиков камнями бросались.
          • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Спасибо, полезная статья.

    <grammar-nazi> Правда, излишняя любовь автора к запятым немного ухудшает читаемость ;) </grammar-nazi>

    С тех пор, css, прошел долгий путь.

    Давайте, лучше, попробуем, что нибудь еще.

    Позиционирование, начинающим, дается с большим трудом.
  • 0
    про отступы у inline-блок не знал, спасибо ) (как раз недавно столкнулся)
  • +1
    По поводу inline-block элементов и пробела между ними.
    А именно про способ: «отрицательный margin»

    Еще один подход к решению задачи, очень похож на предыдущий, но с использование отрицательного отступа. Главный его недостаток он не работает в IE 6/7. Плюс нам необходимо убрать отступ с первого элемента, что бы они ровно встали внутри нашего контейнера.

    .child {
        margin-left: -0.25em;
    }
     
    .child:first-of-type {
        margin-left: 0;
    }
    


    Нам не нужно удалять маргин, у первого элемента
    И более того это работает в IE 6/7/8 если указывать <!DOCTYPE >
    Нужно просто использовать селектор +

    .child + .child {
        margin-left: -0.25em;
    }
    


    Сам я такой метод никогда не использую
    Но раз уж автор разбирает проблемы CSS странно, что он указал способ с очищением для :first-of-type

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