[260218]
- 카드 깜빡임 보완 - 모달 상태, 가격 표시 방법 수정
This commit is contained in:
@@ -8,6 +8,8 @@ import { scrollToImage } from './carousel.js';
|
||||
|
||||
console.log('Total products loaded:', productsData.length);
|
||||
|
||||
let lastThumbnailIndex = -1;
|
||||
|
||||
// HTML onclick에서 사용하기 위한 전역 등록
|
||||
window.openModal = openModal;
|
||||
window.closeModal = closeModal;
|
||||
@@ -326,31 +328,32 @@ window.handleThumbnailHover = (e, productId) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 페이드 업데이트 함수
|
||||
/** * 썸네일을 즉시 업데이트하는 함수
|
||||
* 페이드 애니메이션을 제거하여 반응성을 높이고 깜빡임을 방지합니다.
|
||||
*/
|
||||
function updateThumbnailWithFade(productId, newImageUrl, index) {
|
||||
const mainThumb = document.getElementById(`thumb-${productId}`);
|
||||
const fadeThumb = document.getElementById(`thumb-fade-${productId}`);
|
||||
const indicator = document.getElementById(`indicator-${productId}`);
|
||||
|
||||
if (!mainThumb || !fadeThumb) return;
|
||||
if (!mainThumb) return;
|
||||
|
||||
// 기존에 해당 카드에서 돌아가던 타이머가 있다면 즉시 제거
|
||||
// 1. 기존 페이드 타이머가 있다면 즉시 제거 (충돌 방지)
|
||||
if (fadeTimers[productId]) {
|
||||
clearTimeout(fadeTimers[productId]);
|
||||
delete fadeTimers[productId];
|
||||
}
|
||||
|
||||
// 페이드 레이어 세팅
|
||||
fadeThumb.style.transition = 'opacity 0.3s ease-in-out';
|
||||
fadeThumb.style.backgroundImage = `url("${newImageUrl}")`;
|
||||
fadeThumb.style.opacity = '1';
|
||||
|
||||
// 타이머 시작
|
||||
fadeTimers[productId] = setTimeout(() => {
|
||||
mainThumb.style.backgroundImage = `url("${newImageUrl}")`;
|
||||
// 2. 페이드 레이어(뒷배경)는 즉시 숨기고 메인 이미지만 즉시 교체
|
||||
// transition 없이 즉시 교체되도록 인라인 스타일로 제어합니다.
|
||||
if (fadeThumb) {
|
||||
fadeThumb.style.transition = 'none';
|
||||
fadeThumb.style.opacity = '0';
|
||||
delete fadeTimers[productId]; // 작업 완료 후 타이머 삭제
|
||||
}, 300);
|
||||
}
|
||||
|
||||
mainThumb.style.backgroundImage = `url("${newImageUrl}")`;
|
||||
|
||||
// 3. 인디케이터 UI 업데이트
|
||||
if (indicator) updateIndicatorUI(indicator, index);
|
||||
}
|
||||
|
||||
@@ -360,8 +363,9 @@ window.handleThumbnailLeave = (productId) => {
|
||||
resetThumbnail(productId);
|
||||
};
|
||||
|
||||
/** * 마우스가 나갔을 때 썸네일을 첫 번째 이미지로 복구하는 함수
|
||||
*/
|
||||
function resetThumbnail(productId) {
|
||||
// 1. 진행 중인 모든 페이드 타이머 즉시 파괴
|
||||
if (fadeTimers[productId]) {
|
||||
clearTimeout(fadeTimers[productId]);
|
||||
delete fadeTimers[productId];
|
||||
@@ -374,21 +378,17 @@ function resetThumbnail(productId) {
|
||||
const fadeThumb = document.getElementById(`thumb-fade-${productId}`);
|
||||
const indicator = document.getElementById(`indicator-${productId}`);
|
||||
|
||||
if (mainThumb && fadeThumb) {
|
||||
const firstImg = `url("${product.images[0]}")`;
|
||||
|
||||
// 2. 페이드 레이어를 즉시 숨김 (transition 방해 금지)
|
||||
fadeThumb.style.transition = 'none';
|
||||
fadeThumb.style.opacity = '0';
|
||||
|
||||
// 3. 두 레이어 모두 첫 번째 이미지로 강제 일치
|
||||
mainThumb.style.backgroundImage = firstImg;
|
||||
fadeThumb.style.backgroundImage = firstImg;
|
||||
|
||||
// 4. 다음 호버를 위해 트랜지션 복구
|
||||
setTimeout(() => {
|
||||
fadeThumb.style.transition = 'opacity 0.3s ease-in-out';
|
||||
}, 50);
|
||||
if (mainThumb) {
|
||||
const firstImgUrl = `url("${product.images[0]}")`;
|
||||
|
||||
// 즉시 첫 번째 이미지로 복구
|
||||
mainThumb.style.backgroundImage = firstImgUrl;
|
||||
|
||||
if (fadeThumb) {
|
||||
fadeThumb.style.transition = 'none';
|
||||
fadeThumb.style.opacity = '0';
|
||||
fadeThumb.style.backgroundImage = firstImgUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (indicator) updateIndicatorUI(indicator, 0);
|
||||
@@ -428,17 +428,24 @@ window.handleTouchMove = (e, productId) => {
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
const product = productsData.find((p) => p.id === productId);
|
||||
const step = cardWidth / product.images.length;
|
||||
let index = Math.floor(Math.abs(diffX) / step);
|
||||
index = Math.max(0, Math.min(product.images.length - 1, index));
|
||||
|
||||
// [수정 핵심] 인덱스가 이전과 같으면 함수를 종료하여 불필요한 리렌더링 방지
|
||||
if (lastThumbnailIndex === index) return;
|
||||
lastThumbnailIndex = index;
|
||||
|
||||
const mainThumb = document.getElementById(`thumb-${productId}`);
|
||||
const fadeThumb = document.getElementById(`thumb-fade-${productId}`);
|
||||
|
||||
if (mainThumb && fadeThumb) {
|
||||
// 드래그 중에는 페이드 없이 즉시 교체 (반응성 우선)
|
||||
// [수정 핵심] 인덱스가 실제로 변했을 때만 스타일을 바꿉니다.
|
||||
if (mainThumb && lastThumbnailIndex !== index) {
|
||||
lastThumbnailIndex = index; // 새 인덱스 저장
|
||||
|
||||
mainThumb.style.backgroundImage = `url("${product.images[index]}")`;
|
||||
fadeThumb.style.opacity = '0'; // 페이드 레이어 숨김
|
||||
if (fadeThumb) fadeThumb.style.opacity = '0';
|
||||
updateIndicator(productId, index);
|
||||
}
|
||||
}
|
||||
@@ -452,6 +459,7 @@ window.handleTouchEnd = (e, productId) => {
|
||||
} else {
|
||||
resetThumbnail(productId); // 드래그 종료 시 확실한 리셋
|
||||
}
|
||||
lastThumbnailIndex = -1; // 초기화
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** 상품 상세 모달 (열기/닫기·콘텐츠 채우기·링크 복사) */
|
||||
import { productsData } from './state.js';
|
||||
import { initBetterCarousel } from './carousel.js';
|
||||
import { TAG_STYLES, TAG_DEFAULT_STYLE } from './config.js';
|
||||
import { TAG_STYLES, TAG_DEFAULT_STYLE, PRODUCT_CONDITIONS } from './config.js';
|
||||
|
||||
export function openModal(id) {
|
||||
const product = productsData.find((p) => p.id === id);
|
||||
@@ -11,7 +11,7 @@ export function openModal(id) {
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
modal.classList.add('flex');
|
||||
|
||||
|
||||
const images = product.images;
|
||||
|
||||
const loopImages = [images[images.length - 1], ...images, images[0]];
|
||||
@@ -51,7 +51,6 @@ export function openModal(id) {
|
||||
document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml;
|
||||
document.getElementById('modal-dots').innerHTML = dotsHtml;
|
||||
document.getElementById('modal-title').textContent = product.title;
|
||||
document.getElementById('modal-price').textContent = `${product.currency}${product.price.toLocaleString()}`;
|
||||
|
||||
const modalCategory = document.getElementById('modal-category');
|
||||
if (modalCategory) modalCategory.textContent = product.category;
|
||||
@@ -95,17 +94,21 @@ export function openModal(id) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 제품 상태(specs.condition): 값이 있을 때만 행 노출
|
||||
const conditionText = product.specs?.condition;
|
||||
const conditionKey = product.specs?.condition;
|
||||
const isVerified = product.specs?.isVerified;
|
||||
|
||||
// PRODUCT_CONDITIONS에서 라벨을 가져오되, 없으면 원본 키 표시
|
||||
const conditionData = PRODUCT_CONDITIONS[conditionKey];
|
||||
// 데이터가 객체라면 .label을 쓰고, 아니면 원본 키를 씁니다.
|
||||
const conditionLabel = conditionData?.label || conditionKey || '';
|
||||
|
||||
const conditionValueEl = document.getElementById('modal-condition');
|
||||
const conditionRowEl = document.getElementById('modal-condition-row');
|
||||
const conditionRowWrap = conditionRowEl?.parentElement; // 라벨+값 전체 행
|
||||
const verifiedIcon = document.getElementById('modal-verified-icon');
|
||||
if (conditionText && String(conditionText).trim() !== '') {
|
||||
if (conditionValueEl) conditionValueEl.textContent = conditionText;
|
||||
if (conditionKey && String(conditionKey).trim() !== '') {
|
||||
if (conditionValueEl) conditionValueEl.textContent = conditionLabel;
|
||||
if (conditionRowWrap) {
|
||||
conditionRowWrap.classList.remove('hidden');
|
||||
conditionRowWrap.classList.add('flex');
|
||||
@@ -121,17 +124,30 @@ export function openModal(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// 가격 표시 로직 수정
|
||||
const priceElement = document.getElementById('modal-price');
|
||||
if (priceElement) {
|
||||
// 가격 표시 로직 수정
|
||||
const priceValueEl = document.getElementById('modal-price');
|
||||
const priceRowEl = document.getElementById('modal-price-row');
|
||||
const priceRowWrap = priceRowEl?.parentElement;
|
||||
|
||||
if (priceValueEl && priceRowWrap) {
|
||||
// [중요] 새로운 상품을 열 때마다 일단 hidden을 제거하여 초기화합니다.
|
||||
priceRowWrap.classList.remove('hidden');
|
||||
|
||||
// 1. 최우선 순위: 미판매 상태 체크
|
||||
if (product.status === '미판매') {
|
||||
// 미판매 상태일 때의 처리
|
||||
priceElement.textContent = 'NOT FOR SALE';
|
||||
priceElement.classList.add('text-gray-500'); // 시각적으로 구분되도록 스타일 추가 가능
|
||||
} else {
|
||||
// 정상 판매 중인 경우
|
||||
priceElement.textContent = `${product.currency}${product.price.toLocaleString()}`;
|
||||
priceElement.classList.remove('text-gray-500');
|
||||
priceValueEl.textContent = 'NOT FOR SALE';
|
||||
priceValueEl.classList.add('text-gray-500');
|
||||
}
|
||||
// 2. 가격 데이터가 아예 없는 경우 (null, undefined, 빈 문자열)
|
||||
// 숫자 0은 가격으로 인정하고 싶다면 (product.price === null || product.price === undefined)로 씁니다.
|
||||
else if (product.price === null || product.price === undefined || product.price === '') {
|
||||
priceRowWrap.classList.add('hidden');
|
||||
}
|
||||
// 3. 가격이 존재하는 경우 (0원을 포함하여 값이 있는 경우)
|
||||
else {
|
||||
const currency = product.currency || '₩';
|
||||
priceValueEl.textContent = `${currency}${Number(product.price).toLocaleString()}`;
|
||||
priceValueEl.classList.remove('text-gray-500');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +157,7 @@ export function openModal(id) {
|
||||
if (product.status === '미판매') {
|
||||
modalDesc.innerHTML = '<p class="text-gray-400 italic">판매중인 상품이 아니기에 정보가 제공되지 않습니다.</p>';
|
||||
} else {
|
||||
modalDesc.innerHTML = Array.isArray(product.fullDescription)
|
||||
? product.fullDescription.join('<br>')
|
||||
: product.fullDescription || '';
|
||||
modalDesc.innerHTML = Array.isArray(product.fullDescription) ? product.fullDescription.join('<br>') : product.fullDescription || '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +192,7 @@ export function openModal(id) {
|
||||
|
||||
export function closeModal() {
|
||||
const modal = document.getElementById('product-modal');
|
||||
|
||||
|
||||
// 1. 이미지 캐러셀 영역 초기화
|
||||
const carouselContainer = document.getElementById('modal-main-carousel-container');
|
||||
if (carouselContainer) {
|
||||
|
||||
Reference in New Issue
Block a user