GSAP Flip анимация мигает/глючит, только третий элемент работает нормальноHtml

Программисты Html
Ответить
Anonymous
 GSAP Flip анимация мигает/глючит, только третий элемент работает нормально

Сообщение Anonymous »

Я использую GSAP Flip для анимации изображений из сетки карточек в детальном представлении путем изменения родительского состава изображения, по которому щелкнули. Анимация работает правильно для 3-й, но 1-я, 2-я и 4-я карточки демонстрируют кратковременное моргание/прыжок во время перехода.
Что сбивает с толку:
  • Все карточки используют одинаковую разметку и логику
  • Сбой возникает только на первой row
  • Ошибки консоли нет
  • Проблема видна примерно в течение 1 кадра (выглядит как переход макета или преобразования)
Среда
  • GSAP 3.12.x
    Перевернуть плагин
    Chrome / Edge (воспроизводимый)
    Разметка CSS Grid

Код: Выделить всё

gsap.registerPlugin(Flip);

// --- Elements ---
const cards = document.querySelectorAll('.card');
const detailView = document.querySelector('.detail-view');
const detailBackdrop = document.querySelector('.detail-backdrop');
const detailStage = document.querySelector('#detail-stage');
const detailText = document.querySelector('.detail-text');
const backButton = document.querySelector('.breadcrumb');

const dtTitle = document.querySelector('#dt-title');
const dtBreadcrumb = document.querySelector('#detail-breadcrumb-name');
const dtPrice = document.querySelector('#dt-price');

// State tracking
let activeImage = null;
let originalParent = null;
let isAnimating = false;

// --- SETUP ---
const allImages = document.querySelectorAll('.product-img');
allImages.forEach((img, i) => {
img.setAttribute('data-flip-id', `img-${i}`);
});

// --- Open Logic ---
cards.forEach(card => {
card.addEventListener('click', () => {
if (isAnimating) return;
isAnimating = true;
document.body.classList.add('is-animating');

const img = card.querySelector('.product-img');
const title = card.querySelector('h3').innerText;
const price = card.querySelector('.price').innerText;

const state = Flip.getState(img);

originalParent = img.parentElement;
activeImage = img;

dtTitle.innerText = title;
dtBreadcrumb.innerText = title;
dtPrice.innerText = price;

detailStage.appendChild(img);

const tl = gsap.timeline({
onComplete: () => {
isAnimating = false;
}
});

// Set initial states
gsap.set(detailView, {
visibility: 'visible',
pointerEvents: 'all'
});
gsap.set(detailBackdrop, {
opacity: 0
});
gsap.set(detailStage, {
opacity: 0
});
gsap.set(backButton, {
opacity: 0
});
gsap.set(detailText, {
opacity: 0,
y: 30
});

// 1. Fade in Backdrop AND the Stage Container
tl.to([detailBackdrop, detailStage], {
opacity: 1,
duration: 0.5,
ease: "power2.out"
}, 0);

// 2. Flip Image
tl.add(Flip.from(state, {
duration: 0.6,
ease: "power3.inOut",
absolute: true,
zIndex: 999
}), 0);

// 3. Content
tl.to([detailText, backButton], {
opacity: 1,
y: 0,
duration: 0.5,
ease: "power2.out"
}, "-=0.2");
});
});

// --- Close Logic ---
backButton.addEventListener('click', () => {
if (isAnimating || !activeImage) return;
isAnimating = true;

const state = Flip.getState(activeImage);

originalParent.appendChild(activeImage);

const tl = gsap.timeline({
onComplete: () => {
gsap.set(detailView, {
visibility: 'hidden',
pointerEvents: 'none'
});
document.body.classList.remove('is-animating');
activeImage = null;
originalParent = null;
isAnimating = false;
}
});

// 1. Content Fade Out
tl.to([detailText, backButton], {
opacity: 0,
y: 20,
duration: 0.2,
ease: "power2.in"
}, 0);

// 2. FIXED: INSTANTLY hide the stage container so it doesn't leave a ghost
tl.set(detailStage, {
opacity: 0
}, 0);

// 3.  Flip Image Back
tl.add(Flip.from(state, {
duration: 0.6,
ease: "power3.inOut",
absolute: true,
zIndex: 999
}), 0);

// 4.  Fade Out Backdrop
tl.to(detailBackdrop, {
opacity: 0,
duration: 0.5,
ease: "power2.inOut"
}, 0);
});

Код: Выделить всё

