/** * @module avatarPreviewHandler * @description Обработчик превью изображения (аватара) с управлением object URL. * Показывает выбранное изображение до загрузки на сервер, автоматически освобождает память. * * Пример HTML: *
* * *
* * * Использование: * import { initAvatarPreview } from './avatarPreviewHandler.js'; * initAvatarPreview({ * fileInputSelector: 'input[type="file"][name="avatar"]', * previewImageId: 'avatarPreview', * placeholderId: 'avatarPlaceholder' * }); */ /** * Инициализирует обработчик превью аватара с автоматическим управлением памятью. * @param {Object} options - Опции инициализации * @param {string} options.fileInputSelector - CSS-селектор для input[type="file"] * @param {string} options.previewImageId - ID элемента для превью * @param {string} options.placeholderId - ID элемента-плейсхолдера (иконка) * @returns {Object} API с методами destroy, showPreview, hidePreview */ export function initAvatarPreview(options) { const { fileInputSelector, previewImageId, placeholderId } = options; const fileInput = document.querySelector(fileInputSelector); const preview = document.getElementById(previewImageId); const placeholder = document.getElementById(placeholderId); const formEl = fileInput ? fileInput.closest('form') : null; if (!fileInput || !preview) { console.warn('initAvatarPreview: обязательные элементы не найдены'); return { destroy: () => {}, showPreview: () => {}, hidePreview: () => {} }; } let objectURL = null; /** * Показывает превью изображения, скрывает плейсхолдер. * @param {string} url - URL изображения (blob URL или обычный URL) */ function showPreview(url) { if (placeholder) { placeholder.classList.add('d-none'); } preview.src = url; preview.classList.remove('d-none'); } /** * Скрывает превью изображения, показывает плейсхолдер. */ function hidePreview() { preview.removeAttribute('src'); preview.classList.add('d-none'); if (placeholder) { placeholder.classList.remove('d-none'); } } /** * Безопасно освобождает object URL. */ function revokeObjectURLSafe() { try { if (objectURL) { URL.revokeObjectURL(objectURL); } } catch (e) { console.warn('Не удалось освободить objectURL:', e); } objectURL = null; } /** * Обработчик изменения файла в input. */ function handleFileChange() { revokeObjectURLSafe(); const file = fileInput.files && fileInput.files[0]; if (file) { objectURL = URL.createObjectURL(file); showPreview(objectURL); } else { hidePreview(); } } /** * Обработчик сброса формы. */ function handleFormReset() { revokeObjectURLSafe(); // Небольшая задержка, чтобы браузер успел очистить value у input[type=file] setTimeout(hidePreview, 0); } /** * Обработчик ухода со страницы (освобождение памяти). */ function handleBeforeUnload() { revokeObjectURLSafe(); } // Установка обработчиков fileInput.addEventListener('change', handleFileChange); if (formEl) { formEl.addEventListener('reset', handleFormReset); } window.addEventListener('beforeunload', handleBeforeUnload); /** * Функция для удаления всех обработчиков и освобождения памяти. */ function destroy() { revokeObjectURLSafe(); fileInput.removeEventListener('change', handleFileChange); if (formEl) { formEl.removeEventListener('reset', handleFormReset); } window.removeEventListener('beforeunload', handleBeforeUnload); } return { destroy, showPreview, hidePreview }; } // Экспорт для совместимости с неModular кодом if (typeof window !== 'undefined') { window.initAvatarPreview = initAvatarPreview; }