Pull to refresh

Изменение ширины элемента с «шагом» в несколько пикселей

Reading time 4 min
Views 6.2K
Заранее оговорюсь, что чистым CSS здесь, к сожалению, обойтись не получится: Firefox и IE8+ слишком точно (да-да, в данном случае это плохо) производят вычисления ширины блоков. Однако для этих браузеров дописывается нехитрый скриптик в пару строк, если таки надо достичь идеала, хоть это и портит всю прелесть.

Для тех, кому лень читать всё, ссылка на окончательный вариант: jsfiddle.net/XeFTr/11

В чем суть?


Суть в том, что иногда необходимо отобразить на странице резиновый блок с несколькими дочерними элементами, причем потомок должен быть либо виден целиком, либо не виден вообще. Самый простой пример — лента каких-нибудь картинок с прокруткой и стрелочками по бокам. Очень некрасиво смотрится выглядывающее из-под «overflow: hidden» изображение, обрезаннное сбоку. Выход — заставить ширину обёртки всегда быть кратной скольки-то пикселям.
Разумеется, если ширина потомков разнится от элемента к элементу, то этот способ не имеет смысла.

Как это работает?


Принцип работы заключается в том, что большинство браузеров вычисляют ширину потомка, если она задана в процентах, исходя из ширины родителя. Таким образом, если у элемента ширина 1000%, то при изменении ширины его родителя на пиксель сам элемент растянется или сожмётся на 10 пикселей. Собственно, всё.

Эту штуку описывал в лебедевском скринкасте Сергей Чикуёнок еще в 2008 году, в контексте исправления бага с «прыгающими» блоками в IE6. Однако я, к своему удивлению, так и не нашел описания этой презабавной технологии на Хабре.

Как это написать?


Я буду рассматривать сферический элемент в вакууме, изначально состоящий исключительно из блока с «overflow: hidden» и очень широкого элемента с кучкой потомков, суммарная ширина которых будет превышать ширину обёртки:

<div class="wrapper">
    <ul class="itemList">
        <li class="item"></li>
        <li class="item"></li>
        <li class="item"></li>
        <li class="item"></li>
        <li class="item"></li>
        <li class="item"></li>
    </ul>
</div>

.wrapper {
    border: 1px solid black;
    margin: 10px;
    overflow: hidden;
    width: 60%; /* пусть лента будет чуть шире половины экрана */
}
    .itemList {
        height: 90px;
        list-style: none;
        margin: 0;
        padding: 0;
        width: 10000%; /* просто чтобы всё было в одну строку */
    }
        .item {
            background: green; /* картинок не будет, будет цвет блоков */
            display: inline; /* ну да, грязный хак для IE6 */
            float: left;
            height: 80px;
            margin: 5px;
            padding: 0;
            width: 140px;
        }

Живой пример: jsfiddle.net/KKSCe/4

Итак, у нас есть список элементов, ширина каждого (включая margin) — 150px. Очевидно, что последний элемент списка будет виден целиком в одном случае из 150. Хорошо, в 11 случаях: можно оставить за пределами видимости пятипиксельный правый margin и принять, что левый маргин является частью предыдущего элемента. :-)

Из описания выше понятно, что надо добавить обёртку блоку «.wrapper», а самому «.wrapper» задать ширину в 150 раз большую, нежели ширина этой обёртки. Так же ясно, что если написать «width: 15000%» без дополнительных манипуляций, то результат будет несколько не тем, что требуется. Необходимо, чтобы у «.wrapper» сохранилась ширина в 60% от ширины экрана. Это компенсируется маленькой шириной обёртки, вычисляется по пропорции.
15000 / 100 = 60 / x
x = 0.4
Пишем так:

<div class="pre-wrapper">
    <div class="wrapper">......</div>
</div>

.pre-wrapper {
    margin: 10px;
    width: 0.4%;
}
    .wrapper {
        border: 1px solid black;
        overflow: hidden;
        width: 15000%;
    }

Живой пример (посмотрите в Chrome, что ли): jsfiddle.net/XeFTr/9
Можно порастягивать там область просмотра и посмотреть, как клёво всё работает. Блок действительно принимает значения ширины, кратные 150px.
Казалось бы, отлично!

Но всё не так гладко


Проблема заключается в том, что в части случаев, как и в данном примере, приходится задавать ширину обёртки менее одного процента — когда шаг должен быть более 100px. Большинство браузеров это проглатывают и считают всё правильно. Даже IE6. А вот с Оперой, в том числе последних версий, — беда. Опера не понимает значения меньше 1%.

Решение довольно простое, но требущее большего количества HTML. Вместо того, чтобы делать один блок с шириной 0.4%, сделаем два. Ширина первого — 1%, ширина второго, вложенного в первый, — 40%. Тут и Опера становится в ряды согласных.

<div class="pre-wrapper">
    <div class="opera-fix">
        <div class="wrapper">......</div>
    </div>
</div>

.pre-wrapper {
    margin: 10px;
    width: 1%;
}
    .opera-fix {
        width: 40%;
    }

Живой пример: jsfiddle.net/XeFTr/8

Вполне возможно, что использование значения менее 1% черевато боком и в других браузерах, однако я этого не встречал. Поправьте меня, если я ошибаюсь.
Впрочем, в любом случае, дробное процентное значение — это не особенно хорошо.

Итак, теперь всё работает везде, кроме Firefox и IE последних версий. Исправим и это.

Скрипт для чрезмерно точных браузеров


Для простоты я буду использовать jQuery. Функция, как и разметка, тоже для лабораторных условий.
$(function() {
    if ( $.browser.mozilla || ($.browser.msie && $.browser.version >= 8) ) {
        resizeFix();
        $(window).resize(resizeFix);
    }
});

function resizeFix() {
    var w,
        h = $('.wrapper'),
        i = $('.item').outerWidth() + parseInt($('.item').css('marginLeft')) * 2;
    h.width('');
    w = Math.floor(h.width() / i) * i;
    h.width(w + 'px');
}

Окончательный вариант: jsfiddle.net/XeFTr/11

Резюмирую


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

Проверял работоспособность в Firefox, Opera, Chrome, IE6­­-9 и Safari под Windows.
Было бы здорово посмотреть на это всё под разнообразными Safari под МакОСЬю, Konqueror и т. п.
Также буду рад замечаниям/исправлениям.

В основе написанного лежит упомянутый выше скринкаст и последующие самостоятельные изыскания и эксперименты на эту тему.
Tags:
Hubs:
+32
Comments 39
Comments Comments 39

Articles