:root {
--bg-color: #f3f4f6;
--text-main: #1f2937;
--text-muted: #6b7280;
--accent: #ff7b3d;
}

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: 'Inter', sans-serif;
background-color: var(--bg-color);
color: var(--text-main);
overflow-x: hidden;
}

.container {
max-width: 1200px;
margin: 0 auto;
padding: 60px 20px;
}

header {
margin-bottom: 60px;
text-align: center;
}

h1 {
font-size: 2.5rem;
font-weight: 800;
margin-bottom: 8px;
letter-spacing: -0.02em;
}

.subtitle {
color: var(--text-muted);
font-size: 1.1rem;
}

.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}

/* --- CARD STYLES --- */
.card {
background: #fffcf9;
border-radius: 30px;
width: 100%;
padding: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.05);
position: relative;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
display: flex;
flex-direction: column;
z-index: 1;
}

.card:hover {
transform: translateY(-10px);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.1);
}

body.is-animating .card {
pointer-events: none;
transition: none;
}

.image-container {
position: relative;
width: 100%;
margin-bottom: 45px;
z-index: 2;
}

.image-wrapper {
position: relative;
width: 100%;
aspect-ratio: 1 / 1.1;
border-radius: 25px;
overflow: hidden;
background: #e5e7eb;
transform: translateZ(0);
-webkit-mask-image: -webkit-radial-gradient(white, black);
}

.product-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
will-change: transform;
}

.badge {
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
background: white;
padding: 10px 24px;
border-radius: 50px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}

.badge-icon {
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}

.badge-text {
color: var(--accent);
font-weight: 700;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}

.card-info {
text-align: center;
padding-bottom: 10px;
}

.card-info h3 {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-main);
margin-bottom: 4px;
letter-spacing: -0.5px;
}

.price {
font-size: 0.95rem;
color: var(--text-muted);
font-weight: 400;
}

/* --- Detail View Overlay --- */
.detail-view {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 100;
pointer-events: none;
visibility: hidden;
display: flex;
flex-direction: column;
}

.detail-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(243, 244, 246, 0.95);
backdrop-filter: blur(10px);
opacity: 0;
z-index: -1;
}

.detail-content {
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 40px 20px;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
z-index: 2;
}

.breadcrumb {
margin-bottom: 40px;
color: var(--text-muted);
cursor: pointer;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 10px;
transition: color 0.2s;
width: fit-content;
opacity: 0;
}

.breadcrumb:hover {
color: var(--text-main);
}

.detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 80px;
align-items: start;
margin-top: 20px;
}

.detail-image-stage {
background: #fffcf9;
border-radius: 30px;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
aspect-ratio:  1 / 1.1;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
transform: translateZ(0);
opacity: 0;
}

.detail-text {
padding-top: 40px;
opacity: 0;
}

.detail-title {
font-size: 3rem;
font-weight: 800;
margin-bottom: 20px;
line-height: 1.1;
}

.detail-desc {
font-size: 1.15rem;
line-height: 1.7;
color: var(--text-muted);
margin-bottom: 50px;
}

.detail-price {
font-size: 1.5rem;
font-weight: 500;
display: block;
margin-bottom: 10px;
color: var(--text-muted);
}

.add-btn {
background: var(--accent);
color: white;
border: none;
padding: 18px 40px;
border-radius: 100px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}

.add-btn:hover {
transform: scale(1.05);
box-shadow: 0 10px 20px rgba(255, 123, 61, 0.3);
}

@media (max-width: 900px) {
.detail-grid {
grid-template-columns: 1fr;
gap:  40px;
}
}

Код: Выделить всё

Astro Shop - Profile Cards










Connect
Meet our talented team members.







[img]https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=800&h=800&fit=crop[/img]
                             alt="Marcus Reid"
class="product-img">

💻
Dev



Marcus Reid
15k followers





[img]https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=800&h=800&fit=crop[/img]
                             alt="Marcus Reid"
class="product-img">

💻
Dev



Marcus Reid
15k followers





[img]https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=800&h=800&fit=crop[/img]
                             alt="Marcus Reid"
class="product-img">

💻
Dev



Marcus Reid
15k followers





[img]https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=800&h=800&fit=crop[/img]
                             alt="Marcus Reid"
class="product-img">

💻
Dev



Marcus Reid
15k followers











Home / Profiles / Profile



Profile Name

Passionate about their craft and building community. Has been featured in multiple publications and continues to inspire others through their daily work.


10k Followers
Follow







Подробнее здесь: https://stackoverflow.com/questions/798 ... works-fine
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Html»