/** * @module imagePreviewHandler * @description Универсальный обработчик превью изображений для input[type="file"]. * Показывает выбранное изображение до загрузки, автоматически управляет памятью. * * Пример HTML: * * * * Использование: * import { initImagePreview } from './imagePreviewHandler.js'; * initImagePreview({ * inputId: 'imageInput', * previewId: 'imagePreview' * }); */ /** * Инициализирует превью изображения для input[type="file"]. * @param {Object} options - Опции инициализации * @param {string} options.inputId - ID элемента input[type="file"] * @param {string} options.previewId - ID элемента для превью * @param {string} [options.currentPreviewId] - ID текущего превью (для режима редактирования) * @param {string} [options.hiddenClass='d-none'] - Класс для скрытия элементов * @returns {Object} API с методами destroy, show, hide, clear */ export function initImagePreview(options) { const { inputId, previewId, currentPreviewId, hiddenClass = 'd-none' } = options; const input = document.getElementById(inputId); const preview = document.getElementById(previewId); const currentPreview = currentPreviewId ? document.getElementById(currentPreviewId) : null; if (!input || !preview) { console.warn('initImagePreview: обязательные элементы не найдены'); return { destroy: () => {}, show: () => {}, hide: () => {}, clear: () => {} }; } let objectURL = null; /** * Показывает новое превью, скрывает текущее. * @param {string} url - URL изображения (blob URL) */ function showNewPreview(url) { // Скрываем текущее превью (если есть) if (currentPreview) { currentPreview.classList.add(hiddenClass); } // Показываем новое превью preview.src = url; preview.classList.remove(hiddenClass); } /** * Скрывает новое превью, показывает текущее (если есть). */ function hideNewPreview() { preview.src = ''; preview.classList.add(hiddenClass); // Восстанавливаем текущее превью (если было) if (currentPreview) { currentPreview.classList.remove(hiddenClass); } } /** * Безопасно освобождает object URL. */ function revokeObjectURLSafe() { try { if (objectURL) { URL.revokeObjectURL(objectURL); } } catch (e) { console.warn('Не удалось освободить objectURL:', e); } objectURL = null; } /** * Очищает выбор файла и превью. */ function clear() { revokeObjectURLSafe(); input.value = ''; hideNewPreview(); } /** * Обработчик изменения файла. */ function handleChange() { revokeObjectURLSafe(); const file = input.files && input.files[0]; if (file) { objectURL = URL.createObjectURL(file); showNewPreview(objectURL); } else { hideNewPreview(); } } /** * Обработчик ухода со страницы. */ function handleBeforeUnload() { revokeObjectURLSafe(); } // Устанавливаем обработчики input.addEventListener('change', handleChange); window.addEventListener('beforeunload', handleBeforeUnload); /** * Функция для удаления обработчиков и освобождения памяти. */ function destroy() { revokeObjectURLSafe(); input.removeEventListener('change', handleChange); window.removeEventListener('beforeunload', handleBeforeUnload); } return { destroy, show: showNewPreview, hide: hideNewPreview, clear }; } // Экспорт для совместимости с неModular кодом if (typeof window !== 'undefined') { window.initImagePreview = initImagePreview; }