Хабрахабр

[Из песочницы] Как я делал действительно адаптивный слайдер (карусель)

Доброго времени суток, уважаемые читатели и писатели!

Сегодня я расскажу, как в проекте передо мной возникла задача по изготовлению адаптивного слайдера и что из этого получилось

О статье и для кого она

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

Немножко о том, что случилось и какие инструменты были использованы

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

  1. Максимальная ширина элемента составляет 150рх
  2. Используемый инструмент — React Owl Carousel
  3. Максимальный размер контейнера для карусели — 1190рх
  4. Также есть показатели свойства padding для разных экранов (влияет на ширину видимой части контейнера) и margin (между элементами не менее 5рх)
  5. Карусель должна зацикливаться
    И прочие условия, не имеющие влияния на предмет статьи

Отступление о механике работы карусели

Многие карусели (не исключением из них является React Owl Carousel) используют для показа специальный класс active, описывающий элементы, которые в данный момент демонстрируются на экране.

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

Свойства описываются специальными объектами, интересовать нас будет объект responsive, который отвечает за переназначение свойств.

Остальные данные о механике работы будут понятны по ходу описания решения.

Первые возникшие проблемы

Проблемы начались при выставлении свойства Сначала все шло гладко — были написаны и стилизованы сами элементы, прописаны основные свойства всей карусели.

Карусель зацикливалась неадекватно

При прокручивании до конца списка в карусели оставалось свободное пространство и некоторое время прокручивалось именно оно.

Конкретным примером является ширина контейнера 1190рх, при этом количество элементов выставлено 3. Причина оказалась в максимальной ширине элемента, не согласованной с их количеством.

Другими словами, карусель ожидает, что 3 элемента растянутся на 1190рх, а они больше 150рх стать не могут.

Повышая количество элементов

Проблема приобретает другой ракурс: при слишком большом количестве элементов на контейнер ширина их становится слишком малой (а внутри них есть контент!) Если я задавал свойство min-width, то на некоторых размерах экрана элементы заползают друг на друга, игнорируя margin, что нарушает условия.

Резюмируем условия адаптивности

  1. Количество элементов на экране должно быть меньше отношения размера экрана к
    минимальной ширине элемента — иначе даже элементы минимальной ширины не поместятся на экране.
  2. Отношение размера экрана к предполагаемому количеству элементов не должно быть больше максимальной предполагаемой длины, иначе возникает проблема с зацикливанием.
  3. Описываемые выше условия должны быть соблюдены для любого размера экрана (от 330рх до 1190рх).

Решаем проблему как программисты

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

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

// Названия переменных говорят сами за себя
const getBackTrace = (minScreen = 300, maxElementWidth = 150) => { let backTrace = {} for (let minElementWidth = maxElementWidth; minElementWidth > 0; minElementWidth--){ // Постепенно уменьшаем минимальную ширину до выполнения всех условий и фиксируем результат // записывая его в объект backTrace for(let screen = minScreen; screen <= 1100; screen++){ let elementCount = screen / minElementWidth | 0 if((screen / elementCount) > maxElementWidth){ backTrace[minElementWidth] = screen break } } } for(let key in backTrace){ // Для удобства обходим объект и находим минимальную ширину, до которой приходится сжимать элементы if (backTrace[key - 1] == undefined){ backTrace.result = key - 1 return backTrace } }
} // getBackTrace(300, 150).result = 100

Следовательно, продолжаем поиски до нахождения нужного значения и ищем, чем еще можно жертвовать. Результат в 100рх меня не устроил, так как не позволяет уместить весь контент в элементе.

Для поиска напишем функцию Помните подзаголовок?

const getMinScreen = (minWidth = 300, maxWidth = 767, maxElementWidth = 150) => { let research = []
// по сути, пробуем поменять минимальный размер контейнера и прогнать его через // getBackTrace, пробуем уменьшить адаптивность в угоду контенту for(let min = minWidth; min < maxWidth; min++){ let { result } = getBackTrace(min, maxElementWidth) research.push({result, min}) }
// Перед возвращением уничтожим повторяющиеся значения и вернем корректный и "удобный к употреблению" объект return research .reduce((acc, curr, idx, arr) => { let obj = {} let {min, result} = curr obj[min] = result if(idx == 0) return obj if(arr[idx-1].result == result){ return {...acc} } else { return {...acc, ...obj} } }, {})
}
/* Returned object
{300: 100,
303: 101,
306: 102,
309: 103,
312: 104,
315: 105,
318: 106,
321: 107,
324: 108,
327: 109,
330: 110,
333: 111,
336: 112,
452: 113,
456: 114,
460: 115,
464: 116,
468: 117,
472: 118,
476: 119,
480: 120}
Значения свыше 480 я не рассматривал
*/

Рассматривая полученный объект, видно большой скачок при переходе от 336рх к 452рх.
Я принял волевое решение ограничить адаптивность на 36рх.

Описываем адаптивный объект

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

Осталось дело за малым — собрать полученные данные в кучу и реализовать адаптивный объект:

getResponsiveOwlItems = () => { let responsive = {}; responsive[0] = {items: 2, nav: false} // 112 = 107 (minimal div) + 5 (margins) let itemMinWidthReference = 112; const getOneWidth = deviceWidth => deviceWidth / itemMinWidthReference | 0 // 1190 - container width for(let i = itemMinWidthReference * 3 + 20; i <= 1190; i += itemMinWidthReference){ // .container padding > 768 90px + padding 90(.container) // .container padding < 768 40px + padding -40(.container) // +20px stagePadding let padding = i > 767 ? 200 : 20 if(i > (468 + padding)) { itemMinWidthReference = 117 } if(i > (767 + padding)) { itemMinWidthReference = 127 } let items = getOneWidth(i - padding) let nav = i > 700 ? true : false let margin = 5; if (i > 468){ margin = 10 } if (i > 767){ margin = 15 } responsive[i.toString()] = {items, nav, margin} // для выравнивания брейкпоинтов при изменениях itemMinWidthReference i = i - (i % itemMinWidthReference) + 1 } return responsive; }

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

Спасибо за внимание, жду Ваших комментариев и замечаний!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть