// Global variables let canvas = null; let ctx = null; let currentImage = null; let selectedTemplate = null; // Text Position Management let textPositions = { top: { x: 'center', // left, center, right y: 15 // percentage from top }, bottom: { x: 'center', y: 85 // percentage from top } }; // ============================================== // Advanced Draggable Text System // ============================================== // Text positions and settings let textElements = { top: { x: 0.5, // Relative position (0-1) y: 0.15, text: '', isDragging: false, fontSize: 40, fontFamily: 'Arial Black', color: 'white', strokeColor: 'black', strokeWidth: 3, align: 'center' }, bottom: { x: 0.5, y: 0.85, text: '', isDragging: false, fontSize: 40, fontFamily: 'Arial Black', color: 'white', strokeColor: 'black', strokeWidth: 3, align: 'center' } }; let dragState = { isDragging: false, currentElement: null, startX: 0, startY: 0, offsetX: 0, offsetY: 0 }; // Initialize the application document.addEventListener('DOMContentLoaded', function() { initializeCanvas(); loadTemplates(); setupEventListeners(); setDefaultMeme(); // Initialize text position controls initializeTextPositionControls(); initializeDraggableText(); // Initialize modal system initializeModal(); }); // Initialize canvas function initializeCanvas() { canvas = document.getElementById('meme-canvas'); ctx = canvas.getContext('2d'); canvas.width = 600; canvas.height = 400; // Set default canvas background ctx.fillStyle = '#f0f0f0'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw placeholder text with better font ctx.fillStyle = '#666'; ctx.font = '20px "Inter", Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('เลือกรูปภาพหรือเทมเพลตเพื่อเริ่มสร้างมีม', canvas.width / 2, canvas.height / 2); } // Setup event listeners function setupEventListeners() { // Theme switcher document.getElementById('theme-switcher').addEventListener('click', toggleTheme); // Text inputs document.getElementById('top-text').addEventListener('input', updateTextElements); document.getElementById('bottom-text').addEventListener('input', updateTextElements); // File upload document.getElementById('image-upload').addEventListener('change', handleImageUpload); // Clear button document.getElementById('clear-btn').addEventListener('click', clearMeme); // Download button document.getElementById('download-btn').addEventListener('click', downloadMeme); // Update canvas size display updateCanvasSize(); } // Select template function selectTemplate(template, element) { // Remove previous selection document.querySelectorAll('.template-item').forEach(item => { item.classList.remove('selected'); }); // Add selection to current template element.classList.add('selected'); // Haptic feedback for mobile if (navigator.vibrate) { navigator.vibrate(50); } selectedTemplate = template; // Load template image const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function() { currentImage = img; canvas.width = template.width; canvas.height = template.height; updateMeme(); updateCanvasSize(); // Show toast if available if (typeof showToast === 'function') { showToast(`เลือกเทมเพลต: ${template.name}`); } }; img.onerror = function() { console.error('Failed to load template:', template.src); if (typeof showToast === 'function') { showToast('❌ ไม่สามารถโหลดเทมเพลตได้', 'error'); } }; img.src = template.src; } // Toggle theme function toggleTheme() { const body = document.body; const currentTheme = body.getAttribute('data-theme'); const newTheme = currentTheme === 'light' ? 'dark' : 'light'; body.setAttribute('data-theme', newTheme); // Update theme icon const themeIcon = document.querySelector('.theme-icon'); themeIcon.textContent = newTheme === 'light' ? '🌙' : '☀️'; // Save theme preference localStorage.setItem('theme', newTheme); } // Handle image upload function handleImageUpload(event) { const file = event.target.files[0]; if (!file) return; if (!validateFile(file)) { event.target.value = ''; // Clear invalid file return; } // Clear template selection document.querySelectorAll('.template-item').forEach(item => { item.classList.remove('selected'); }); selectedTemplate = null; const reader = new FileReader(); reader.onload = function(e) { const img = new Image(); img.onload = function() { currentImage = img; // Resize canvas to fit image while maintaining aspect ratio const maxWidth = 600; const maxHeight = 600; let {width, height} = img; if (width > maxWidth || height > maxHeight) { const ratio = Math.min(maxWidth / width, maxHeight / height); width *= ratio; height *= ratio; } canvas.width = width; canvas.height = height; updateMeme(); }; img.onerror = () => handleImageError(img); img.src = e.target.result; }; reader.readAsDataURL(file); } // Initialize text position controls function initializeTextPositionControls() { // Position buttons document.querySelectorAll('.position-btn').forEach(btn => { btn.addEventListener('click', function() { const textType = this.dataset.text; // 'top' or 'bottom' const position = this.dataset.pos; // 'left', 'center', 'right' // Update active state document.querySelectorAll(`[data-text="${textType}"]`).forEach(b => b.classList.remove('active')); this.classList.add('active'); // Update position textPositions[textType].x = position; updateMemeWithPositions(); // Haptic feedback if (navigator.vibrate) { navigator.vibrate(30); } // Show toast const positionNames = {left: 'ซ้าย', center: 'กลาง', right: 'ขวา'}; if (typeof showToast === 'function') { showToast(`📍 ย้ายข้อความ${textType === 'top' ? 'บน' : 'ล่าง'}ไป${positionNames[position]}`, 'success'); } }); }); // Set initial active states document.querySelector('[data-text="top"][data-pos="center"]')?.classList.add('active'); document.querySelector('[data-text="bottom"][data-pos="center"]')?.classList.add('active'); } // Enhanced text drawing with position support function drawTextWithPosition(text, textType) { if (!text || !currentImage) return; const position = textPositions[textType]; const fontSize = Math.max(canvas.width / 15, 24); const strokeWidth = fontSize / 12; ctx.font = `bold ${fontSize}px "Impact", "Arial Black", sans-serif`; ctx.textAlign = position.x === 'left' ? 'left' : position.x === 'right' ? 'right' : 'center'; ctx.fillStyle = 'white'; ctx.strokeStyle = 'black'; ctx.lineWidth = strokeWidth; ctx.lineJoin = 'round'; ctx.miterLimit = 2; // Calculate X position let x; const margin = canvas.width * 0.05; // 5% margin switch (position.x) { case 'left': x = margin; break; case 'right': x = canvas.width - margin; break; default: // center x = canvas.width / 2; break; } // Calculate Y position const y = (canvas.height * position.y) / 100; // Word wrapping const words = text.split(' '); const lines = []; let currentLine = words[0] || ''; for (let i = 1; i < words.length; i++) { const testLine = currentLine + ' ' + words[i]; const metrics = ctx.measureText(testLine); const testWidth = metrics.width; if (testWidth > canvas.width - (margin * 2)) { lines.push(currentLine); currentLine = words[i]; } else { currentLine = testLine; } } lines.push(currentLine); // Draw text lines const lineHeight = fontSize * 1.2; const totalHeight = lines.length * lineHeight; let startY = y; // Adjust Y position for multiple lines if (textType === 'top') { startY = y + fontSize; } else { startY = y - totalHeight + fontSize; } lines.forEach((line, index) => { const lineY = startY + (index * lineHeight); // Draw text stroke (outline) ctx.strokeText(line, x, lineY); // Draw text fill ctx.fillText(line, x, lineY); }); } // Enhanced updateMeme function with position support function updateMemeWithPositions() { if (!currentImage) return; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw image ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height); // Draw texts with positions const topText = document.getElementById('top-text').value; const bottomText = document.getElementById('bottom-text').value; if (topText) { drawTextWithPosition(topText, 'top'); } if (bottomText) { drawTextWithPosition(bottomText, 'bottom'); } } // Compatibility functions function updateMeme() { drawMemeWithDraggableText(); } function updateMemeWithPositions() { drawMemeWithDraggableText(); } // Reset text positions function resetTextPositions() { if (!textElements) { console.error('textElements not defined'); return; } // Reset to default positions textElements.top.x = 0.5; textElements.top.y = 0.15; textElements.bottom.x = 0.5; textElements.bottom.y = 0.85; // Redraw drawMemeWithDraggableText(); // Show success message if (typeof showToast === 'function') { showToast('🔄 รีเซ็ตตำแหน่งแล้ว', 'success'); } } // Add keyboard shortcuts for position control function initializePositionKeyboardShortcuts() { document.addEventListener('keydown', function(e) { if (e.ctrlKey || e.metaKey) { switch (e.key) { case 'ArrowLeft': e.preventDefault(); document.querySelector('[data-text="top"][data-pos="left"]')?.click(); break; case 'ArrowRight': e.preventDefault(); document.querySelector('[data-text="top"][data-pos="right"]')?.click(); break; case 'ArrowUp': e.preventDefault(); document.querySelector('[data-text="top"][data-pos="center"]')?.click(); break; case 'ArrowDown': e.preventDefault(); document.querySelector('[data-text="bottom"][data-pos="center"]')?.click(); break; } } }); } // Select template function enhancedSelectTemplate(template, element) { // Remove previous selections document.querySelectorAll('.template-item').forEach(item => { item.classList.remove('selected'); }); // Add selection with animation element.classList.add('selected'); addSuccessAnimation(element); // Haptic feedback if (navigator.vibrate) { navigator.vibrate([50, 30, 50]); } selectedTemplate = template; loadTemplateImage(template); if (typeof showToast === 'function') { showToast(`✨ เลือก: ${template.name}`, 'success'); } } // Load template image function loadTemplateImage(template) { showLoadingOverlay('กำลังโหลดเทมเพลต...'); const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function() { currentImage = img; canvas.width = template.width; canvas.height = template.height; updateMeme(); updateCanvasSize(); hideLoadingOverlay(); // Success animation on canvas addSuccessAnimation(canvas); }; img.onerror = function() { hideLoadingOverlay(); console.error('Failed to load template:', template.src); if (typeof showToast === 'function') { showToast('❌ ไม่สามารถโหลดเทมเพลตได้', 'error'); } }; img.src = template.src; } // Enhanced Download Function async function enhancedDownloadMeme() { const downloadBtn = document.getElementById('download-btn'); const originalText = downloadBtn.innerHTML; try { // Show loading state downloadBtn.innerHTML = ' กำลังสร้าง...'; downloadBtn.disabled = true; showLoadingOverlay('กำลังสร้างมีมคุณภาพสูง...'); // Wait a bit for UI update await new Promise(resolve => setTimeout(resolve, 500)); // Create download link const link = document.createElement('a'); const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, ''); link.download = `meme-${timestamp}.png`; link.href = canvas.toDataURL('image/png', 1.0); // Trigger download document.body.appendChild(link); link.click(); document.body.removeChild(link); hideLoadingOverlay(); // Success feedback addSuccessAnimation(downloadBtn); showToast('🎉 ดาวน์โหลดสำเร็จ!', 'success'); } catch (error) { hideLoadingOverlay(); console.error('Download failed:', error); addErrorState(downloadBtn); showToast('❌ เกิดข้อผิดพลาดในการดาวน์โหลด', 'error'); } finally { // Restore button setTimeout(() => { downloadBtn.innerHTML = originalText; downloadBtn.disabled = false; }, 1000); } } // Enhanced Theme Switching function enhancedToggleTheme() { const body = document.body; const themeBtn = document.getElementById('theme-switcher'); const themeIcon = themeBtn.querySelector('.theme-icon'); const themeText = themeBtn.querySelector('.theme-text'); const currentTheme = body.getAttribute('data-theme'); const newTheme = currentTheme === 'light' ? 'dark' : 'light'; // Animate theme transition body.style.transition = 'all 0.3s ease'; body.setAttribute('data-theme', newTheme); // Update button content if (newTheme === 'light') { themeIcon.textContent = '☀️'; themeText.textContent = 'Light'; } else { themeIcon.textContent = '🌙'; themeText.textContent = 'Dark'; } // Success animation addSuccessAnimation(themeBtn); // Save preference localStorage.setItem('theme', newTheme); // Remove transition after animation setTimeout(() => { body.style.transition = ''; }, 300); } // ============================================ // Modal System Functions // ============================================ // Open modal with content function openModal(title, content) { const modal = document.getElementById('modal'); const modalTitle = document.getElementById('modal-title'); const modalBody = document.getElementById('modal-body'); if (!modal || !modalTitle || !modalBody) { console.error('Modal elements not found'); return; } modalTitle.textContent = title; modalBody.innerHTML = content; // Show modal with animation modal.classList.remove('hidden'); setTimeout(() => { modal.classList.add('show'); }, 10); // Add escape key listener document.addEventListener('keydown', handleModalEscape); // Prevent body scroll document.body.style.overflow = 'hidden'; // Focus management for accessibility const closeButton = modal.querySelector('.modal-close'); if (closeButton) { closeButton.focus(); } } // Close modal function closeModal() { const modal = document.getElementById('modal'); if (!modal) { console.error('Modal element not found'); return; } // Hide modal with animation modal.classList.remove('show'); setTimeout(() => { modal.classList.add('hidden'); // Clear content const modalTitle = document.getElementById('modal-title'); const modalBody = document.getElementById('modal-body'); if (modalTitle) modalTitle.textContent = ''; if (modalBody) modalBody.innerHTML = ''; }, 300); // Match CSS transition duration // Remove escape key listener document.removeEventListener('keydown', handleModalEscape); // Restore body scroll document.body.style.overflow = ''; // Return focus to trigger element (if available) if (window.lastFocusedElement) { window.lastFocusedElement.focus(); window.lastFocusedElement = null; } } // Handle escape key press function handleModalEscape(event) { if (event.key === 'Escape') { closeModal(); } } // Show About modal function showAbout() { // Store current focused element window.lastFocusedElement = document.activeElement; const aboutContent = `
🎨

