first commit 2
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
function initProducts(productsData) {
|
||||
const preview = document.getElementById('hover-preview');
|
||||
const previewGrid = document.getElementById('preview-grid');
|
||||
const hotspotLayer = document.getElementById('hotspot-layer');
|
||||
const coordDisplay = document.getElementById('coord-display');
|
||||
const map = document.getElementById('main-map');
|
||||
let isFixed = false;
|
||||
|
||||
const noImg = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMjIyIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiM0NDQiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIiBmb250LWZhbWl5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjI0Ij5ObyBJbWFnZTwvdGV4dD48L3N2Zz4=";
|
||||
|
||||
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 + '%';
|
||||
|
||||
const updateContent = (showFullContent) => {
|
||||
const hasContent = group.some(p => p.content && p.content.trim() !== "");
|
||||
const isSingle = group.length === 1;
|
||||
|
||||
if (showFullContent && hasContent) {
|
||||
previewGrid.className = `products-grid full-html-mode ${isSingle ? 'single-item' : 'multi-items'}`;
|
||||
previewGrid.innerHTML = group.map(p => {
|
||||
console.log( p.content)
|
||||
|
||||
if (p.content && p.content.trim() !== "") {
|
||||
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
|
||||
return `<div class="product-item item-vertical" ${linkAttr}>
|
||||
<div class="editor-content-view">${p.content}</div>
|
||||
<div class="info-content">
|
||||
<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" onclick="location.href='${p.link}'" style="cursor:pointer;">자세히 보기 ></div>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
|
||||
return `<div class="product-item item-vertical" ${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('<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 item-vertical" ${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) { updateContent(false); preview.style.display = 'block'; preview.classList.remove('fixed-mode'); } };
|
||||
hs.onmouseleave = () => { if (!isFixed) 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');
|
||||
}
|
||||
});
|
||||
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)}`);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
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)}`);
|
||||
// };
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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)}`);
|
||||
// };
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* [최종 통합본] 제품 사이드 메뉴 및 메인 맵 연동 모듈 (products2.js)
|
||||
* 1: 중앙팝업, 2: 점선연결, 3: 제자리확대, 4: 1초 지연 후 중앙팝업
|
||||
*/
|
||||
(function() {
|
||||
// 💡 설정: 원하는 모드 번호를 입력하세요.
|
||||
const POPUP_MODE = 4;
|
||||
|
||||
let popupTimer = null; // 4번 모드용 타이머
|
||||
|
||||
// 모든 효과와 타이머를 제거하는 청소기 함수
|
||||
function clearAllPopups() {
|
||||
// 실행 중인 타이머가 있으면 취소
|
||||
if (popupTimer) {
|
||||
clearTimeout(popupTimer);
|
||||
popupTimer = null;
|
||||
}
|
||||
|
||||
// 핫스팟 숨김
|
||||
const hotspot = document.querySelector('.product-hotspot');
|
||||
if (hotspot) hotspot.style.display = 'none';
|
||||
|
||||
// 생성된 클론 이미지(모드 1, 4) 삭제
|
||||
const clones = document.querySelectorAll('#active-popup-img');
|
||||
clones.forEach(c => c.remove());
|
||||
|
||||
// 모든 아이템의 클래스 및 스타일 초기화
|
||||
const items = document.querySelectorAll('.product-item');
|
||||
items.forEach(item => {
|
||||
item.classList.remove('show-connector', 'active');
|
||||
const img = item.querySelector('.product-img img');
|
||||
if (img) {
|
||||
img.classList.remove('mode-self-zoom');
|
||||
img.style.transform = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initProductEvents() {
|
||||
const productItems = document.querySelectorAll('.product-section .product-item');
|
||||
const mainArea = document.querySelector('.product-main');
|
||||
const section = document.querySelector('.product-section');
|
||||
|
||||
if (!productItems.length || !mainArea) return;
|
||||
|
||||
productItems.forEach(item => {
|
||||
const imgElement = item.querySelector('.product-img img');
|
||||
const imgSrc = item.dataset.img || (imgElement ? imgElement.src : '');
|
||||
const proLink = item.dataset.prolink;
|
||||
|
||||
item.addEventListener('mouseenter', function() {
|
||||
clearAllPopups(); // 새로 시작하기 전 초기화
|
||||
|
||||
const x1 = this.dataset.x1;
|
||||
const y1 = this.dataset.y1;
|
||||
|
||||
if (x1 && y1) {
|
||||
// 1. 핫스팟(블루 스폿)은 어떤 모드든 즉시 표시
|
||||
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) {
|
||||
// [모드 4] 1초 지연 후 중앙 팝업
|
||||
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초 대기
|
||||
|
||||
} else if (POPUP_MODE === 1 && imgElement) {
|
||||
// [모드 1] 즉시 중앙 팝업
|
||||
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'));
|
||||
|
||||
} 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 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; };
|
||||
}
|
||||
|
||||
} else if (POPUP_MODE === 3 && imgElement) {
|
||||
// [모드 3] 제자리 600 확대
|
||||
imgElement.src = imgSrc;
|
||||
imgElement.classList.add('mode-self-zoom');
|
||||
if (proLink) {
|
||||
imgElement.style.cursor = 'pointer';
|
||||
imgElement.style.pointerEvents = 'auto';
|
||||
imgElement.onclick = () => { location.href = proLink; };
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 아이템 자체 클릭 시에도 이동 (이미지 외 영역 클릭 대비)
|
||||
if (proLink) {
|
||||
item.style.cursor = 'pointer';
|
||||
item.addEventListener('click', function(e) {
|
||||
if (e.target.tagName !== 'IMG') {
|
||||
location.href = proLink;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 마우스가 아이템을 벗어나면 즉시 청소 (타이머 취소 포함)
|
||||
item.addEventListener('mouseleave', clearAllPopups);
|
||||
});
|
||||
|
||||
// 섹션 전체를 벗어나도 청소 (안전장치)
|
||||
if (section) {
|
||||
section.addEventListener('mouseleave', clearAllPopups);
|
||||
}
|
||||
}
|
||||
|
||||
// 문서 로드 완료 시 실행
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initProductEvents);
|
||||
} else {
|
||||
initProductEvents();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,64 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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