用純前端技術製作一個專業的圖文選單工具,讓使用者能自訂文字、上傳背景並一鍵下載

DEMO頁面:https://kuokaikai.github.io/line_oa_menu_img_gen/index.html

專案概述

這個專案是一個純前端的LINE OA圖文選單產生器,主要功能包括:

  • 🎨 多種模板選擇(2×2、1×3、3×1、2×3)
  • 🖼️ 支援背景圖片上傳與拖拽
  • 🎨 自訂顏色與文字描邊
  • 📱 響應式設計
  • 💾 一鍵下載PNG圖片

技術棧: HTML5 Canvas + CSS3 + Vanilla JavaScript

核心技術解析

1. HTML結構設計

採用語意化標籤構建清晰的頁面結構:

<div class="container">  
    <header class="header">  
        <!-- 標題區 -->  
    </header>  
    <main class="main-content">  
        <aside class="controls">  
            <!-- 控制面板:模板選擇、背景設定、文字輸入 -->  
        </aside>  
        <section class="preview">  
            <canvas id="menuCanvas" width="800" height="540"></canvas>  
            <!-- 預覽與下載 -->  
        </section>  
    </main>  
</div>

2. CSS現代化設計

響應式佈局

.main-content {  
    display: flex;  
    gap: 30px;  
}


/* 手機版垂直排列 */  
@media (max-width: 768px) {  
    .main-content {  
        flex-direction: column;  
    }  
}

視覺效果

.section {  
    border-radius: 15px;  
    transition: all 0.3s ease;  
}


.section:hover {  
    transform: translateY(-2px);  
    box-shadow: 0 5px 15px rgba(0,195,0,0.1);  
}

3. JavaScript核心邏輯

狀態管理

let currentTemplate = '2x2';  
let menuTexts = ['選單1', '選單2', '選單3', '選單4'];  
let backgroundImage = null;


const templates = {  
    '2x2': { cols: 2, rows: 2, count: 4 },  
    '1x3': { cols: 1, rows: 3, count: 3 },  
    // ...其他模板  
};

事件驅動架構

function initializeApp() {  
    setupTemplateButtons();  
    setupBackgroundControls();  
    setupColorInputs();  
    generateMenuInputs();  
    drawMenu();  
}

Canvas繪圖引擎

核心繪製流程

function drawMenu() {  
    const canvas = document.getElementById('menuCanvas');  
    const ctx = canvas.getContext('2d');  
    const template = templates[currentTemplate];  
      
    // 1. 清除畫布  
    ctx.fillStyle = '#ffffff';  
    ctx.fillRect(0, 0, canvas.width, canvas.height);  
      
    // 2. 繪製背景  
    if (backgroundType === 'image' && backgroundImage) {  
        drawBackgroundImage(ctx, canvas);  
    } else {  
        ctx.fillStyle = document.getElementById('bgColor').value;  
        ctx.fillRect(0, 0, canvas.width, canvas.height);  
    }  
      
    // 3. 繪製格線和文字  
    drawMenuGrid(ctx, template);  
}

智慧文字處理

function drawWrappedText(ctx, text, x, y, maxWidth) {  
    const words = text.split('');  
    let line = '';  
    const lines = [];  
      
    // 自動換行邏輯  
    for (let char of words) {  
        const testLine = line + char;  
        if (ctx.measureText(testLine).width > maxWidth && line !== '') {  
            lines.push(line);  
            line = char;  
        } else {  
            line = testLine;  
        }  
    }  
    lines.push(line);  
      
    // 繪製多行文字  
    const lineHeight = fontSize * 1.2;  
    lines.forEach((line, index) => {  
        ctx.fillText(line, x, y + index * lineHeight);  
    });  
}

檔案上傳系統

多元上傳方式

點擊上傳

const fileInput = document.getElementById('bgImageInput');  
const uploadArea = document.getElementById('fileUploadArea');


uploadArea.addEventListener('click', () => fileInput.click());  
fileInput.addEventListener('change', handleFileSelect);

拖拽上傳

function setupDragAndDrop(uploadArea) {  
    uploadArea.addEventListener('dragover', (e) => {  
        e.preventDefault();  
        uploadArea.classList.add('dragover');  
    });  
      
    uploadArea.addEventListener('drop', (e) => {  
        e.preventDefault();  
        uploadArea.classList.remove('dragover');  
          
        const files = e.dataTransfer.files;  
        if (files[0] && files[0].type.startsWith('image/')) {  
            loadImageFile(files[0]);  
        }  
    });  
}

圖片處理

function loadImageFile(file) {  
    const reader = new FileReader();  
    reader.onload = function(e) {  
        const img = new Image();  
        img.onload = function() {  
            backgroundImage = img;  
            drawMenu(); // 重新繪製  
        };  
        img.src = e.target.result;  
    };  
    reader.readAsDataURL(file);  
}

關鍵特色功能

1. 動態模板切換

function setupTemplateButtons() {  
    const templateBtns = document.querySelectorAll('.template-btn');  
    templateBtns.forEach(btn => {  
        btn.addEventListener('click', () => {  
            currentTemplate = btn.dataset.template;  
            generateMenuInputs(); // 重新生成輸入框  
            drawMenu(); // 重新繪製  
        });  
    });  
}

2. 背景圖片適配

支援三種填充模式:填滿裁切、完整顯示、拉伸填滿

function drawBackgroundImage(ctx, canvas) {  
    const fitMode = document.getElementById('bgFitMode').value;  
    const opacity = document.getElementById('bgOpacity').value;  
      
    ctx.save();  
    ctx.globalAlpha = opacity;  
      
    // 根據模式計算尺寸  
    const dimensions = calculateImageDimensions(backgroundImage, canvas, fitMode);  
    ctx.drawImage(backgroundImage, dimensions.x, dimensions.y, dimensions.width, dimensions.height);  
      
    ctx.restore();  
}

3. 一鍵下載功能

function downloadImage() {  
    const canvas = document.getElementById('menuCanvas');  
    const link = document.createElement('a');  
      
    try {  
        link.download = `LINE_OA_圖文選單_${currentTemplate}_${Date.now()}.png`;  
        link.href = canvas.toDataURL('image/png', 1.0);  
          
        document.body.appendChild(link);  
        link.click();  
        document.body.removeChild(link);  
    } catch (error) {  
        alert('下載失敗,請重新嘗試');  
    }  
}

開發技巧與優化

1. 效能優化

  • 使用防抖動避免頻繁重繪
  • Canvas高解析度適配
  • 圖片預載入優化

2. 使用者體驗

  • 即時預覽更新
  • 視覺回饋動畫
  • 錯誤處理提示

3. 響應式設計

  • Mobile-first方法
  • 彈性網格系統
  • 觸控友善介面

部署與發布

GitHub Pages部署

  1. 將檔案上傳到GitHub倉庫
  2. 啟用GitHub Pages功能
  3. 選擇main分支作為來源
  4. 專案即可線上使用

檔案結構

line-oa-generator/  
├── index.html          # 主頁面  
├── README.md           # 專案說明  
└── assets/  
    ├── css/style.css   # 樣式檔案  
    └── js/app.js       # 主要邏輯

總結

這個專案展示了如何用純前端技術打造一個實用的圖形設計工具。關鍵學習點包括:

🎨 Canvas API — 學會用程式碼繪製圖形和文字
📱 響應式設計 — 現代化的UI/UX實現
🗂️ 檔案處理 — 拖拽上傳與圖片處理
效能優化 — 防抖動與Canvas優化技巧

透過這個專案,你可以掌握前端圖形處理的核心技術,並應用到更多創意專案中!