Meme Forge Pro

"เปลี่ยนบั๊กเป็นเสียงฮา โค้ดปัญหาให้เป็นมีม!"

🚀 เกี่ยวกับโปรเจกต์

Meme Forge Pro เป็นเว็บแอปพลิเคชันสำหรับสร้างมีมที่เน้นไปที่กลุ่มนักพัฒนาซอฟต์แวร์ นักวิทยาศาสตร์ข้อมูล และผู้ที่สนใจเทคโนโลยี AI

✨ ฟีเจอร์หลัก

🛠️ เทคโนโลยีที่ใช้

HTML5 CSS3 JavaScript Canvas API PWA

👨‍💻 ผู้พัฒนา

`; openModal('ℹ️ เกี่ยวกับ Meme Forge Pro', aboutContent); } // Initialize modal system function initializeModal() { const modal = document.getElementById('modal'); if (!modal) { console.error('Modal element not found'); return; } // Close modal when clicking overlay modal.addEventListener('click', (event) => { if (event.target === modal) { closeModal(); } }); // Prevent modal content clicks from closing modal const modalContent = modal.querySelector('.modal-content'); if (modalContent) { modalContent.addEventListener('click', (event) => { event.stopPropagation(); }); } } // Clear meme function function clearMeme() { if (!canvas || !ctx) return; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Reset current image currentImage = null; // Clear text inputs const topTextInput = document.getElementById('top-text'); const bottomTextInput = document.getElementById('bottom-text'); if (topTextInput) topTextInput.value = ''; if (bottomTextInput) bottomTextInput.value = ''; // Reset text elements textElements.top.text = ''; textElements.bottom.text = ''; // Reset positions to default textElements.top.x = 0.5; textElements.top.y = 0.15; textElements.bottom.x = 0.5; textElements.bottom.y = 0.85; // Show success message if (typeof showToast === 'function') { showToast('🧹 ล้างข้อมูลเรียบร้อยแล้ว', 'success'); } // Add success animation if (typeof addSuccessAnimation === 'function') { addSuccessAnimation(canvas); } } // Download meme function function downloadMeme() { if (!canvas || !currentImage) { if (typeof showToast === 'function') { showToast('❌ กรุณาเลือกรูปภาพก่อนดาวน์โหลด', 'error'); } return; } try { // Show loading if (typeof showLoadingOverlay === 'function') { showLoadingOverlay('กำลังสร้างมีม...', 'pulse'); } // Create download link const link = document.createElement('a'); const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, ''); link.download = `meme-${timestamp}.png`; link.href = canvas.toDataURL('image/png', 1.0); // Trigger download document.body.appendChild(link); link.click(); document.body.removeChild(link); // Hide loading and show success setTimeout(() => { if (typeof hideLoadingOverlay === 'function') { hideLoadingOverlay(); } if (typeof showToast === 'function') { showToast('🎉 ดาวน์โหลดสำเร็จ!', 'success'); } }, 500); } catch (error) { console.error('Download failed:', error); if (typeof hideLoadingOverlay === 'function') { hideLoadingOverlay(); } if (typeof showToast === 'function') { showToast('❌ เกิดข้อผิดพลาดในการดาวน์โหลด', 'error'); } } } // Enhanced Draggable Text System // Initialize draggable text system function initializeDraggableText() { try { // Check requirements if (!canvas) { console.error('Canvas not found'); return false; } if (!textElements) { console.error('textElements not defined'); return false; } if (!dragState) { console.error('dragState not defined'); return false; } // Remove existing listeners to prevent duplicates canvas.removeEventListener('mousedown', handleTextMouseDown); canvas.removeEventListener('mousemove', handleTextMouseMove); canvas.removeEventListener('mouseup', handleTextMouseUp); canvas.removeEventListener('mouseleave', handleTextMouseUp); canvas.removeEventListener('touchstart', handleTextTouchStart); canvas.removeEventListener('touchmove', handleTextTouchMove); canvas.removeEventListener('touchend', handleTextTouchEnd); // Add new listeners canvas.addEventListener('mousedown', handleTextMouseDown); canvas.addEventListener('mousemove', handleTextMouseMove); canvas.addEventListener('mouseup', handleTextMouseUp); canvas.addEventListener('mouseleave', handleTextMouseUp); // Touch events with passive: false for preventDefault to work canvas.addEventListener('touchstart', handleTextTouchStart, {passive: false}); canvas.addEventListener('touchmove', handleTextTouchMove, {passive: false}); canvas.addEventListener('touchend', handleTextTouchEnd); // Update text when inputs change const topTextInput = document.getElementById('top-text'); const bottomTextInput = document.getElementById('bottom-text'); if (topTextInput) { topTextInput.removeEventListener('input', updateTextElements); topTextInput.addEventListener('input', updateTextElements); } if (bottomTextInput) { bottomTextInput.removeEventListener('input', updateTextElements); bottomTextInput.addEventListener('input', updateTextElements); } // Initialize reset button const resetBtn = document.getElementById('reset-positions-btn'); if (resetBtn) { resetBtn.removeEventListener('click', resetTextPositions); resetBtn.addEventListener('click', resetTextPositions); } return true; } catch (error) { console.error('Error initializing draggable text:', error); return false; } } // Handle mouse down event for draggable text function handleTextMouseDown(e) { const rect = canvas.getBoundingClientRect(); const x = (e.clientX - rect.left) * (canvas.width / rect.width); const y = (e.clientY - rect.top) * (canvas.height / rect.height); const elementKey = getTextElementAt(x, y); if (elementKey) { dragState.isDragging = true; dragState.currentElement = textElements[elementKey]; dragState.startX = x; dragState.startY = y; dragState.offsetX = x - (textElements[elementKey].x * canvas.width); dragState.offsetY = y - (textElements[elementKey].y * canvas.height); // Visual feedback canvas.style.cursor = 'grabbing'; canvas.classList.add('dragging'); e.preventDefault(); } } // Handle mouse move event for draggable text function handleTextMouseMove(e) { if (dragState.isDragging && dragState.currentElement) { const rect = canvas.getBoundingClientRect(); const x = (e.clientX - rect.left) * (canvas.width / rect.width); const y = (e.clientY - rect.top) * (canvas.height / rect.height); // Update position dragState.currentElement.x = Math.max(0, Math.min(1, (x - dragState.offsetX) / canvas.width)); dragState.currentElement.y = Math.max(0, Math.min(1, (y - dragState.offsetY) / canvas.height)); // Redraw canvas drawMemeWithDraggableText(); e.preventDefault(); } } // Handle mouse up event for draggable text function handleTextMouseUp(e) { if (dragState.isDragging) { // Reset drag state dragState.isDragging = false; dragState.currentElement = null; // Reset cursor and visual feedback canvas.style.cursor = 'default'; canvas.classList.remove('dragging'); // Redraw canvas to finalize position drawMemeWithDraggableText(); e.preventDefault(); } } // Handle touch start event for draggable text function handleTextTouchStart(e) { const rect = canvas.getBoundingClientRect(); const touch = e.touches[0]; const x = (touch.clientX - rect.left) * (canvas.width / rect.width); const y = (touch.clientY - rect.top) * (canvas.height / rect.height); const elementKey = getTextElementAt(x, y); if (elementKey) { dragState.isDragging = true; dragState.currentElement = textElements[elementKey]; dragState.startX = x; dragState.startY = y; dragState.offsetX = x - (textElements[elementKey].x * canvas.width); dragState.offsetY = y - (textElements[elementKey].y * canvas.height); // Visual feedback canvas.classList.add('dragging'); e.preventDefault(); } } // Handle touch move event for draggable text function handleTextTouchMove(e) { if (dragState.isDragging && dragState.currentElement) { const rect = canvas.getBoundingClientRect(); const touch = e.touches[0]; const x = (touch.clientX - rect.left) * (canvas.width / rect.width); const y = (touch.clientY - rect.top) * (canvas.height / rect.height); // Update position dragState.currentElement.x = Math.max(0, Math.min(1, (x - dragState.offsetX) / canvas.width)); dragState.currentElement.y = Math.max(0, Math.min(1, (y - dragState.offsetY) / canvas.height)); // Redraw canvas drawMemeWithDraggableText(); e.preventDefault(); } } // Handle touch end event for draggable text function handleTextTouchEnd(e) { if (dragState.isDragging) { // Reset drag state dragState.isDragging = false; dragState.currentElement = null; // Reset visual feedback canvas.classList.remove('dragging'); // Redraw canvas to finalize position drawMemeWithDraggableText(); e.preventDefault(); } } // Update text elements from inputs function updateTextElements() { textElements.top.text = document.getElementById('top-text').value; textElements.bottom.text = document.getElementById('bottom-text').value; drawMemeWithDraggableText(); } // Core function to draw meme with draggable text function drawMemeWithDraggableText() { if (!currentImage || !canvas || !ctx) return; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw background image ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height); // Draw text elements Object.keys(textElements).forEach(key => { const element = textElements[key]; if (element.text) { drawTextElement(element); } }); } // Draw individual text element function drawTextElement(element) { const x = element.x * canvas.width; const y = element.y * canvas.height; // Set text properties ctx.font = `${element.fontSize}px ${element.fontFamily}`; ctx.textAlign = element.align; ctx.textBaseline = 'middle'; ctx.fillStyle = element.color; ctx.strokeStyle = element.strokeColor; ctx.lineWidth = element.strokeWidth; ctx.lineJoin = 'round'; ctx.miterLimit = 2; // Text wrapping const words = element.text.split(' '); const maxWidth = canvas.width * 0.9; const lines = []; let currentLine = ''; words.forEach(word => { const testLine = currentLine + (currentLine ? ' ' : '') + word; const metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && currentLine) { lines.push(currentLine); currentLine = word; } else { currentLine = testLine; } }); if (currentLine) { lines.push(currentLine); } // Draw lines const lineHeight = element.fontSize * 1.2; const totalHeight = lines.length * lineHeight; const startY = y - (totalHeight / 2) + (lineHeight / 2); lines.forEach((line, index) => { const lineY = startY + (index * lineHeight); // Draw stroke (outline) ctx.strokeText(line, x, lineY); // Draw fill ctx.fillText(line, x, lineY); }); } // Update text elements from input fields function updateTextElements() { const topText = document.getElementById('top-text'); const bottomText = document.getElementById('bottom-text'); if (topText) { textElements.top.text = topText.value; } if (bottomText) { textElements.bottom.text = bottomText.value; } drawMemeWithDraggableText(); } // Enhanced meme drawing function function drawMemeWithDraggableText() { if (!currentImage) return; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw image ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height); // Draw all text elements Object.keys(textElements).forEach(key => { const element = textElements[key]; if (element.text) { drawDraggableText(element); } }); // Draw drag indicators if dragging if (dragState.isDragging && dragState.currentElement) { drawDragIndicator(dragState.currentElement); } } // Draw individual text element function drawDraggableText(element) { const x = element.x * canvas.width; const y = element.y * canvas.height; // Set text properties ctx.font = `${element.fontSize}px ${element.fontFamily} `; ctx.textAlign = element.align; ctx.textBaseline = 'middle'; ctx.fillStyle = element.color; ctx.strokeStyle = element.strokeColor; ctx.lineWidth = element.strokeWidth; ctx.lineJoin = 'round'; ctx.miterLimit = 2; // Draw text with word wrapping const words = element.text.split(' '); const maxWidth = canvas.width * 0.9; const lines = []; let currentLine = ''; words.forEach(word => { const testLine = currentLine + (currentLine ? ' ' : '') + word; const metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && currentLine) { lines.push(currentLine); currentLine = word; } else { currentLine = testLine; } }); if (currentLine) { lines.push(currentLine); } // Draw each line const lineHeight = element.fontSize * 1.2; const totalHeight = lines.length * lineHeight; const startY = y - (totalHeight / 2) + (lineHeight / 2); lines.forEach((line, index) => { const lineY = startY + (index * lineHeight); // Draw stroke (outline) ctx.strokeText(line, x, lineY); // Draw fill ctx.fillText(line, x, lineY); }); } // Update canvas size display and styling function updateCanvasSize() { if (!canvas) return; const container = canvas.parentElement; if (!container) return; // Update container styling container.style.maxWidth = canvas.width + 'px'; container.style.margin = '0 auto'; // Add responsive scaling const maxWidth = Math.min(window.innerWidth * 0.9, 800); if (canvas.width > maxWidth) { const scale = maxWidth / canvas.width; canvas.style.width = (canvas.width * scale) + 'px'; canvas.style.height = (canvas.height * scale) + 'px'; } else { canvas.style.width = canvas.width + 'px'; canvas.style.height = canvas.height + 'px'; } // Update canvas attributes for better display canvas.style.border = '2px solid var(--glass-border)'; canvas.style.borderRadius = 'var(--radius-lg)'; canvas.style.boxShadow = 'var(--shadow-lg)'; canvas.style.background = '#f8f9fa'; } // Enhanced text element detection function getTextElementAt(x, y) { const relativeX = x / canvas.width; const relativeY = y / canvas.height; for (let key of Object.keys(textElements)) { const element = textElements[key]; if (!element.text) continue; // Calculate actual text bounds more accurately ctx.font = `${element.fontSize}px ${element.fontFamily}`; const metrics = ctx.measureText(element.text); const textWidth = metrics.width / canvas.width; const textHeight = (element.fontSize * 1.5) / canvas.height; const textX = element.x; const textY = element.y; // Create generous hit area for easy selection const hitAreaX = Math.max(textWidth / 2, 0.15); // อย่างน้อย 15% ของ canvas const hitAreaY = Math.max(textHeight / 2, 0.08); // อย่างน้อย 8% ของ canvas if (Math.abs(relativeX - textX) < hitAreaX && Math.abs(relativeY - textY) < hitAreaY) { return key; } } return null; } // Enhanced text positioning with better visual feedback function drawEnhancedDraggableText(element) { const x = element.x * canvas.width; const y = element.y * canvas.height; // Set text properties ctx.font = `${element.fontSize}px ${element.fontFamily}`; ctx.textAlign = element.align; ctx.textBaseline = 'middle'; ctx.fillStyle = element.color; ctx.strokeStyle = element.strokeColor; ctx.lineWidth = element.strokeWidth; ctx.lineJoin = 'round'; ctx.miterLimit = 2; // Enhanced text rendering with better wrapping const text = element.text; if (!text) return; const words = text.split(' '); const maxWidth = canvas.width * 0.95; // ใช้พื้นที่เกือบเต็ม canvas const lines = []; let currentLine = ''; words.forEach(word => { const testLine = currentLine + (currentLine ? ' ' : '') + word; const metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && currentLine) { lines.push(currentLine); currentLine = word; } else { currentLine = testLine; } }); if (currentLine) { lines.push(currentLine); } // Draw each line with enhanced positioning const lineHeight = element.fontSize * 1.2; const totalHeight = lines.length * lineHeight; const startY = y - (totalHeight / 2) + (lineHeight / 2); lines.forEach((line, index) => { const lineY = startY + (index * lineHeight); // Add drop shadow for better visibility ctx.save(); ctx.shadowColor = 'rgba(0, 0, 0, 0.8)'; ctx.shadowBlur = 4; ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; // Draw stroke (outline) ctx.strokeText(line, x, lineY); ctx.restore(); // Draw fill ctx.fillText(line, x, lineY); }); // Show coordinates when dragging if (dragState.isDragging && dragState.currentElement === element) { ctx.save(); ctx.font = '14px Arial'; ctx.fillStyle = '#00d4ff'; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; const coordText = `X: ${Math.round(element.x * 100)}%, Y: ${Math.round(element.y * 100)}%`; const coordX = Math.min(x + 20, canvas.width - 120); const coordY = Math.max(y - 40, 20); // Background for coordinates ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.fillRect(coordX - 5, coordY - 5, 110, 25); // Coordinate text ctx.fillStyle = '#00d4ff'; ctx.fillText(coordText, coordX, coordY); ctx.restore(); } } // Override the original drawDraggableText function function drawDraggableText(element) { drawEnhancedDraggableText(element); } // Set default meme template with error handling function setDefaultMeme() { try { // Wait for templates to be loaded from templates.js if (typeof memeTemplates === 'undefined' || !memeTemplates.length) { setTimeout(setDefaultMeme, 300); return; } // Check if canvas is ready if (!canvas || !ctx) { setTimeout(setDefaultMeme, 200); return; } // Set default template to first one (Distracted Boyfriend) const defaultTemplate = memeTemplates.find(t => t.name === "Distracted Boyfriend") || memeTemplates[0]; if (defaultTemplate && currentImage) { // Already have a template loaded return; } if (defaultTemplate) { setTimeout(() => { loadTemplate(defaultTemplate); }, 100); } else { console.warn('No default template found'); } } catch (error) { console.error('Error in setDefaultMeme:', error); // Retry after delay setTimeout(setDefaultMeme, 500); } } // Enhanced template loading with error handling function loadTemplate(template) { try { if (!template || !template.src) { console.error('Invalid template:', template); if (typeof showToast === 'function') { showToast('❌ เทมเพลตไม่ถูกต้อง', 'error'); } return; } // Show loading state if (typeof showLoadingOverlay === 'function') { showLoadingOverlay('กำลังโหลดเทมเพลต...', 'pulse'); } const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function() { try { currentImage = img; canvas.width = template.width || img.naturalWidth; canvas.height = template.height || img.naturalHeight; // Update canvas size and position updateCanvasSize(); // Reset text positions for new template textElements.top.x = 0.5; textElements.top.y = 0.15; textElements.bottom.x = 0.5; textElements.bottom.y = 0.85; // Update text and redraw updateTextElements(); drawMemeWithDraggableText(); // Hide loading if (typeof hideLoadingOverlay === 'function') { hideLoadingOverlay(); } // Success feedback if (typeof showToast === 'function') { showToast('✨ โหลด ' + template.name + ' สำเร็จ!', 'success'); } // Add success animation if (typeof addSuccessAnimation === 'function') { addSuccessAnimation(canvas); } } catch (error) { console.error('Error processing loaded template:', error); if (typeof hideLoadingOverlay === 'function') { hideLoadingOverlay(); } if (typeof showToast === 'function') { showToast('❌ เกิดข้อผิดพลาดในการประมวลผลเทมเพลต', 'error'); } } }; img.onerror = function() { console.error('Failed to load template image:', template.src); if (typeof hideLoadingOverlay === 'function') { hideLoadingOverlay(); } if (typeof showToast === 'function') { showToast('❌ ไม่สามารถโหลดรูปภาพเทมเพลตได้', 'error'); } }; img.src = template.src; } catch (error) { console.error('Error in loadTemplate:', error); if (typeof hideLoadingOverlay === 'function') { hideLoadingOverlay(); } if (typeof showToast === 'function') { showToast('❌ เกิดข้อผิดพลาดในการโหลดเทมเพลต', 'error'); } } }