[20250721] domUtils add

This commit is contained in:
djaco
2025-07-21 16:30:32 +09:00
parent e8f94c680f
commit d7d37c7aaa

View File

@@ -0,0 +1,158 @@
// src/utils/domUtils.js
// DOM 요소 관련 유틸리티 함수들 (크로미움68 호환)
/**
* 특정 spotlight-id를 가진 요소를 재시도 메커니즘과 함께 찾습니다.
* @param {string} spotlightId - 찾을 spotlight id
* @param {number} maxRetries - 최대 재시도 횟수
* @param {number} interval - 재시도 간격 (ms)
* @returns {Promise<Element|null>} 찾은 DOM 요소 또는 null
*/
export const findElementWithRetry = (spotlightId, maxRetries = 10, interval = 100) => {
return new Promise((resolve) => {
let attempts = 0;
const checkElement = () => {
const selector = `[data-spotlight-id="${spotlightId}"]`;
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
attempts++;
if (attempts < maxRetries) {
setTimeout(checkElement, interval);
} else {
console.warn(`[domUtils] Element not found after ${maxRetries} attempts: ${selector}`);
resolve(null);
}
};
checkElement();
});
};
/**
* DOM 요소의 위치와 크기 정보를 안전하게 가져옵니다.
* @param {Element} element - 위치를 계산할 DOM 요소
* @returns {Object|null} 위치 정보 객체 또는 null
*/
export const getElementPosition = (element) => {
if (!element) {
return null;
}
try {
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
};
} catch (error) {
console.error('[domUtils] Error getting element position:', error);
return null;
}
};
/**
* spotlight-id로 요소를 찾고 위치 정보를 반환합니다.
* @param {string} spotlightId - 찾을 spotlight id
* @param {number} maxRetries - 최대 재시도 횟수
* @param {number} interval - 재시도 간격 (ms)
* @returns {Promise<Object|null>} 위치 정보 객체 또는 null
*/
export const getElementPositionById = async (spotlightId, maxRetries = 10, interval = 100) => {
const element = await findElementWithRetry(spotlightId, maxRetries, interval);
return getElementPosition(element);
};
/**
* 모달 스타일 객체를 생성합니다.
* @param {Object} position - 위치 정보 객체
* @param {number} scale - 스케일 값 (선택적)
* @returns {Object} 모달 스타일 객체
*/
export const createModalStyle = (position, scale = 1) => {
if (!position) {
return {};
}
return {
width: position.width + "px",
height: position.height + "px",
top: position.top + "px",
left: position.left + "px",
position: "fixed",
overflow: "visible",
transform: scale !== 1 ? `scale(${scale})` : undefined,
};
};
/**
* 배너별 기본 위치 정보를 반환합니다. (fallback용)
* @param {string} bannerId - 배너 ID (banner0, banner1, etc.)
* @returns {Object} 기본 위치 정보
*/
export const getDefaultBannerPosition = (bannerId) => {
const defaultPositions = {
banner0: { width: 640, height: 360, top: 100, left: 100 },
banner1: { width: 640, height: 360, top: 100, left: 760 },
banner2: { width: 640, height: 360, top: 500, left: 100 },
banner3: { width: 640, height: 360, top: 500, left: 760 },
};
return defaultPositions[bannerId] || defaultPositions.banner0;
};
/**
* 모든 배너의 위치 정보를 수집합니다.
* @param {Array<string>} bannerIds - 수집할 배너 ID 배열
* @returns {Promise<Object>} 배너별 위치 정보 맵
*/
export const collectBannerPositions = async (bannerIds = ['banner0', 'banner1', 'banner2', 'banner3']) => {
const positions = {};
for (const bannerId of bannerIds) {
const position = await getElementPositionById(bannerId, 5, 50); // 빠른 수집
if (position) {
positions[bannerId] = position;
} else {
// fallback 위치 사용
positions[bannerId] = getDefaultBannerPosition(bannerId);
console.warn(`[domUtils] Using fallback position for ${bannerId}`);
}
}
return positions;
};
/**
* 크로미움68 호환 - Optional Chaining 대신 안전한 객체 접근
* @param {Object} obj - 접근할 객체
* @param {string} path - 접근 경로 (예: 'a.b.c')
* @param {*} defaultValue - 기본값
* @returns {*} 값 또는 기본값
*/
export const safeGet = (obj, path, defaultValue = null) => {
try {
const keys = path.split('.');
let result = obj;
for (const key of keys) {
if (result === null || result === undefined) {
return defaultValue;
}
result = result[key];
}
return result !== undefined ? result : defaultValue;
} catch (error) {
return defaultValue;
}
};