Руст Кулматов
|
Иногда простая с виду задача оказывается неожиданно сложной. При этом проблемы могут всплыть на любом этапе работы, неважно, как тщательно вы продумали решение сначала. Первым делом я бы посоветовался с дизайнером, рассказал о сложившейся трудности. Может оказаться так, что задача упрощается без ущерба для проекта. Например, тени в данном случае могут оказаться не важны или дизайнер решит отказаться от карусели. Если же скорректировать дизайн невозможно, запасаемся временем, вооружаемся яваскриптом и приступаем. Для начала соберём тестовый стенд. Для простоты возьмём слайдер с фиксированной шириной и известным количество слайдов. Также я усилю рамки и тени в изобразительных целях: |
<!-- index.html -->
<div class="slider">
<div class="slider-holder">
<div class="slider-list">
<div class="card">A</div>
<div class="card">B</div>
<div class="card">C</div>
<!-- ... -->
</div>
</div>
<button class="slider-pager">
Перелистнуть
</button>
</div>
/* style.css */
.slider {
width: 600px;
}
/* Обрезаем «невидимую» часть списка */
.slider-holder {
overflow: hidden;
}
/* Жёстко задаём ширину прокручивающейся
части с запасом */
.slider-list {
display: flex;
width: 10000%;
transition: transform .3s;
}
.card {
cursor: pointer;
box-sizing: border-box;
width: 200px;
height: 200px;
border: 1px solid #efefef;
transition: box-shadow .3s;
}
/* При наведении пытаемся показать тень */
.card:hover {
box-shadow: 0 0 2px rgba(0, 0, 0, .2);
}
// script.js
const SLIDER_WIDTH = 600
const VISIBLE_SLIDES = 3
const PAGES_NUMBER = 3
const PAGE_OFFSET = SLIDER_WIDTH
const $slider = document.querySelector('.slider')
const $list = $slider.querySelector('.slider-list')
const $pager = $slider.querySelector('.slider-pager')
let page = 0
// При нажатии на кнопку сдвигаем список на ширину
// одной «страницы». Если докрутили до конца,
// перематываем на начало
$pager.addEventListener('click', function() {
page += 1
if (page === PAGES_NUMBER) page = 0
$list.style.transform = `translateX(-${page * PAGE_OFFSET}px)`
})
Получилась фигня: рамки между соседями складываются, а тени оберзаются родительским элементом и соседними карточками. Для начала победим утолщение рамок. Для этого наложим карточки друг на друга на ширину рамки: |
/* style.css */
.slider {
width: 598px;
}
.card {
margin-left: -1px;
}
.card:first-child {
margin-left: 0;
}
// script.js
const SLIDER_WIDTH = 598
// Из-за наложения карточек друг на друга
// смещать список теперь нужно на пиксель меньше ширины слайдера
const PAGE_OFFSET = SLIDER_WIDTH - 1
// ...
Теперь разберёмся с обрезанием тени. Проблема в том, что для того, чтобы скрыть «невидимую» часть списка, мы обрезаем всё, что вылезает за пределы слайдера с помощью Чтобы с этим разобраться, склонируем список карточек и поместим его снаружи обрезающего элемента. После этого привяжем смещение клонированного списка к смещению оригинального: |
// script.js
const SLIDER_WIDTH = 598
const VISIBLE_SLIDES = 3
const PAGE_OFFSET = SLIDER_WIDTH - 1
const $slider = document.querySelector('.slider')
const $list = $slider.querySelector('.slider-list')
const $pager = $slider.querySelector('.slider-pager')
const $cards = $list.querySelectorAll('.card')
// Создадим контейнер для списка-клона
// и назначим ему нужные классы
const $listClone = document.createElement('div')
$listClone.classList.add('slider-list', 'is__clone')
Array.from($cards).forEach(function($card) {
// Создадим клон для каждой карточки
// и поместим его в список-клон
const $cardClone = document.createElement('div')
$cardClone.classList.add('card')
$listClone.append($cardClone)
// При наведении на оригинальную карточку
// добавляем класс `is__hovered` клонированной
$card.addEventListener('mouseover', () =>
$cardClone.classList.add('is__hovered'))
$card.addEventListener('mouseout', () =>
$cardClone.classList.remove('is__hovered'))
})
// Добавим список-клон в слайдер
$slider.append($listClone)
let page = 0
$pager.addEventListener('click', function() {
page += 1
if (page === VISIBLE_SLIDES) page = 0
// При листании одновременно двигаем
// оригинальный и клонированный списки
$list.style.transform =
`translateX(-${page * PAGE_OFFSET}px)`
$listClone.style.transform =
`translateX(-${page * PAGE_OFFSET}px)`
})
/* style.css */
/* Разместим клонированный список
поверх основного */
.slider.is__clone {
position: absolute;
top: 0;
left: 0;
/* Чтобы этот список не мешал наводить
и кликать на оригинальные карточки,
отключим у него взаимодействие с мышью */
pointer-events: none;
}
/* Перенесём тень на клонированную карточку */
.slider-list.is__clone .card {
opacity: 0;
box-shadow: 0 0 2px rgba(0,0,0,.2);
}
.card.is__hovered {
opacity: 1;
}
В результате при наведении на карточку появляется необрезанная тень, как мы и хотели. |