Создаем эффективные стили для каруселей

в 14:27, , рубрики: css, Блог компании Paysto, карусели, слайдеры, метки: , ,

Эта статья не о дизайне эффективных каруселей, а об эффективном создании стиля. Другими словами, речь пойдет не о UI-дизайне, а о конструкциях CSS – смене элементов карусели, их позиционировании и размерах.

Зависимость от JavaScript с точки зрения взаимодействия, а не стиля

«Эффективная карусель» — это карусель, которая не зависит от JavaScript с точки зрения:

• отзывчивости
• содержания любого количества элементов
• отображения любого количества элементов

Вызов

Существует множество способов отображения элементов в карусели типа «side-by-side» (в ряд), однако некоторые из этих способов лучше других.

Используем float

Карусель на сайте disneystore.com демонстрирует два основных ограничения такого стиля:

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

Чтобы лучше понять проблему, посмотрите на следующий пример:

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
ul {
    border: 5px solid deeppink;
    overflow: hidden; /* должен содержать поплавки*/
}

li {
    border: 1px solid #fff;
    background: #3CB371;
    width: 100px;
    height: 100px;
    float: left;
}
image

Вышеуказанный список включает пять элементов – все оформленные с помощью элемента float. Сам по себе список создан с помощью overflow:hidden (чтобы создать контекст форматирования блока), поэтому список выравнивает поплавки вместо того, чтобы схлопнуться.

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

ul {
    overflow: hidden; /* должен содержать поплавки */
    width: 450px;
}

li {
    float: left;
}
image

Поэтому это решение требует установки точной ширины контейнера, которая не даст этому контейнеру быть отзывчивым (без необходимости JavaScript).

Используем position:absolute

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

Посмотрите на еще один пример:

ul {
    height: 100px; /* предотвращает нарушение контейнера */
}

li {
    position: absolute;
}
image

Так как все рамки имеют абсолютное позиционирование, они удаляются с потока и размещаются на одних и тех же координатах x/y; последняя рамка показывает верх стопки.

В результате, авторам нужно сделать 3 вещи:
• оформить контейнер с помощью элемента height, чтобы предотвратить смещение вверх последующих элементов (за рамки).
• оформить каждую рамку со смещением влево (left ), чтобы отобразить элементы в ряд.
• оформить контейнер с помощью элемента position:relative, чтобы превратить его в блок, содержащий дочерние элементы.

Одинаковые рамки создаются следующим образом:

ul {
    height: 100px; /* предотвращает нарушение контейнера */
    position: relative; /* делает этот контейнер блоком, содержащим вложенные рамки AP */
}

li {
    position: absolute;
}

li:nth-child(2) {
    left: 100px;  /* ширина + левая/правая граница первой рамки */
}

li:nth-child(3) {
    left: 200px; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(4) {
    left: 300px; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(5) {
    left: 400px; /* ширина + левая/правая граница предыдущих рамок */
}
image

Именно так выполнена карусель на сайте aol.com, и все смещения там указаны в пикселях. Но интересно то, что ширина (width) контейнера задана таким же образом, как и в нашем примере с float. Контейнер оформлен таким образом, чтобы иметь ту же ширину, что и суммарная ширина всех его дочерних элементов – даже несмотря на использование position:absolute он позволяет авторам использовать более простой подход, который улучшает характеристики блоков контейнера.

В отличие от конструкций float, ширина (width) контейнера не играет роли в позиционировании вложенных рамок. Это означает, что можно использовать процентное соотношение для отображения элементов в полную ширину (100%) или в качестве части их контейнера (блока, в котором они содержатся); например, оформление каждой рамки с соотношением 50% покажет 2 элемента в ряд, как показано ниже:

ul {
    height: 100px; /* предотвращает нарушение контейнера */
    position: relative; /* делает этот контейнер блоком, содержащим вложенные рамки AP */
    width: 30%;
}

li {
    position: absolute;
    width: 50%;
}

li:nth-child(2) {
    left: 50%; /* ширина + левая/правая граница первой рамки */
}

li:nth-child(3) {
    left: 100%; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(4) {
    left: 150%; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(5) {
    left: 200%; /* ширина + левая/правая граница предыдущих рамок */
}
ul {
    height: 100px; /* предотвращает нарушение контейнера */
    position: relative; /* делает этот контейнер блоком, содержащим вложенные рамки AP */
    width: 30%;
}

li {
    position: absolute;
    width: 50%;
}

li:nth-child(2) {
    left: 50%; /* ширина + левая/правая граница первой рамки */
}

li:nth-child(3) {
    left: 100%; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(4) {
    left: 150%; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(5) {
    left: 200%; /* ширина + левая/правая граница предыдущих рамок */
}
image

Ширина (width) вышеуказанного контейнера установлена на значении 30%, все остальные значения (смещения влево (left) и ширина (width) рамок) также указаны в виде процентных соотношений.

ul {
    height: 100px; /* предотвращает нарушение контейнера */
    position: relative; /* делает этот контейнер блоком, содержащим вложенные рамки AP */
    width: 50%;
}

li {
    position: absolute;
    width: 25%;
}

li:nth-child(2) {
    left: 25%; /* ширина + левая/правая граница первой рамки */
}

li:nth-child(3) {
    left: 50%; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(4) {
    left: 75%; /* ширина + левая/правая граница предыдущих рамок */
}

li:nth-child(5) {
    left: 100%; /* ширина + левая/правая граница предыдущих рамок */
}
image

В данном случае ширина контейнера составляет 50%, а ширина каждой рамки — 25%, что позволяет отображать четыре рамки в ряд внутри контейнера.

Это решение определенно лучше, чем использование float, но недостатком тут является то, что удаление все элементов с потока требует оформления контейнера с указанием высоты (height), чтобы предотвратить отображение последующих исходных элементов за всеми рамками.

Решение

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

Используем inline-block

Начнем с основ:

<ul>
    <li>1</li><!--
    --><li>2</li><!--
    --><li>3</li><!--
    --><li>4</li><!--
    --><li>5</li>
</ul>
li {
    display: inline-block;
}
image

С таким простым оформлением и макетом, как и в конструкции с float, вложенные рамки сползают, если им недостаточно места:

ul {
    width: 450px;
}

li {
    display: inline-block;
}
image

Волшебная пилюля

Мы можем избежать сползания рамок с помощью white-space:nowrap:

ul {
    width: 450px;
    white-space: nowrap;
}

li {
    display: inline-block;
}
image

Теперь у нас есть решение, которое не требует установки точного значения высоты (height) контейнера и оформления вложенных рамок со смещением или настройки их ширины (width) с точными значениями. И в качестве бонуса – это решение подходит для RTL:

<ul class="example example-10" dir="rtl">
    <li>1</li><!--
    --><li>2</li><!--
    --><li>3</li><!--
    --><li>4</li><!--
    --><li>5</li>
</ul>
image

Реализация карусели

Смещение за счет margin

Использования левого поля на первом элементе достаточно, чтобы сдвинуть все поля за раз (влево или вправо):

ul {
    width: 100px;
    white-space: nowrap;
}

li {
    display: inline-block;
    width: 100%;
}

li:first-child {
    margin-left: -100%; /* для старых IE может потребоваться этот класс*/
}

image

Оформление контейнера с overflow:hidden скроет элементы, которые находятся за пределами контейнера:

ul {
    width: 100px;
    white-space: nowrap;
    overflow: hidden;
}

li {
    display: inline-block;
    width: 100%;
}

li:first-child {
    margin-left: -100%;
}
image

Единственно, что стоит не забывать – это сбрасывать описание nowrap, если этот стиль унаследован.

Смещение за счет translate, position, и пр.

Перемещение контейнера, а не его первого дочернего элемента, требует использования дополнительной обертки (обратите внимание, что все стили переносятся со списка на эту надстройку):

<div>
    <ul>
        <li>1</li><!--
        --><li>2</li><!--
        --><li>3</li><!--
        --><li>4</li><!--
        --><li>5</li>
    </ul>
</div>
div {
    white-space: nowrap;
    width: 50%;
    overflow: hidden;
    border: 5px solid deeppink;
}
ul {
    border: none;
    *position: relative; /* возврат к oldIE */
    *left: -100%;        /* возврат к oldIE */
    transform: translateX(-100%);
}

/*фолбэк для IE8 */
@media screen {
    .example-13 {
        position: relative;
        left: -100%;
    }
}

li {
    white-space: normal; /* сброс */
    display: inline-block;
    width: 50%;
}
image

Карусель с выглядывающим элементом

Это решение мы можем легко выполнить следующим образом:

div {
    padding-right: 12%; /* создает разрыв справа от списка, открывая часть следующего поля */
}

img {
    width: 100%; /* та же ширина, что и у контейнера */
    vertical-align: bottom;
}
image

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

Чистая CSS-карусель с эффектом плавного появления

Логика, в которой не требуются знания математики!

<div class="carousel">
    <input role="presentation" name="carousel" type="radio" value="1" checked />
    <input role="presentation" name="carousel" type="radio" value="2" />
    <input role="presentation" name="carousel" type="radio" value="3" />
    <input role="presentation" name="carousel" type="radio" value="4" />
    <input role="presentation" name="carousel" type="radio" value="5" />
    <ul class="carousel-list">
        <li><img src="..." alt="Mask #1"></li><!--
        --><li><img src="..." alt="Mask #2"></li><!--
        --><li><img src="..." alt="Mask #3"></li><!--
        --><li><img src="..." alt="Mask #4"></li><!--
        --><li><img src="..." alt="Mask #4"></li>
    </ul>
</div>
<div class="carousel">
    <input role="presentation" name="carousel" type="radio" value="1" checked />
    <input role="presentation" name="carousel" type="radio" value="2" />
    <input role="presentation" name="carousel" type="radio" value="3" />
    <input role="presentation" name="carousel" type="radio" value="4" />
    <input role="presentation" name="carousel" type="radio" value="5" />
    <ul class="carousel-list">
        <li><img src="..." alt="Mask #1"></li><!--
        --><li><img src="..." alt="Mask #2"></li><!--
        --><li><img src="..." alt="Mask #3"></li><!--
        --><li><img src="..." alt="Mask #4"></li><!--
        --><li><img src="..." alt="Mask #4"></li>
    </ul>
</div>
.carousel {
    width: 200px;
    padding: 5px;
    overflow: hidden;
    border: 1px solid #ccc;
    border-radius: 3px;
    text-align: center;  /* центрирует кнопки-переключатели */
}

.carousel-list {
    white-space: nowrap;
    padding: 0;
    margin: 0;
    transition: transform .3s;
}

.carousel-list li {
    white-space: normal; /* сброс */
    display: inline-block;
    width: 100%;
}

.carousel-list img {
    width: 100%; /* подгоняется под контейнер */
    vertical-align: bottom; /* удаляет пустое пространство под изображением */
}

/**
 * список перемещается кнопками-переключателями
 */
input:nth-child(1):checked ~ ul {
    transform: translateX(0);
}

input:nth-child(2):checked ~ ul {
    transform: translateX(-100%);
}

input:nth-child(3):checked ~ ul {
    transform: translateX(-200%);
}

input:nth-child(4):checked ~ ul {
    transform: translateX(-300%);
}

input:nth-child(5):checked ~ ul {
    transform: translateX(-400%);
}

/**
 * эффект плавного появления
 */
.carousel-list li {
    opacity: .1;
    transition: all .4s;
    transform: scale(.1);
}

input:nth-child(1):checked ~ ul li:nth-child(1),
input:nth-child(2):checked ~ ul li:nth-child(2),
input:nth-child(3):checked ~ ul li:nth-child(3),
input:nth-child(4):checked ~ ul li:nth-child(4),
input:nth-child(5):checked ~ ul li:nth-child(5) {
    opacity: 1;
    transform: scale(1);
}
image

Вы можете отредактировать значение width ниже, чтобы проверить «отзывчивость» этого решения.

.carousel{width:200px}

Более сложная карусель

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

.carousel {
    display: inline-block;
    width: 200px;
    padding-right: 190px; /* ширина контейнера минут  10px внутреннего поля элементов списка */
    overflow: hidden;
    border: 1px solid #ccc;
    border-radius: 3px;
    text-align: center;  /* центрирует кнопки-переключатели */
}

.carousel-list {
    white-space: nowrap;
    padding: 0;
    margin: 0;
    border: none;
    transition: transform .3s;
}

.carousel-list li {
    white-space: normal; /* сброс */
    display: inline-block;
    width: 100%;
    box-sizing: border-box;
    padding-right: 10px; /* создает разрыв между изображениями */
}

.carousel-list img {
    width: 100%; /* подгоняется под контейнер */
    vertical-align: bottom;
}

.carousel input {
    margin-left: -3px;
}

.carousel input:nth-child(1):checked ~ ul {
    transform: translateX(0);
}

.carousel input:nth-child(2):checked ~ ul {
    transform: translateX(-100%);
}

.carousel input:nth-child(3):checked ~ ul {
    transform: translateX(-200%);
}

.carousel input:nth-child(4):checked ~ ul {
    transform: translateX(-300%);
}

.carousel input:nth-child(5):checked ~ ul {
    transform: translateX(-400%);
}

.carousel input:nth-child(6):checked ~ ul {
    transform: translateX(-500%);
}

.carousel input:nth-child(7):checked ~ ul {
    transform: translateX(-600%);
}
image

Автор: Irina_Ua

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js