마우스 오버 후 1초가 지나면 이미지 미리보기 팝업창 실행

This commit is contained in:
2026-05-14 11:43:54 +09:00
parent 8c90aa5be8
commit c09227facc
3 changed files with 103 additions and 4 deletions
+73
View File
@@ -0,0 +1,73 @@
const DELAY = 1000;
let _el = null;
let _imgEl = null;
let _timer = null;
let _visible = false;
export function init() {
_el = document.createElement('div');
_el.className = 'hover-preview';
_imgEl = document.createElement('img');
_el.appendChild(_imgEl);
document.body.appendChild(_el);
}
export function setupCard(card) {
card.addEventListener('mouseenter', _onEnter);
card.addEventListener('mouseleave', _onLeave);
}
function _onEnter() {
const card = this;
clearTimeout(_timer);
_timer = setTimeout(() => {
const thumb = card.querySelector('img.card-thumb');
if (!thumb) return;
_imgEl.src = thumb.src;
_visible = true;
_show(card);
}, DELAY);
}
function _onLeave() {
clearTimeout(_timer);
_timer = null;
_visible = false;
_el.style.display = 'none';
_el.style.visibility = 'hidden';
}
function _show(card) {
// 위치 계산 전 화면 밖에 렌더링해서 실제 크기를 측정
_el.style.display = 'block';
_el.style.visibility = 'hidden';
_el.style.left = '0px';
_el.style.top = '0px';
requestAnimationFrame(() => {
if (!_visible) return;
const popup = _el.getBoundingClientRect();
const cardRect = card.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
const gap = 16;
// 가로: 카드 오른쪽 우선, 공간 부족 시 왼쪽
let left = cardRect.right + gap;
if (left + popup.width > vw - 8) {
left = cardRect.left - popup.width - gap;
}
left = Math.max(8, Math.min(left, vw - popup.width - 8));
// 세로: 카드와 중앙 정렬, 화면 경계 클램프
let top = cardRect.top + (cardRect.height - popup.height) / 2;
top = Math.max(8, Math.min(top, vh - popup.height - 8));
_el.style.left = left + 'px';
_el.style.top = top + 'px';
_el.style.visibility = 'visible';
});
}
+7 -4
View File
@@ -1,7 +1,8 @@
import { state } from './state.js';
import * as pinchZoom from './handlers/pinch-zoom.js';
import * as selection from './handlers/selection.js';
import * as dragDrop from './handlers/drag-drop.js';
import { state } from './state.js';
import * as pinchZoom from './handlers/pinch-zoom.js';
import * as selection from './handlers/selection.js';
import * as dragDrop from './handlers/drag-drop.js';
import * as hoverPreview from './handlers/hover-preview.js';
// ── DOM ──────────────────────────────────────────────
const grid = document.getElementById('grid');
@@ -38,6 +39,7 @@ const callbacks = {
pinchZoom.init({ gridContainer, thumbSizeHint });
selection.init({ gridContainer, rubberBand, grid, state, callbacks });
dragDrop.init({ grid, gridContainer, dragGhostEl, state, callbacks });
hoverPreview.init();
// ── 버튼 이벤트 ──────────────────────────────────
inputPrefix.addEventListener('input', updatePreview);
@@ -146,6 +148,7 @@ function createCard(file, idx, dataUrl) {
card.appendChild(body);
dragDrop.setupCard(card);
hoverPreview.setupCard(card);
card.addEventListener('click', selection.onCardClick);
return card;
+23
View File
@@ -269,6 +269,29 @@ body {
@keyframes spin { to { transform: rotate(360deg); } }
#loading p { color: var(--text); font-size: 14px; }
/* ── 호버 이미지 팝업 ── */
.hover-preview {
position: fixed;
display: none;
visibility: hidden;
z-index: 8000;
pointer-events: none;
background: #0d0d1a;
border: 1px solid var(--border);
border-radius: 12px;
padding: 8px;
box-shadow: 0 20px 60px rgba(0,0,0,0.85), 0 0 0 1px rgba(124,106,247,0.15);
}
.hover-preview img {
display: block;
max-width: 840px;
max-height: 840px;
width: auto;
height: auto;
border-radius: 6px;
object-fit: contain;
}
/* ── 토스트 ── */
#toast {
position: fixed; bottom: 40px; left: 50%;