анимация css перестроение карточек по выбранной категории
подскажите, как реализовать. есть начало https://codepen.io/YuliyaVoronovich/pen/VwNapeb нужно чтобы при клике на категорию карточки нужной категории плавно перемещались. сейчас они просто исчезают и появляются и остаются "дыры" между. нужно как-то пересчитывать позицию и я не понимаю как это сделать
const tabs = document.querySelectorAll('.tabs button');
const cardsContainer = document.querySelector('.cards');
const loadCards = (category) => {
const cards = Array.from(cardsContainer.children);
// Сначала скрываем карточки
cards.forEach(card => {
card.classList.add('hide'); // Добавляем класс hide для анимации скрытия
});
// Ждем завершения анимации скрытия
setTimeout(() => {
let matchingCards;
// Определяем, какие карточки показывать
if (category === 'all') {
matchingCards = cards; // Показываем все карточки
} else {
matchingCards = cards.filter(card => card.getAttribute('data-categories').includes(category));
}
// Убираем класс hide у соответствующих карточек с задержкой
matchingCards.forEach((card, index) => {
setTimeout(() => {
card.classList.remove('hide'); // Убираем класс hide для появления
card.style.opacity = '1'; // Устанавливаем opacity в 1 для анимации
card.style.transform = 'translateY(0)'; // Возвращаем карточку в исходное положение
}, index * 100); // Задержка в 100 мс между карточками
});
}, 400); // Длительность анимации скрытия
};
// Обработчик для табов
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const category = tab.getAttribute('data-category');
loadCards(category);
});
});
.cards {
display: flex;
flex-wrap: wrap;
gap: 16px; /* Промежуток между карточками */
}
.cards-child {
flex: 1 1 calc(33.333% - 16px); /* Примерный размер карточек (3 в ряд) */
box-sizing: border-box; /* Включаем бордеры в размеры */
border: 2px solid #ccc; /* Бордеры для карточек */
border-radius: 8px; /* Закругление углов */
padding: 16px; /* Отступ внутри карточек */
transition: opacity 0.4s ease, transform 0.4s ease; /* Плавный переход для карточек */
opacity: 1; /* Начальное состояние (видимо) */
transform: translateY(0); /* Исходная позиция */
}
.cards-child.hide {
opacity: 0; /* Полностью невидим */
visibility: hidden; /* Скрываем элемент, но он занимает место */
transform: translateY(-20px); /* Сдвигаем карточку вверх для анимации */
}
Ответы (2 шт):
Используйте display, а не visibility и добавь анимацию. В итоге твой css файл должен быть примерно такой
.cards {
display: flex;
flex-wrap: wrap;
gap: 16px; /* Промежуток между карточками */
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(20px); /* Начальное положение снизу */
}
100% {
opacity: 1;
transform: translateY(0); /* Конечное положение */
}
}
.cards-child {
flex: 1 1 calc(33.333% - 16px);
box-sizing: border-box;
border: 2px solid #ccc;
border-radius: 8px;
padding: 16px;
transition: opacity 0.4s ease, transform 0.4s ease;
opacity: 0; /* Начальное состояние (невидимо) */
transform: translateY(20px); /* Исходная позиция (снизу) */
animation: fadeIn 0.6s forwards; /* Применить анимацию появления */
}
.cards-child.hide {
display: none; /* Скрываем элемент, но он занимает место */
transform: translateY(-20px); /* Сдвигаем карточку вверх для анимации */
}
Вот как я бы решил эту задачу. Тут есть ряд изображений, которые в обычном случае располагались бы один под другим. Но мы контролируем их позиции с помощью transform: translate
. У нас есть ширина и высота ячейки и с поправкой на gap
, можно задать координаты изображений с помощью коэффициентов. Например, второе изображение нам нужно сместить на одну позицию вправо (коэф. = 1) и на одну позицию наверх (коэф. = -1). Если коэффициенты не заданы undefined
, то показывать изображение не нужно. Ссылка на CodePen
UP: Поправил отсутствие анимации между odd и even
UP2: Добавил расчет высоты обертки .list
function show(coords) {
const slidesCount = coords.filter((element) => element !== undefined).length;
const rows = Math.floor((slidesCount - 1) / 3) + 1;
console.log(slidesCount, rows);
document.querySelector(".list").style.maxHeight = `calc(${rows} * var(--item-height) - 2em)`;
const refCoords = [
[0, 0],
[1, -1],
[2, -2],
[0, -2],
[1, -3]
];
let items = document.querySelectorAll(".item");
for (i = 0; i < items.length; i++) {
items[i].style.opacity = coords[i] ? 1 : 0;
const [x, y] = coords[i] || refCoords[i];
items[
i
].style.transform = `translate(calc(${x} * var(--item-width)), calc(${y} * var(--item-height)))`;
}
}
addEventListener("click", (e) => {
let b = e.target;
if (b.classList.contains("filter") && b.ariaPressed !== "true") {
let a = document.querySelector(".filter[aria-pressed=true]");
[b.ariaPressed, a.ariaPressed] = [a.ariaPressed, b.ariaPressed];
}
});
:root {
--item-width: calc(100% + 2em);
--item-height: calc(300px + 2em);
}
.filter-set {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.4em;
padding: 2em 0;
}
.filter {
background-color: #088;
color: white;
font-weight: 600;
padding: 0.8em 1.6em;
border: none;
border-radius: 0.3em;
transition: 0.3s;
box-shadow: 0 0 10px #0005;
}
.filter:hover {
background-color: #059;
}
.filter[aria-pressed="true"] {
background-color: #066;
}
.list {
overflow: hidden;
max-height: calc(2 * var(--item-height) - 2em);
}
.item {
display: block;
object-fit: cover;
background-color: lightblue;
width: calc(var(--item-width) / 3 - 2em);
height: calc(var(--item-height) - 2em);
margin-bottom: 2em;
transition: 0.3s;
opacity: 1;
}
/* 0 0 */
.item:nth-child(1) {
transform: translate( calc(0 * var(--item-width)), calc(0 * var(--item-height)));
}
/* 1 -1 */
.item:nth-child(2) {
transform: translate( calc(1 * var(--item-width)), calc(-1 * var(--item-height)));
}
/* 2 -2 */
.item:nth-child(3) {
transform: translate( calc(2 * var(--item-width)), calc(-2 * var(--item-height)));
}
/* 0 -2 */
.item:nth-child(4) {
transform: translate( calc(0 * var(--item-width)), calc(-2 * var(--item-height)));
}
/* 1 -3 */
.item:nth-child(5) {
transform: translate( calc(1 * var(--item-width)), calc(-3 * var(--item-height)));
}
body {
font: sans-serif;
padding: 0 2em;
}
<div class="filter-set">
<button class="filter" aria-pressed="true" onclick="show([[0,0],[1,-1],[2,-2],[0,-2],[1,-3]]);">All</button>
<button class="filter" onclick="show([[0,0],undefined,[1,-2],undefined,[2,-4]]);">Odd</button>
<button class="filter" onclick="show([undefined,[0,-1],undefined,[1,-3],undefined]);">Even</button>
<button class="filter" onclick="show([undefined,undefined,undefined,[0,-3],[1,-4]]);">The last two</button>
</div>
<div class="list">
<img class="item" src="https://picsum.photos/id/209/400/300">
<img class="item" src="https://picsum.photos/id/308/400/300">
<img class="item" src="https://picsum.photos/id/407/400/300">
<img class="item" src="https://picsum.photos/id/506/400/300">
<img class="item" src="https://picsum.photos/id/605/400/300">
</div>