// 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
✨ ฟีเจอร์หลัก
- 🎯 Drag & Drop Text: ลากข้อความไปตำแหน่งที่ต้องการ
- 🖼️ 22+ Templates: เทมเพลตมีมยอดนิยม
- 📱 Responsive: ใช้งานได้ทุกอุปกรณ์
- 🌙 Dark/Light Mode: เลือกธีมตามใจ
- ⚡ Real-time Preview: เห็นผลทันที
- 💾 High Quality Export: ดาวน์โหลด PNG คุณภาพสูง
🛠️ เทคโนโลยีที่ใช้
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');
}
}
}