add
This commit is contained in:
@@ -79,7 +79,7 @@ function initProducts(productsData) {
|
||||
preview.classList.add('fixed-mode');
|
||||
preview.style.display = 'flex';
|
||||
};
|
||||
hotspotLayer.appendChild(hs);
|
||||
// hotspotLayer.appendChild(hs);
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
function initProducts(productsData) {
|
||||
const map = document.getElementById('main-map');
|
||||
const hotspotLayer = document.getElementById('hotspot-layer');
|
||||
const preview = document.getElementById('hover-preview');
|
||||
const previewGrid = document.getElementById('preview-grid');
|
||||
// const coordDisplay = document.getElementById('coord-display');
|
||||
|
||||
// 그룹화 로직
|
||||
const groups = {};
|
||||
productsData.forEach(p => {
|
||||
const key = `${parseFloat(p.xPct).toFixed(1)}_${parseFloat(p.yPct).toFixed(1)}`;
|
||||
if (!groups[key]) groups[key] = [];
|
||||
groups[key].push(p);
|
||||
});
|
||||
|
||||
Object.keys(groups).forEach(key => {
|
||||
const group = groups[key];
|
||||
const hs = document.createElement('div');
|
||||
hs.className = 'hotspot';
|
||||
hs.style.left = group[0].xPct + '%';
|
||||
hs.style.top = group[0].yPct + '%';
|
||||
hs.style.transform = 'translate(-50%, -50%)';
|
||||
|
||||
hs.onmouseenter = () => {
|
||||
preview.style.display = 'block';
|
||||
previewGrid.innerHTML = group.map(p => `
|
||||
<div class="product-item">
|
||||
<div class="product-img-box"><img src="${p.img_url}" onerror="this.src='https://via.placeholder.com/200x120?text=No+Image'"></div>
|
||||
<div class="text-[10px] text-blue-400 font-bold mb-1">${p.page}</div>
|
||||
<h4 class="text-white text-[13px] font-bold truncate">${p.title}</h4>
|
||||
<p class="text-slate-500 text-[10px] line-clamp-2">${p.desc}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
};
|
||||
hs.onmouseleave = () => preview.style.display = 'none';
|
||||
hs.onmousemove = (e) => {
|
||||
let x = e.clientX + 30;
|
||||
let y = e.clientY + 30;
|
||||
if (x + 540 > window.innerWidth) x = e.clientX - 560;
|
||||
if (y + preview.offsetHeight > window.innerHeight) y = window.innerHeight - preview.offsetHeight - 20;
|
||||
preview.style.left = x + 'px';
|
||||
preview.style.top = y + 'px';
|
||||
};
|
||||
hotspotLayer.appendChild(hs);
|
||||
});
|
||||
|
||||
// 좌표 도구
|
||||
// map.onmousemove = (e) => {
|
||||
// const r = map.getBoundingClientRect();
|
||||
// coordDisplay.textContent = `X: ${((e.clientX-r.left)/r.width*100).toFixed(1)}%, Y: ${((e.clientY-r.top)/r.height*100).toFixed(1)}%`;
|
||||
// };
|
||||
// map.onclick = (e) => {
|
||||
// const r = map.getBoundingClientRect();
|
||||
// prompt("좌표:", `xPct: ${((e.clientX-r.left)/r.width*100).toFixed(1)}, yPct: ${((e.clientY-r.top)/r.height*100).toFixed(1)}`);
|
||||
// };
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
function initProducts(productsData) {
|
||||
const map = document.getElementById('main-map');
|
||||
const hotspotLayer = document.getElementById('hotspot-layer');
|
||||
const preview = document.getElementById('hover-preview');
|
||||
const previewGrid = document.getElementById('preview-grid');
|
||||
// const coordDisplay = document.getElementById('coord-display');
|
||||
|
||||
let isFixed = false; // 팝업 고정 상태 변수
|
||||
|
||||
const groups = {};
|
||||
productsData.forEach(p => {
|
||||
const key = `${parseFloat(p.x_pct || p.xPct).toFixed(1)}_${parseFloat(p.y_pct || p.yPct).toFixed(1)}`;
|
||||
if (!groups[key]) groups[key] = [];
|
||||
groups[key].push(p);
|
||||
});
|
||||
|
||||
Object.keys(groups).forEach(key => {
|
||||
const group = groups[key];
|
||||
const hs = document.createElement('div');
|
||||
hs.className = 'hotspot';
|
||||
hs.style.left = (group[0].x_pct || group[0].xPct) + '%';
|
||||
hs.style.top = (group[0].y_pct || group[0].yPct) + '%';
|
||||
hs.style.transform = 'translate(-50%, -50%)';
|
||||
|
||||
// 팝업 내용 생성 함수
|
||||
const updateContent = () => {
|
||||
const gridClass = group.length === 1 ? 'single-item' : 'multi-items';
|
||||
previewGrid.className = `products-grid ${gridClass}`;
|
||||
previewGrid.innerHTML = group.map(p => `
|
||||
<div class="product-item">
|
||||
<div class="product-img-box"><img src="${p.img_url}" onerror="this.src='https://via.placeholder.com/200x120?text=No+Image'"></div>
|
||||
<div class="info-content">
|
||||
<div class="text-[11px] text-blue-400 font-bold mb-1">${p.page}</div>
|
||||
<h4 class="text-white text-[16px] font-bold mb-2">${p.title}</h4>
|
||||
<p class="text-slate-400 text-[13px] leading-relaxed">${p.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
};
|
||||
|
||||
// 마우스 진입 (호버 시작)
|
||||
hs.onmouseenter = (e) => {
|
||||
if (isFixed) return;
|
||||
updateContent();
|
||||
preview.style.display = 'block';
|
||||
};
|
||||
|
||||
// 마우스 이동 (팝업이 마우스를 따라다님)
|
||||
hs.onmousemove = (e) => {
|
||||
if (isFixed) return;
|
||||
let x = e.clientX + 20;
|
||||
let y = e.clientY - (preview.offsetHeight / 2);
|
||||
if (x + 560 > window.innerWidth) x = e.clientX - 580;
|
||||
if (y < 20) y = 20;
|
||||
preview.style.left = x + 'px';
|
||||
preview.style.top = y + 'px';
|
||||
};
|
||||
|
||||
// 마우스 이탈 (호버 종료)
|
||||
hs.onmouseleave = () => {
|
||||
if (isFixed) return;
|
||||
preview.style.display = 'none';
|
||||
};
|
||||
|
||||
// 클릭 시 고정
|
||||
hs.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
isFixed = true;
|
||||
updateContent();
|
||||
preview.style.display = 'block';
|
||||
|
||||
// 고정 시에는 화면 중앙 근처 적절한 위치에 배치
|
||||
let x = e.clientX + 30;
|
||||
let y = e.clientY - (preview.offsetHeight / 2);
|
||||
if (x + 720 > window.innerWidth) x = e.clientX - 730;
|
||||
if (y < 20) y = 20;
|
||||
|
||||
preview.style.left = x + 'px';
|
||||
preview.style.top = y + 'px';
|
||||
preview.classList.add('fixed-mode');
|
||||
};
|
||||
|
||||
hotspotLayer.appendChild(hs);
|
||||
});
|
||||
|
||||
// 외부 클릭 시 고정 해제 및 닫기
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!preview.contains(e.target)) {
|
||||
isFixed = false;
|
||||
preview.style.display = 'none';
|
||||
preview.classList.remove('fixed-mode');
|
||||
}
|
||||
});
|
||||
// // 좌표 도구
|
||||
// map.onmousemove = (e) => {
|
||||
// const r = map.getBoundingClientRect();
|
||||
// coordDisplay.textContent = `X: ${((e.clientX-r.left)/r.width*100).toFixed(1)}%, Y: ${((e.clientY-r.top)/r.height*100).toFixed(1)}%`;
|
||||
// };
|
||||
// map.onclick = (e) => {
|
||||
// const r = map.getBoundingClientRect();
|
||||
// prompt("좌표:", `xPct: ${((e.clientX-r.left)/r.width*100).toFixed(1)}, yPct: ${((e.clientY-r.top)/r.height*100).toFixed(1)}`);
|
||||
// };
|
||||
}
|
||||
@@ -1,16 +1,24 @@
|
||||
/**
|
||||
* [최종 통합본] 제품 사이드 메뉴 및 메인 맵 연동 모듈 (products2.js)
|
||||
* 1: 중앙팝업, 2: 점선연결, 3: 제자리확대, 4: 1초 지연 후 중앙팝업
|
||||
* 5: 점선연결 + 1초후 중앙팝업
|
||||
* 1: 중앙팝업, 2: 점선연결, 3: 제자리확대, 4: 1초 지연 후 중앙팝업, 5: 팝업 박스 표시, 6: 점선연결 + 팝업 박스 표시
|
||||
*/
|
||||
(function() {
|
||||
// 💡 설정: 원하는 모드 번호를 입력하세요.
|
||||
const POPUP_MODE = 5;
|
||||
const POPUP_MODE = 6; // 6번 모드로 설정합니다.
|
||||
// 💡 설정: 팝업 박스에 표시할 내용의 타입을 선택하세요 ('text' 또는 'image').
|
||||
const POPUP_CONTENT_DISPLAY_TYPE = 'image'; // 'text' 또는 'image'로 변경하여 테스트
|
||||
|
||||
let popupTimer = null; // 4번 모드용 타이머
|
||||
let hoverTimeout = null; // 호버 인텐트 지연 타이머
|
||||
const HOVER_DELAY = 50; // 마우스가 완전히 벗어났다고 판단할 지연 시간 (밀리초)
|
||||
const MAX_MOVE_DISTANCE = 10; // 팝업이 핫스팟에서 이동할 수 있는 최대 거리 (px)
|
||||
|
||||
|
||||
// 모든 효과와 타이머를 제거하는 청소기 함수 (popupBox 숨기는 로직은 제외)
|
||||
function clearAllPopupsContent() { // 함수 이름 변경: 내용만 초기화
|
||||
console.log('clearAllPopupsContent called!');
|
||||
// console.trace(); // 이제 제거해도 됩니다.
|
||||
|
||||
// 모든 효과와 타이머를 제거하는 청소기 함수
|
||||
function clearAllPopups() {
|
||||
// 실행 중인 타이머가 있으면 취소
|
||||
if (popupTimer) {
|
||||
clearTimeout(popupTimer);
|
||||
@@ -35,12 +43,24 @@
|
||||
img.style.transform = '';
|
||||
}
|
||||
});
|
||||
|
||||
// popupContent 내용 및 스타일 초기화 (popupBox 자체는 건드리지 않음)
|
||||
const popupBox = document.getElementById('popup-box');
|
||||
if (popupBox) {
|
||||
const popupContent = popupBox.querySelector('.content');
|
||||
if (popupContent) {
|
||||
popupContent.innerHTML = ''; // 내용 비우기
|
||||
popupContent.style.cssText = ''; // 모든 인라인 스타일 초기화
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initProductEvents() {
|
||||
const productItems = document.querySelectorAll('.product-section .product-item');
|
||||
const mainArea = document.querySelector('.product-main');
|
||||
const section = document.querySelector('.product-section');
|
||||
const popupBox = document.getElementById('popup-box');
|
||||
const popupContent = popupBox ? popupBox.querySelector('.content') : null;
|
||||
|
||||
if (!productItems.length || !mainArea) return;
|
||||
|
||||
@@ -48,24 +68,262 @@
|
||||
const imgElement = item.querySelector('.product-img img');
|
||||
const imgSrc = item.dataset.img || (imgElement ? imgElement.src : '');
|
||||
const proLink = item.dataset.prolink;
|
||||
const x1 = parseFloat(item.dataset.x1);
|
||||
const y1 = parseFloat(item.dataset.y1);
|
||||
|
||||
// Get product details from data attributes
|
||||
const title = item.dataset.title;
|
||||
const desc = item.dataset.desc;
|
||||
const content = item.dataset.content;
|
||||
|
||||
|
||||
item.addEventListener('mouseenter', function() {
|
||||
clearAllPopups(); // 새로 시작하기 전 초기화
|
||||
console.log('Mouse entered product-item:', title);
|
||||
|
||||
const x1 = this.dataset.x1;
|
||||
const y1 = this.dataset.y1;
|
||||
// 섹션 숨김 지연 타이머가 있다면 취소 (호버 깜빡임 방지)
|
||||
if (hoverTimeout) {
|
||||
console.log('Clearing hoverTimeout on mouseenter.');
|
||||
clearTimeout(hoverTimeout);
|
||||
hoverTimeout = null;
|
||||
}
|
||||
|
||||
if (x1 && y1) {
|
||||
// 1. 핫스팟(블루 스폿)은 어떤 모드든 즉시 표시
|
||||
const hotspot = document.querySelector('.product-hotspot');
|
||||
if (hotspot) {
|
||||
hotspot.style.left = x1 + '%';
|
||||
hotspot.style.top = y1 + '%';
|
||||
hotspot.style.display = 'block';
|
||||
// 새로운 팝업을 띄우기 전에 이전 팝업의 내용만 초기화
|
||||
clearAllPopupsContent();
|
||||
|
||||
// 팝업 박스를 명시적으로 보이는 상태로 설정 (애니메이션 시작 전)
|
||||
if (popupBox) {
|
||||
gsap.killTweensOf(popupBox); // 혹시 모를 숨김 애니메이션 중단
|
||||
popupBox.style.display = 'block';
|
||||
|
||||
// 애니메이션 초기 상태 설정
|
||||
popupBox.style.width = '0px';
|
||||
popupBox.style.height = '0px';
|
||||
popupBox.style.opacity = '0';
|
||||
popupBox.style.transform = 'none'; // transform 제거
|
||||
|
||||
// mainRect는 popupBox를 포함하는 product-main의 크기 정보
|
||||
|
||||
|
||||
// 핫스팟 위치에 따라 팝업의 초기 시작 지점 설정
|
||||
const isLeft = this.closest('.product-side').classList.contains('left');
|
||||
const rect = item.getBoundingClientRect();//this.getBoundingClientRect();
|
||||
const mainRect = mainArea.getBoundingClientRect();
|
||||
const startX = isLeft ? rect.right : rect.left;
|
||||
const startY = rect.bottom + (rect.height / 2); //rect.bottom + (rect.height / 2);
|
||||
|
||||
|
||||
if (isLeft) {
|
||||
// 핫스팟이 왼쪽에 있으면 팝업은 mainArea의 좌측 상단에서 시작
|
||||
popupBox.style.left = startX+'px';
|
||||
popupBox.style.top = startY+'px';
|
||||
} else {
|
||||
// 핫스팟이 오른쪽에 있으면 팝업은 mainArea의 우측 상단에서 시작
|
||||
popupBox.style.left = startX +'px';
|
||||
popupBox.style.top = startY +'px';
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNaN(x1) && !isNaN(y1)) {
|
||||
// 1. 핫스팟(블루 스폿)은 어떤 모드든 즉시 표시
|
||||
if(x1 && y1) {
|
||||
const hotspot = document.querySelector('.product-hotspot');
|
||||
if (hotspot) {
|
||||
hotspot.style.left = x1 + '%';
|
||||
hotspot.style.top = y1 + '%';
|
||||
hotspot.style.display = 'block';
|
||||
}
|
||||
}
|
||||
// 2. 모드별 효과 실행
|
||||
if (POPUP_MODE === 4) {
|
||||
if (POPUP_MODE === 5 || POPUP_MODE === 6) {
|
||||
if (popupBox && popupContent) {
|
||||
// Set popup dimensions
|
||||
const popupWidth = 600; // 요청하신 대로 600
|
||||
const popupHeight = 500; // 요청하신 대로 500
|
||||
const POPUP_HORIZONTAL_OFFSET = 20; // 핫스팟과 팝업 사이의 가로 간격 (px)
|
||||
|
||||
// popupContent의 기본 스타일 설정 (매우 중요!)
|
||||
popupContent.style.width = '100%';
|
||||
popupContent.style.height = '100%';
|
||||
popupContent.style.display = 'flex';
|
||||
popupContent.style.alignItems = 'center';
|
||||
popupContent.style.justifyContent = 'center';
|
||||
popupContent.style.color = '#fff'; // 기본 텍스트 색상
|
||||
popupContent.style.overflow = 'hidden'; // 이 줄을 추가합니다.
|
||||
popupContent.style.padding = '0'; // 이 줄을 추가합니다.
|
||||
|
||||
// Populate popup content based on POPUP_CONTENT_DISPLAY_TYPE
|
||||
if (POPUP_CONTENT_DISPLAY_TYPE === 'image' && imgElement) {
|
||||
popupContent.innerHTML = ''; // Clear previous content
|
||||
popupContent.style.background = 'none'; // 이미지 표시 시 배경 제거
|
||||
|
||||
const clone = imgElement.cloneNode(true);
|
||||
clone.src = imgSrc;
|
||||
// clone.id = 'active-popup-img'; // 이 줄을 제거합니다!
|
||||
clone.style.width = '100%'; // Make image fit popup box
|
||||
clone.style.height = '100%';
|
||||
clone.style.objectFit = 'contain'; // 'cover'에서 'contain'으로 변경
|
||||
// clone.style.cursor = proLink ? 'pointer' : 'default'; // 이 줄을 주석 처리하거나 제거합니다.
|
||||
clone.style.pointerEvents = 'none'; // 이미지 위에서 마우스 이벤트 방지
|
||||
if (proLink) {
|
||||
clone.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
|
||||
// console.log('--- Debugging appendChild ---');
|
||||
// console.log('proLink:', proLink);
|
||||
// console.log('Clone element:', clone);
|
||||
// console.log('popupContent BEFORE append:', popupContent.innerHTML);
|
||||
// console.log('popupContent.style.cssText BEFORE append:', popupContent.style.cssText); // 스타일 확인
|
||||
popupContent.appendChild(clone);
|
||||
// console.log('popupContent AFTER append:', popupContent.innerHTML);
|
||||
// console.log('popupContent.style.cssText AFTER append:', popupContent.style.cssText); // 스타일 확인
|
||||
// console.log('popupContent children AFTER append:', popupContent.children.length);
|
||||
// console.log('popupContent ', popupContent);
|
||||
// console.log('popupBox ', popupBox);
|
||||
// console.log('--- End Debugging ---');
|
||||
|
||||
} else { // Default to text content or if POPUP_CONTENT_DISPLAY_TYPE is 'text'
|
||||
popupContent.innerHTML = `
|
||||
<h3>${title}</h3>
|
||||
<p>${desc}</p>
|
||||
<p>${content}</p>
|
||||
${proLink ? `<a href="${proLink}" style="color: white; text-decoration: underline;">자세히 보기</a>` : ''}
|
||||
`;
|
||||
// 텍스트 표시 시 배경 적용
|
||||
popupContent.style.background = 'linear-gradient(135deg, #667eea, #764ba2)';
|
||||
}
|
||||
|
||||
|
||||
const isLeft = this.closest('.product-side').classList.contains('left');
|
||||
const rect = item.getBoundingClientRect();
|
||||
const mainRect = mainArea.getBoundingClientRect();
|
||||
const startX = isLeft ? rect.right : rect.left;
|
||||
const startY = rect.bottom + (rect.height / 2);
|
||||
// const hotspotX_px = startX +popupWidth;
|
||||
// const hotspotY_px = !isLeft ? startY+popupHeight : startY - popupHeight;
|
||||
|
||||
const corners = {
|
||||
topLeft: {
|
||||
x: rect.left ,
|
||||
y: rect.top
|
||||
},
|
||||
topRight: {
|
||||
x: rect.right ,
|
||||
y: rect.top
|
||||
},
|
||||
bottomLeft: {
|
||||
x: rect.left ,
|
||||
y: rect.bottom
|
||||
},
|
||||
bottomRight: {
|
||||
x: rect.right ,
|
||||
y: rect.bottom
|
||||
}
|
||||
};
|
||||
// const hotspotX_px = isLeft ? rect.right : rect.left - popupWidth -POPUP_HORIZONTAL_OFFSET ;
|
||||
// const hotspotY_px = isLeft ? rect.top - rect.height :rect.y;
|
||||
const hotspotX_px = isLeft ? 462.0833435058594 : 922.0833740234375 ;
|
||||
const hotspotY_px = isLeft ? 174.66668701171875 :247.33334350585938;
|
||||
console.log(isLeft);
|
||||
console.log(mainRect);
|
||||
console.log(rect);
|
||||
console.log(hotspotX_px);
|
||||
console.log(hotspotY_px);
|
||||
// Determine if hotspot is on the left or right half of the mainArea
|
||||
const isHotspotLeftHalf = x1 < 50;
|
||||
|
||||
// Calculate ideal raw targetLeft based on side
|
||||
let idealTargetLeft_px;
|
||||
if (isHotspotLeftHalf) {
|
||||
// Hotspot on left, popup appears to its right
|
||||
idealTargetLeft_px = hotspotX_px + POPUP_HORIZONTAL_OFFSET;
|
||||
} else {
|
||||
// Hotspot on right, popup appears to its left
|
||||
idealTargetLeft_px = hotspotX_px - popupWidth - POPUP_HORIZONTAL_OFFSET;
|
||||
}
|
||||
|
||||
// Calculate ideal raw targetTop (vertically centered with hotspot)
|
||||
let idealTargetTop_px = hotspotY_px - (popupHeight / 2);
|
||||
|
||||
// Clamp ideal targetLeft and targetTop to stay within mainArea bounds
|
||||
let clampedIdealTargetLeft_px = Math.max(0, Math.min(idealTargetLeft_px, mainRect.width - popupWidth));
|
||||
let clampedIdealTargetTop_px = Math.max(0, Math.min(idealTargetTop_px, mainRect.height - popupHeight));
|
||||
|
||||
// Calculate actual final position, limiting movement distance
|
||||
let finalTargetLeft_px = clampedIdealTargetLeft_px;
|
||||
let finalTargetTop_px = clampedIdealTargetTop_px;
|
||||
|
||||
// Calculate distance from hotspot center to clamped ideal popup center
|
||||
const hotspotCenterToPopupCenterX = clampedIdealTargetLeft_px + (popupWidth / 2) - hotspotX_px;
|
||||
const hotspotCenterToPopupCenterY = clampedIdealTargetTop_px + (popupHeight / 2) - hotspotY_px;
|
||||
const distance = Math.sqrt(
|
||||
hotspotCenterToPopupCenterX * hotspotCenterToPopupCenterX +
|
||||
hotspotCenterToPopupCenterY * hotspotCenterToPopupCenterY
|
||||
);
|
||||
|
||||
if (distance > MAX_MOVE_DISTANCE) {
|
||||
// If distance exceeds max, scale down the movement vector
|
||||
const ratio = MAX_MOVE_DISTANCE / distance;
|
||||
finalTargetLeft_px = hotspotX_px + (hotspotCenterToPopupCenterX * ratio) - (popupWidth / 2);
|
||||
finalTargetTop_px = hotspotY_px + (hotspotCenterToPopupCenterY * ratio) - (popupHeight / 2);
|
||||
|
||||
// Re-clamp to ensure it stays within mainArea bounds after adjustment
|
||||
finalTargetLeft_px = Math.max(0, Math.min(finalTargetLeft_px, mainRect.width - popupWidth));
|
||||
finalTargetTop_px = Math.max(0, Math.min(finalTargetTop_px, mainRect.height - popupHeight));
|
||||
}
|
||||
|
||||
|
||||
// 팝업 박스를 보여주는 애니메이션 (크기, 위치, 투명도 동시 애니메이션)
|
||||
gsap.to(popupBox, {
|
||||
width: popupWidth, // 최종 너비
|
||||
height: popupHeight, // 최종 높이
|
||||
left: hotspotX_px,//finalTargetLeft_px, // 최종 왼쪽 위치
|
||||
top: hotspotY_px,//finalTargetTop_px, // 최종 위쪽 위치
|
||||
opacity: 1, // 최종 투명도
|
||||
transform: 'none', // transform 속성 제거
|
||||
duration: 1.5, // 요청하신 대로 1.5초로 변경
|
||||
ease: "power2.out",
|
||||
pointerEvents: 'auto',
|
||||
onComplete: () => { // 이 onComplete 콜백을 추가합니다.
|
||||
popupBox.style.opacity = '1'; // 애니메이션 완료 후 opacity를 1로 강제
|
||||
popupBox.style.display = 'block'; // 애니메이션 완료 후 display를 block으로 강제
|
||||
popupBox.style.transform = 'none'; // transform 속성 초기화 (중요)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Mode 6 specific: Add connector line
|
||||
if (POPUP_MODE === 6 &&x1 &&y1) {
|
||||
const rect = this.getBoundingClientRect();
|
||||
const mainRect = mainArea.getBoundingClientRect();
|
||||
const targetX = mainRect.left + (mainRect.width * (x1 / 100));
|
||||
const targetY = mainRect.top + (mainRect.height * (y1 / 100));
|
||||
const isLeft = this.closest('.product-side').classList.contains('left');
|
||||
const startX = isLeft ? rect.right : rect.left;
|
||||
const startY = rect.top + (rect.height / 2);
|
||||
const dx = targetX - startX;
|
||||
const dy = targetY - startY;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
let angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
||||
if (!isLeft) angle += 180;
|
||||
|
||||
this.style.setProperty('--line-width', dist + 'px');
|
||||
this.style.setProperty('--line-angle', angle + 'deg');
|
||||
this.classList.add('show-connector');
|
||||
requestAnimationFrame(() => this.classList.add('active'));
|
||||
// Note: imgElement.style.transform = 'scale(1.2)' is not applied here
|
||||
// as the image is either in the popup or not the primary focus for mode 6.
|
||||
// If you want the side image to scale, uncomment the following:
|
||||
// if (imgElement) imgElement.style.transform = 'scale(1.2)';
|
||||
|
||||
if (imgElement && proLink) {
|
||||
// This click handler is for the side image, not the popup image
|
||||
// imgElement.style.cursor = 'pointer'; // 이 줄을 주석 처리하거나 제거합니다.
|
||||
imgElement.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
}
|
||||
|
||||
} else if (POPUP_MODE === 4) {
|
||||
// [모드 4] 1초 지연 후 중앙 팝업
|
||||
popupTimer = setTimeout(() => {
|
||||
if (imgElement) {
|
||||
@@ -99,8 +357,8 @@
|
||||
// [모드 2] 점선 연결선 정밀 계산
|
||||
const rect = this.getBoundingClientRect();
|
||||
const mainRect = mainArea.getBoundingClientRect();
|
||||
const targetX = mainRect.left + (mainRect.width * (parseFloat(x1) / 100));
|
||||
const targetY = mainRect.top + (mainRect.height * (parseFloat(y1) / 100));
|
||||
const targetX = mainRect.left + (mainRect.width * (x1 / 100));
|
||||
const targetY = mainRect.top + (mainRect.height * (y1 / 100));
|
||||
const isLeft = this.closest('.product-side').classList.contains('left');
|
||||
const startX = isLeft ? rect.right : rect.left;
|
||||
const startY = rect.top + (rect.height / 2);
|
||||
@@ -120,57 +378,15 @@
|
||||
imgElement.style.cursor = 'pointer';
|
||||
imgElement.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
|
||||
} else if (POPUP_MODE === 3 && imgElement) {
|
||||
} else if (POPUP_MODE === 3 && imgElement) { // 3번 모드 로직 추가
|
||||
// [모드 3] 제자리 600 확대
|
||||
imgElement.src = imgSrc;
|
||||
// imgElement.src = imgSrc;
|
||||
imgElement.classList.add('mode-self-zoom');
|
||||
if (proLink) {
|
||||
imgElement.style.cursor = 'pointer';
|
||||
imgElement.style.pointerEvents = 'auto';
|
||||
imgElement.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
} else if(POPUP_MODE === 5 && imgElement) {
|
||||
// [모드 2] 점선 연결선 정밀 계산
|
||||
const rect = this.getBoundingClientRect();
|
||||
const mainRect = mainArea.getBoundingClientRect();
|
||||
const targetX = mainRect.left + (mainRect.width * (parseFloat(x1) / 100));
|
||||
const targetY = mainRect.top + (mainRect.height * (parseFloat(y1) / 100));
|
||||
const isLeft = this.closest('.product-side').classList.contains('left');
|
||||
const startX = isLeft ? rect.right : rect.left;
|
||||
const startY = rect.top + (rect.height / 2);
|
||||
const dx = targetX - startX;
|
||||
const dy = targetY - startY;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
let angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
||||
if (!isLeft) angle += 180;
|
||||
|
||||
this.style.setProperty('--line-width', dist + 'px');
|
||||
this.style.setProperty('--line-angle', angle + 'deg');
|
||||
this.classList.add('show-connector');
|
||||
requestAnimationFrame(() => this.classList.add('active'));
|
||||
if (imgElement) imgElement.style.transform = 'scale(1.2)';
|
||||
|
||||
if (imgElement && proLink) {
|
||||
imgElement.style.cursor = 'pointer';
|
||||
imgElement.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
|
||||
var popupTimer = setTimeout(() => {
|
||||
if (imgElement) {
|
||||
const clone = imgElement.cloneNode(true);
|
||||
clone.id = 'active-popup-img';
|
||||
clone.src = imgSrc;
|
||||
if (proLink) {
|
||||
clone.style.cursor = 'pointer';
|
||||
clone.style.pointerEvents = 'auto';
|
||||
clone.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
mainArea.appendChild(clone);
|
||||
requestAnimationFrame(() => clone.classList.add('active'));
|
||||
}
|
||||
}, 1000); // 1000ms = 1초 대기
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -186,12 +402,58 @@
|
||||
}
|
||||
|
||||
// 마우스가 아이템을 벗어나면 즉시 청소 (타이머 취소 포함)
|
||||
item.addEventListener('mouseleave', clearAllPopups);
|
||||
item.addEventListener('mouseleave', function() {
|
||||
console.log('Mouse left item!');
|
||||
console.log('Setting hoverTimeout to hide popup in', HOVER_DELAY, 'ms');
|
||||
|
||||
// 팝업 숨김을 HOVER_DELAY만큼 지연시킵니다.
|
||||
hoverTimeout = setTimeout(() => {
|
||||
console.log('hoverTimeout triggered: Hiding popup.');
|
||||
clearAllPopupsContent(); // 내용만 초기화
|
||||
const popupBox = document.getElementById('popup-box');
|
||||
if (popupBox) {
|
||||
gsap.killTweensOf(popupBox); // 현재 진행 중인 애니메이션을 즉시 중단
|
||||
gsap.to(popupBox, {
|
||||
width: 0,
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
onComplete: () => {
|
||||
popupBox.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}, HOVER_DELAY); // HOVER_DELAY만큼 지연
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
// 섹션 전체를 벗어나도 청소 (안전장치)
|
||||
// 섹션 전체를 벗어나면 팝업 박스 숨기기
|
||||
if (section) {
|
||||
section.addEventListener('mouseleave', clearAllPopups);
|
||||
section.addEventListener('mouseleave', function() {
|
||||
console.log('Mouse left product-section!');
|
||||
console.log('Setting hoverTimeout to hide popup in', HOVER_DELAY, 'ms');
|
||||
|
||||
// 팝업 숨김을 HOVER_DELAY만큼 지연시킵니다.
|
||||
hoverTimeout = setTimeout(() => {
|
||||
console.log('hoverTimeout triggered: Hiding popup.');
|
||||
clearAllPopupsContent(); // 내용만 초기화
|
||||
const popupBox = document.getElementById('popup-box');
|
||||
if (popupBox) {
|
||||
gsap.killTweensOf(popupBox); // 현재 진행 중인 애니메이션을 즉시 중단
|
||||
gsap.to(popupBox, {
|
||||
width: 0,
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
onComplete: () => {
|
||||
popupBox.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}, HOVER_DELAY); // HOVER_DELAY만큼 지연
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,4 +463,5 @@
|
||||
} else {
|
||||
initProductEvents();
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
function initProductEvents() {
|
||||
// 💡 설정: 1(메인중앙), 2(점선), 3(제자리확대)
|
||||
const POPUP_MODE = 1;
|
||||
|
||||
const productItems = document.querySelectorAll('.product-section .product-item');
|
||||
const mainArea = document.querySelector('.product-main');
|
||||
|
||||
if (!productItems.length) return;
|
||||
|
||||
productItems.forEach(item => {
|
||||
// 💡 [수정] 데이터 속성에서 이미지 경로 직접 읽기 (없으면 내부 img 사용)
|
||||
const imgSrc = item.dataset.img || item.querySelector('.product-img img').src;
|
||||
const imgElement = item.querySelector('.product-img img');
|
||||
|
||||
item.addEventListener('mouseenter', function() {
|
||||
clearAllPopups();
|
||||
const x1 = this.dataset.x1;
|
||||
const y1 = this.dataset.y1;
|
||||
|
||||
if (x1 && y1) {
|
||||
ProductModule.setHotspot(x1, y1);
|
||||
|
||||
if (POPUP_MODE === 1 && mainArea) {
|
||||
// [모드 1] 메인 중앙 복제 팝업
|
||||
const clone = document.createElement('img');
|
||||
clone.src = imgSrc; // 💡 전달받은 경로로 로드
|
||||
clone.id = 'active-popup-img';
|
||||
mainArea.appendChild(clone);
|
||||
requestAnimationFrame(() => clone.classList.add('active'));
|
||||
|
||||
} else if (POPUP_MODE === 2) {
|
||||
// [모드 2] 점선 연결 (기존 로직 동일)
|
||||
const rect = this.getBoundingClientRect();
|
||||
const mainRect = mainArea.getBoundingClientRect();
|
||||
const targetX = mainRect.left + (mainRect.width * (parseFloat(x1) / 100));
|
||||
const targetY = mainRect.top + (mainRect.height * (parseFloat(y1) / 100));
|
||||
const isLeft = this.closest('.product-side').classList.contains('left');
|
||||
const startX = isLeft ? rect.right : rect.left;
|
||||
const dx = targetX - startX;
|
||||
const dy = targetY - (rect.top + rect.height/2);
|
||||
let angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
||||
if (!isLeft) angle += 180;
|
||||
|
||||
this.style.setProperty('--line-width', Math.sqrt(dx*dx + dy*dy) + 'px');
|
||||
this.style.setProperty('--line-angle', angle + 'deg');
|
||||
this.classList.add('show-connector');
|
||||
requestAnimationFrame(() => this.classList.add('active'));
|
||||
|
||||
} else if (POPUP_MODE === 3) {
|
||||
// 💡 [모드 3] 제자리 400x400 확대
|
||||
if (imgElement) {
|
||||
imgElement.src = imgSrc; // 💡 이미지 경로 갱신
|
||||
imgElement.classList.add('mode-self-zoom');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
item.addEventListener('mouseleave', function() {
|
||||
clearAllPopups();
|
||||
if (imgElement) imgElement.classList.remove('mode-self-zoom');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
function initProducts(productsData) {
|
||||
const map = document.getElementById('main-map');
|
||||
const hotspotLayer = document.getElementById('hotspot-layer');
|
||||
const preview = document.getElementById('hover-preview');
|
||||
const previewGrid = document.getElementById('preview-grid');
|
||||
|
||||
let isFixed = false;
|
||||
const noImg = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMjIyIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiM0NDQiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIiBmb250LWZhbWl5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjI0Ij5ObyBJbWFnZTwvdGV4dD48L3N2Zz4=";
|
||||
|
||||
// 1. 그룹화 로직
|
||||
const groups = {};
|
||||
productsData.forEach(p => {
|
||||
const key = `${parseFloat(p.xPct).toFixed(1)}_${parseFloat(p.yPct).toFixed(1)}`;
|
||||
if (!groups[key]) groups[key] = [];
|
||||
groups[key].push(p);
|
||||
});
|
||||
|
||||
// 2. 핫스팟 생성 및 이벤트
|
||||
Object.keys(groups).forEach(key => {
|
||||
const group = groups[key];
|
||||
const hs = document.createElement('div');
|
||||
hs.className = 'hotspot';
|
||||
hs.style.left = group[0].xPct + '%';
|
||||
hs.style.top = group[0].yPct + '%';
|
||||
hs.style.transform = 'translate(-50%, -50%)';
|
||||
|
||||
const updateContent = (showFullContent) => {
|
||||
const hasContent = group.some(p => p.content && p.content.trim() !== "");
|
||||
const isSingle = group.length === 1;
|
||||
|
||||
if (showFullContent && hasContent) {
|
||||
// 클릭 시: 상세 HTML 모드
|
||||
previewGrid.className = `products-grid full-html-mode ${isSingle ? 'single-item' : 'multi-items'}`;
|
||||
previewGrid.innerHTML = group.map(p => {
|
||||
if (p.content && p.content.trim() !== "") {
|
||||
return `<div class="editor-content-view">${p.content}</div>`;
|
||||
} else {
|
||||
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
|
||||
return `<div class="product-item" ${linkAttr}>
|
||||
<div class="product-img-box"><img src="${p.img_url}" onerror="this.onerror=null; this.src='${noImg}';"></div>
|
||||
<div class="info-content">
|
||||
<div class="text-[12px] text-blue-400 font-bold mb-2">${p.page}</div>
|
||||
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
|
||||
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
|
||||
${p.link ? '<div class="text-[11px] text-blue-500 mt-2">자세히 보기 ></div>' : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}).join('<hr class="content-divider">');
|
||||
} else {
|
||||
// 호버 시: 요약 모드
|
||||
const gridClass = isSingle ? 'single-item' : 'multi-items';
|
||||
previewGrid.className = `products-grid ${gridClass}`;
|
||||
previewGrid.innerHTML = group.map(p => {
|
||||
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
|
||||
return `<div class="product-item" ${linkAttr}>
|
||||
<div class="product-img-box"><img src="${p.img_url}" onerror="this.onerror=null; this.src='${noImg}';"></div>
|
||||
<div class="info-content">
|
||||
<div class="text-[12px] text-blue-400 font-bold mb-2">${p.page}</div>
|
||||
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
|
||||
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
};
|
||||
|
||||
hs.onmouseenter = () => {
|
||||
if (isFixed) return;
|
||||
updateContent(false);
|
||||
preview.style.display = 'block';
|
||||
preview.classList.remove('fixed-mode');
|
||||
};
|
||||
|
||||
hs.onmouseleave = () => {
|
||||
if (isFixed) return;
|
||||
preview.style.display = 'none';
|
||||
};
|
||||
|
||||
hs.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
isFixed = true;
|
||||
updateContent(true);
|
||||
preview.classList.add('fixed-mode');
|
||||
preview.style.display = 'flex';
|
||||
};
|
||||
|
||||
hotspotLayer.appendChild(hs);
|
||||
});
|
||||
|
||||
// 외부 클릭 시 닫기
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!preview.contains(e.target)) {
|
||||
isFixed = false;
|
||||
preview.style.display = 'none';
|
||||
preview.classList.remove('fixed-mode');
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user