文章目录
-
- 在当今数字时代,视觉内容已成为网站吸引用户、提升参与度的关键因素。动画和GIF图像因其生动、直观的表达方式,在社交媒体、产品演示、教程说明等场景中发挥着不可替代的作用。然而,对于大多数网站运营者来说,创建这些视觉元素通常需要依赖外部工具或专业设计人员,这不仅增加了成本,也降低了内容创作的灵活性。 通过为WordPress网站开发一个内置的简易动画制作与GIF生成工具,您可以: 大幅降低视觉内容创作门槛 提高内容生产效率 保持品牌视觉一致性 增强用户互动体验 减少对外部服务的依赖 本教程将详细指导您如何通过WordPress代码二次开发,实现这一实用功能,让您的网站具备专业级的简易动画制作能力。
-
- 在开始开发之前,确保您已具备以下条件: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel搭建本地WordPress环境 代码编辑器:Visual Studio Code、Sublime Text或PHPStorm WordPress版本:5.0或更高版本 基础技能:HTML、CSS、JavaScript、PHP基础知识和WordPress主题开发经验
- 为避免影响主主题功能,建议创建专用子主题: /* Theme Name: Animation Tools Child Theme Template: your-parent-theme-folder-name Version: 1.0 */ // 引入父主题样式表 add_action('wp_enqueue_scripts', 'animation_tools_enqueue_styles'); function animation_tools_enqueue_styles() { wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); wp_enqueue_style('child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style')); }
- 在子主题目录中创建以下结构: your-child-theme/ ├── animation-tools/ │ ├── css/ │ │ └── animation-editor.css │ ├── js/ │ │ ├── animation-editor.js │ │ └── gif-generator.js │ └── lib/ │ └── gif.js ├── templates/ │ └── animation-tool-page.php └── functions.php
-
- 首先,在WordPress后台添加一个管理页面: // 在functions.php中添加 add_action('admin_menu', 'animation_tools_admin_menu'); function animation_tools_admin_menu() { add_menu_page( '动画制作工具', '动画工具', 'manage_options', 'animation-tools', 'animation_tools_admin_page', 'dashicons-video-alt3', 30 ); } function animation_tools_admin_page() { ?> <div class="wrap"> <h1>网站动画制作工具</h1> <div id="animation-tools-admin"> <p>从这里可以访问网站内置的动画编辑器。</p> <a href="<?php echo home_url('/animation-editor/'); ?>" class="button button-primary" target="_blank">打开动画编辑器</a> </div> </div> <?php }
- 创建自定义页面模板,用于前端动画编辑: <?php /* Template Name: 动画编辑器 */ get_header(); ?> <div class="animation-editor-container"> <div class="editor-header"> <h1>简易动画制作工具</h1> <div class="editor-tabs"> <button class="tab-btn active" data-tab="canvas-editor">画布编辑</button> <button class="tab-btn" data-tab="timeline">时间轴</button> <button class="tab-btn" data-tab="export">导出选项</button> </div> </div> <div class="editor-main"> <div class="tool-panel"> <div class="tool-section"> <h3>绘图工具</h3> <div class="tool-buttons"> <button class="tool-btn active" data-tool="select">选择</button> <button class="tool-btn" data-tool="brush">画笔</button> <button class="tool-btn" data-tool="shape">形状</button> <button class="tool-btn" data-tool="text">文字</button> <button class="tool-btn" data-tool="eraser">橡皮擦</button> </div> </div> <div class="tool-section"> <h3>属性设置</h3> <div class="property-controls"> <div class="control-group"> <label>画笔大小:</label> <input type="range" id="brush-size" min="1" max="50" value="5"> <span id="brush-size-value">5px</span> </div> <div class="control-group"> <label>颜色:</label> <input type="color" id="brush-color" value="#ff0000"> </div> <div class="control-group"> <label>不透明度:</label> <input type="range" id="brush-opacity" min="0" max="100" value="100"> <span id="opacity-value">100%</span> </div> </div> </div> </div> <div class="canvas-area"> <div class="canvas-container"> <canvas id="animation-canvas" width="800" height="600"></canvas> <div class="canvas-overlay"> <div class="canvas-grid"></div> </div> </div> <div class="canvas-controls"> <button id="clear-canvas">清空画布</button> <button id="undo-action">撤销</button> <button id="redo-action">重做</button> <button id="add-frame">添加帧</button> </div> </div> <div class="timeline-panel"> <div class="frames-container"> <div class="frames-list" id="frames-list"> <!-- 帧缩略图将通过JS动态生成 --> </div> <div class="timeline-controls"> <button id="play-animation">播放</button> <input type="range" id="frame-speed" min="1" max="30" value="12"> <span>帧速率: <span id="fps-value">12</span> fps</span> </div> </div> </div> </div> <div class="export-panel"> <h3>导出选项</h3> <div class="export-options"> <div class="export-format"> <label><input type="radio" name="export-format" value="gif" checked> GIF动画</label> <label><input type="radio" name="export-format" value="apng"> APNG</label> <label><input type="radio" name="export-format" value="spritesheet"> 精灵图</label> </div> <div class="export-settings"> <div class="setting-group"> <label>循环次数:</label> <select id="loop-count"> <option value="0">无限循环</option> <option value="1">1次</option> <option value="3">3次</option> <option value="5">5次</option> </select> </div> <div class="setting-group"> <label>质量:</label> <input type="range" id="export-quality" min="1" max="100" value="80"> <span id="quality-value">80%</span> </div> </div> <div class="export-actions"> <button id="preview-export">预览</button> <button id="generate-export" class="button-primary">生成并下载</button> <button id="save-to-media">保存到媒体库</button> </div> </div> </div> </div> <?php get_footer(); ?>
-
- 创建动画编辑器的主要JavaScript功能: // animation-editor.js document.addEventListener('DOMContentLoaded', function() { // 初始化变量 const canvas = document.getElementById('animation-canvas'); const ctx = canvas.getContext('2d'); const framesList = document.getElementById('frames-list'); let currentTool = 'brush'; let brushSize = 5; let brushColor = '#ff0000'; let brushOpacity = 1.0; let frames = []; let currentFrameIndex = 0; let isDrawing = false; let lastX = 0; let lastY = 0; let undoStack = []; let redoStack = []; // 初始化第一帧 function initializeFirstFrame() { const frameData = { id: Date.now(), imageData: null, thumbnail: null, delay: 100 // 默认每帧延迟100ms }; frames.push(frameData); updateFrameDisplay(); saveFrameState(); } // 保存当前帧状态 function saveFrameState() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); frames[currentFrameIndex].imageData = imageData; // 创建缩略图 const thumbnailCanvas = document.createElement('canvas'); thumbnailCanvas.width = 80; thumbnailCanvas.height = 60; const thumbnailCtx = thumbnailCanvas.getContext('2d'); thumbnailCtx.drawImage(canvas, 0, 0, 80, 60); frames[currentFrameIndex].thumbnail = thumbnailCanvas.toDataURL(); updateFrameDisplay(); } // 更新帧显示 function updateFrameDisplay() { framesList.innerHTML = ''; frames.forEach((frame, index) => { const frameElement = document.createElement('div'); frameElement.className = `frame-thumb ${index === currentFrameIndex ? 'active' : ''}`; frameElement.innerHTML = ` <div class="frame-number">${index + 1}</div> <img src="${frame.thumbnail || ''}" alt="帧 ${index + 1}"> <div class="frame-actions"> <button class="frame-delete" data-index="${index}">删除</button> </div> `; frameElement.addEventListener('click', () => { switchToFrame(index); }); framesList.appendChild(frameElement); }); } // 切换到指定帧 function switchToFrame(index) { saveFrameState(); // 保存当前帧 currentFrameIndex = index; // 恢复帧内容 const frame = frames[index]; if (frame.imageData) { ctx.putImageData(frame.imageData, 0, 0); } else { ctx.clearRect(0, 0, canvas.width, canvas.height); } updateFrameDisplay(); } // 添加新帧 document.getElementById('add-frame').addEventListener('click', function() { saveFrameState(); const newFrame = { id: Date.now(), imageData: null, thumbnail: null, delay: 100 }; frames.splice(currentFrameIndex + 1, 0, newFrame); currentFrameIndex++; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); updateFrameDisplay(); saveFrameState(); }); // 绘图功能 function startDrawing(e) { if (currentTool === 'select') return; isDrawing = true; [lastX, lastY] = getMousePos(canvas, e); // 开始新路径 if (currentTool === 'brush' || currentTool === 'eraser') { ctx.beginPath(); ctx.moveTo(lastX, lastY); } } function draw(e) { if (!isDrawing) return; const [x, y] = getMousePos(canvas, e); switch(currentTool) { case 'brush': ctx.lineTo(x, y); ctx.strokeStyle = brushColor; ctx.lineWidth = brushSize; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.globalAlpha = brushOpacity; ctx.stroke(); break; case 'eraser': ctx.lineTo(x, y); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = brushSize; ctx.lineCap = 'round'; ctx.stroke(); break; case 'shape': // 绘制形状逻辑 break; case 'text': // 文本输入逻辑 break; } [lastX, lastY] = [x, y]; } function stopDrawing() { if (!isDrawing) return; isDrawing = false; ctx.closePath(); saveFrameState(); } // 获取鼠标位置 function getMousePos(canvas, evt) { const rect = canvas.getBoundingClientRect(); return [ evt.clientX - rect.left, evt.clientY - rect.top ]; } // 工具选择 document.querySelectorAll('.tool-btn').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); currentTool = this.dataset.tool; }); }); // 属性控制 document.getElementById('brush-size').addEventListener('input', function() { brushSize = this.value; document.getElementById('brush-size-value').textContent = brushSize + 'px'; }); document.getElementById('brush-color').addEventListener('input', function() { brushColor = this.value; }); document.getElementById('brush-opacity').addEventListener('input', function() { brushOpacity = this.value / 100; document.getElementById('opacity-value').textContent = this.value + '%'; }); // 画布事件监听 canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseout', stopDrawing); // 清空画布 document.getElementById('clear-canvas').addEventListener('click', function() { if (confirm('确定要清空当前帧吗?')) { ctx.clearRect(0, 0, canvas.width, canvas.height); saveFrameState(); } }); // 初始化 initializeFirstFrame(); });
- // 在animation-editor.js中添加动画播放功能 let animationInterval = null; let isPlaying = false; function playAnimation() { if (frames.length < 2) { alert('至少需要两帧才能播放动画'); return; } if (isPlaying) { stopAnimation(); return; } isPlaying = true; document.getElementById('play-animation').textContent = '停止'; let frameIndex = 0; const fps = parseInt(document.getElementById('frame-speed').value); const delay = 1000 / fps; animationInterval = setInterval(() => { switchToFrame(frameIndex); frameIndex = (frameIndex + 1) % frames.length; }, delay); } function stopAnimation() { isPlaying = false; document.getElementById('play-animation').textContent = '播放'; clearInterval(animationInterval); } // 播放按钮事件 document.getElementById('play-animation').addEventListener('click', playAnimation); // 帧速率控制 document.getElementById('frame-speed').addEventListener('input', function() { document.getElementById('fps-value').textContent = this.value; // 如果正在播放,重新开始以应用新速度 if (isPlaying) { stopAnimation(); playAnimation(); } });
-
- 首先,下载并引入GIF.js库: // 在functions.php中注册GIF.js add_action('wp_enqueue_scripts', 'enqueue_animation_tools_scripts'); function enqueue_animation_tools_scripts() { if (is_page_template('animation-editor.php')) { wp_enqueue_script('gif-js', get_stylesheet_directory_uri() . '/animation-tools/lib/gif.js', array(), '1.0.0', true); wp_enqueue_script('animation-editor', get_stylesheet_directory_uri() . '/animation-tools/js/animation-editor.js', array('jquery', 'gif-js'), '1.0.0', true); wp_enqueue_script('gif-generator', get_stylesheet_directory_uri() . '/animation-tools/js/gif-generator.js', array('animation-editor'), '1.0.0', true); wp_enqueue_style('animation-editor-style', get_stylesheet_directory_uri() . '/animation-tools/css/animation-editor.css', array(), '1.0.0'); } }
- // gif-generator.js class GIFGenerator { constructor(options = {}) { this.options = { quality: options.quality || 10, width: options.width || 800, height: options.height || 600, workerScript: options.workerScript || '/wp-content/themes/your-child-theme/animation-tools/lib/gif.worker.js' }; this.gif = new GIF({ workers: 2, quality: this.options.quality, width: this.options.width, height: this.options.height, workerScript: this.options.workerScript }); this.frames = []; this.onProgress = null; this.onFinished = null; } addFrame(canvas, delay) { this.gif.addFrame(canvas, { delay: delay || 100, copy: true }); } setOptions(options) { if (options.quality) this.gif.setOption('quality', options.quality); if (options.repeat !== undefined) this.gif.setOption('repeat', options.repeat); } generate() { return new Promise((resolve, reject) => { this.gif.on('progress', (progress) => { if (this.onProgress) { this.onProgress(progress); } }); this.gif.on('finished', (blob) => { if (this.onFinished) { this.onFinished(blob); } resolve(blob); }); this.gif.render(); }); } abort() { this.gif.abort(); } } // 导出功能实现document.addEventListener('DOMContentLoaded', function() { const generateExportBtn = document.getElementById('generate-export'); const previewExportBtn = document.getElementById('preview-export'); const saveToMediaBtn = document.getElementById('save-to-media'); const exportQuality = document.getElementById('export-quality'); const loopCount = document.getElementById('loop-count'); let currentGIFBlob = null; // 更新质量显示 exportQuality.addEventListener('input', function() { document.getElementById('quality-value').textContent = this.value + '%'; }); // 生成GIF async function generateGIF() { if (frames.length === 0) { alert('请先创建至少一帧动画'); return; } generateExportBtn.disabled = true; generateExportBtn.textContent = '生成中...'; try { // 创建临时画布用于生成GIF const tempCanvas = document.createElement('canvas'); tempCanvas.width = canvas.width; tempCanvas.height = canvas.height; const tempCtx = tempCanvas.getContext('2d'); // 初始化GIF生成器 const gifGenerator = new GIFGenerator({ quality: parseInt(exportQuality.value), width: canvas.width, height: canvas.height }); // 设置循环次数 const repeat = parseInt(loopCount.value); gifGenerator.setOptions({ repeat: repeat === 0 ? 0 : repeat - 1 }); // 添加进度监听 gifGenerator.onProgress = (progress) => { console.log(`生成进度: ${Math.round(progress * 100)}%`); }; // 添加所有帧 for (let i = 0; i < frames.length; i++) { const frame = frames[i]; if (frame.imageData) { tempCtx.putImageData(frame.imageData, 0, 0); gifGenerator.addFrame(tempCanvas, frame.delay); } } // 生成GIF currentGIFBlob = await gifGenerator.generate(); // 创建下载链接 const url = URL.createObjectURL(currentGIFBlob); const a = document.createElement('a'); a.href = url; a.download = `animation-${Date.now()}.gif`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); alert('GIF生成完成并已开始下载!'); } catch (error) { console.error('GIF生成失败:', error); alert('GIF生成失败,请重试'); } finally { generateExportBtn.disabled = false; generateExportBtn.textContent = '生成并下载'; } } // 预览GIF async function previewGIF() { if (frames.length === 0) { alert('请先创建至少一帧动画'); return; } previewExportBtn.disabled = true; previewExportBtn.textContent = '预览生成中...'; try { // 创建临时画布 const tempCanvas = document.createElement('canvas'); tempCanvas.width = 400; // 预览尺寸较小 tempCanvas.height = 300; const tempCtx = tempCanvas.getContext('2d'); // 创建预览GIF生成器 const previewGenerator = new GIFGenerator({ quality: 20, // 预览质量较低 width: 400, height: 300 }); // 添加所有帧(缩放) for (let i = 0; i < frames.length; i++) { const frame = frames[i]; if (frame.imageData) { // 创建临时画布绘制并缩放 const frameCanvas = document.createElement('canvas'); frameCanvas.width = canvas.width; frameCanvas.height = canvas.height; const frameCtx = frameCanvas.getContext('2d'); frameCtx.putImageData(frame.imageData, 0, 0); // 缩放到预览尺寸 tempCtx.clearRect(0, 0, 400, 300); tempCtx.drawImage(frameCanvas, 0, 0, 400, 300); previewGenerator.addFrame(tempCanvas, frame.delay); } } // 生成预览GIF const previewBlob = await previewGenerator.generate(); const previewUrl = URL.createObjectURL(previewBlob); // 显示预览 showPreviewModal(previewUrl); } catch (error) { console.error('预览生成失败:', error); alert('预览生成失败'); } finally { previewExportBtn.disabled = false; previewExportBtn.textContent = '预览'; } } // 显示预览模态框 function showPreviewModal(gifUrl) { // 移除现有模态框 const existingModal = document.querySelector('.preview-modal'); if (existingModal) { existingModal.remove(); } // 创建模态框 const modal = document.createElement('div'); modal.className = 'preview-modal'; modal.innerHTML = ` <div class="preview-modal-content"> <div class="preview-modal-header"> <h3>GIF预览</h3> <button class="close-preview">×</button> </div> <div class="preview-modal-body"> <img src="${gifUrl}" alt="GIF预览" class="preview-gif"> <div class="preview-info"> <p>尺寸: 400x300 (预览)</p> <p>帧数: ${frames.length}</p> <p>文件大小: ${Math.round(currentGIFBlob?.size / 1024) || '未知'} KB</p> </div> </div> <div class="preview-modal-footer"> <button id="download-preview">下载预览</button> <button id="use-full-quality">使用高质量生成</button> </div> </div> `; document.body.appendChild(modal); // 关闭按钮事件 modal.querySelector('.close-preview').addEventListener('click', function() { modal.remove(); URL.revokeObjectURL(gifUrl); }); // 点击外部关闭 modal.addEventListener('click', function(e) { if (e.target === modal) { modal.remove(); URL.revokeObjectURL(gifUrl); } }); // 下载预览 modal.querySelector('#download-preview').addEventListener('click', function() { const a = document.createElement('a'); a.href = gifUrl; a.download = `preview-${Date.now()}.gif`; a.click(); }); // 使用高质量生成 modal.querySelector('#use-full-quality').addEventListener('click', function() { modal.remove(); URL.revokeObjectURL(gifUrl); generateGIF(); }); } // 保存到媒体库 async function saveToMediaLibrary() { if (!currentGIFBlob) { alert('请先生成GIF'); return; } saveToMediaBtn.disabled = true; saveToMediaBtn.textContent = '上传中...'; try { // 创建FormData const formData = new FormData(); formData.append('action', 'save_animation_to_media'); formData.append('security', animationToolsAjax.nonce); formData.append('gif_data', currentGIFBlob, `animation-${Date.now()}.gif`); formData.append('title', `动画作品 ${new Date().toLocaleDateString()}`); // 发送AJAX请求 const response = await fetch(animationToolsAjax.ajax_url, { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { alert(`动画已保存到媒体库!n文件ID: ${result.data.attachment_id}n查看: ${result.data.edit_link}`); } else { throw new Error(result.data || '上传失败'); } } catch (error) { console.error('保存到媒体库失败:', error); alert('保存失败: ' + error.message); } finally { saveToMediaBtn.disabled = false; saveToMediaBtn.textContent = '保存到媒体库'; } } // 绑定事件 generateExportBtn.addEventListener('click', generateGIF); previewExportBtn.addEventListener('click', previewGIF); saveToMediaBtn.addEventListener('click', saveToMediaLibrary); }); ## 第五部分:后端处理与媒体库集成 ### 5.1 创建AJAX处理函数 // 在functions.php中添加AJAX处理add_action('wp_ajax_save_animation_to_media', 'handle_save_animation_to_media');add_action('wp_ajax_nopriv_save_animation_to_media', 'handle_save_animation_to_media'); function handle_save_animation_to_media() { // 安全检查 check_ajax_referer('animation_tools_nonce', 'security'); // 验证用户权限 if (!current_user_can('upload_files')) { wp_die('权限不足'); } // 检查文件上传 if (!isset($_FILES['gif_data']) || $_FILES['gif_data']['error'] !== UPLOAD_ERR_OK) { wp_send_json_error('文件上传失败'); } $file = $_FILES['gif_data']; // 验证文件类型 $allowed_types = array('image/gif'); $filetype = wp_check_filetype(basename($file['name'])); if (!in_array($filetype['type'], $allowed_types)) { wp_send_json_error('仅支持GIF格式'); } // 准备上传文件 $upload_overrides = array( 'test_form' => false, 'mimes' => array('gif' => 'image/gif') ); // 上传文件 $upload = wp_handle_upload($file, $upload_overrides); if (isset($upload['error'])) { wp_send_json_error($upload['error']); } // 准备附件数据 $attachment = array( 'post_mime_type' => $filetype['type'], 'post_title' => sanitize_text_field($_POST['title'] ?? '动画作品'), 'post_content' => '', 'post_status' => 'inherit', 'post_author' => get_current_user_id() ); // 插入附件到媒体库 $attachment_id = wp_insert_attachment($attachment, $upload['file']); if (is_wp_error($attachment_id)) { wp_send_json_error('插入媒体库失败'); } // 生成附件元数据 require_once(ABSPATH . 'wp-admin/includes/image.php'); $attachment_data = wp_generate_attachment_metadata($attachment_id, $upload['file']); wp_update_attachment_metadata($attachment_id, $attachment_data); // 返回成功响应 wp_send_json_success(array( 'attachment_id' => $attachment_id, 'url' => wp_get_attachment_url($attachment_id), 'edit_link' => admin_url('post.php?post=' . $attachment_id . '&action=edit') )); } // 注册AJAX脚本add_action('wp_enqueue_scripts', 'register_animation_tools_ajax');function register_animation_tools_ajax() { if (is_page_template('animation-editor.php')) { wp_localize_script('gif-generator', 'animationToolsAjax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('animation_tools_nonce') )); } } ### 5.2 创建短代码功能 // 添加短代码,允许在文章/页面中插入动画工具add_shortcode('animation_tool', 'animation_tool_shortcode');function animation_tool_shortcode($atts) { $atts = shortcode_atts(array( 'width' => '100%', 'height' => '600px', 'mode' => 'editor' // editor, preview, simple ), $atts); ob_start(); if ($atts['mode'] === 'simple') { // 简化版动画工具 ?> <div class="simple-animation-tool" style="width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>;"> <div class="simple-tool-header"> <h4>简易动画制作</h4> </div> <div class="simple-canvas-container"> <canvas class="simple-animation-canvas"></canvas> </div> <div class="simple-controls"> <button class="simple-draw-btn">绘制</button> <button class="simple-clear-btn">清空</button> <button class="simple-save-btn">保存为GIF</button> </div> </div> <?php } else { // 完整版工具链接 $editor_url = home_url('/animation-editor/'); ?> <div class="animation-tool-link"> <h3>动画制作工具</h3> <p>使用我们的内置工具创建自定义动画和GIF</p> <a href="<?php echo esc_url($editor_url); ?>" class="button button-primary"> 打开动画编辑器 </a> </div> <?php } return ob_get_clean(); } ## 第六部分:样式设计与优化 ### 6.1 基础样式设计 / animation-editor.css /.animation-editor-container { max-width: 1400px; margin: 20px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .editor-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; } .editor-header h1 { margin: 0 0 20px 0; font-size: 28px; } .editor-tabs { display: flex; gap: 10px; } .tab-btn { background: rgba(255,255,255,0.2); border: none; color: white; padding: 10px 20px; border-radius: 4px; cursor: pointer; transition: background 0.3s; } .tab-btn.active { background: rgba(255,255,255,0.4); } .tab-btn:hover { background: rgba(255,255,255,0.3); } .editor-main { display: grid; grid-template-columns: 250px 1fr 300px; min-height: 600px; } .tool-panel { background: #f8f9fa; border-right: 1px solid #dee2e6; padding: 20px; } .tool-section { margin-bottom: 30px; } .tool-section h3 { margin-top: 0; color: #495057; font-size: 16px; border-bottom: 2px solid #667eea; padding-bottom: 5px; } .tool-buttons { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; } .tool-btn { background: white; border: 2px solid #dee2e6; padding: 10px; border-radius: 4px; cursor: pointer; transition: all 0.3s; } .tool-btn.active { border-color: #667eea; background: #667eea; color: white; } .tool-btn:hover { border-color: #495057; } .property-controls { display: flex; flex-direction: column; gap: 15px; } .control-group { display: flex; flex-direction: column; gap: 5px; } .control-group label { font-size: 14px; color: #6c757d; } .control-group input[type="range"] { width: 100%; } .canvas-area { padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; } .canvas-container { position: relative; border: 2px dashed #dee2e6; border-radius: 4px; margin-bottom: 20px; }
- display: block; background: white; cursor: crosshair; } .canvas-overlay { position: absolute; top: 0; left: 0; right:
在当今数字时代,视觉内容已成为网站吸引用户、提升参与度的关键因素。动画和GIF图像因其生动、直观的表达方式,在社交媒体、产品演示、教程说明等场景中发挥着不可替代的作用。然而,对于大多数网站运营者来说,创建这些视觉元素通常需要依赖外部工具或专业设计人员,这不仅增加了成本,也降低了内容创作的灵活性。
通过为WordPress网站开发一个内置的简易动画制作与GIF生成工具,您可以:
- 大幅降低视觉内容创作门槛
- 提高内容生产效率
- 保持品牌视觉一致性
- 增强用户互动体验
- 减少对外部服务的依赖
本教程将详细指导您如何通过WordPress代码二次开发,实现这一实用功能,让您的网站具备专业级的简易动画制作能力。
在开始开发之前,确保您已具备以下条件:
- 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel搭建本地WordPress环境
- 代码编辑器:Visual Studio Code、Sublime Text或PHPStorm
- WordPress版本:5.0或更高版本
- 基础技能:HTML、CSS、JavaScript、PHP基础知识和WordPress主题开发经验
为避免影响主主题功能,建议创建专用子主题:
/*
Theme Name: Animation Tools Child Theme
Template: your-parent-theme-folder-name
Version: 1.0
*/
// 引入父主题样式表
add_action('wp_enqueue_scripts', 'animation_tools_enqueue_styles');
function animation_tools_enqueue_styles() {
wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css');
wp_enqueue_style('child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style'));
}
在子主题目录中创建以下结构:
your-child-theme/
├── animation-tools/
│ ├── css/
│ │ └── animation-editor.css
│ ├── js/
│ │ ├── animation-editor.js
│ │ └── gif-generator.js
│ └── lib/
│ └── gif.js
├── templates/
│ └── animation-tool-page.php
└── functions.php
首先,在WordPress后台添加一个管理页面:
// 在functions.php中添加
add_action('admin_menu', 'animation_tools_admin_menu');
function animation_tools_admin_menu() {
add_menu_page(
'动画制作工具',
'动画工具',
'manage_options',
'animation-tools',
'animation_tools_admin_page',
'dashicons-video-alt3',
30
);
}
function animation_tools_admin_page() {
?>
<div class="wrap">
<h1>网站动画制作工具</h1>
<div id="animation-tools-admin">
<p>从这里可以访问网站内置的动画编辑器。</p>
<a href="<?php echo home_url('/animation-editor/'); ?>" class="button button-primary" target="_blank">打开动画编辑器</a>
</div>
</div>
<?php
}
创建自定义页面模板,用于前端动画编辑:
<?php
/*
Template Name: 动画编辑器
*/
get_header(); ?>
<div class="animation-editor-container">
<div class="editor-header">
<h1>简易动画制作工具</h1>
<div class="editor-tabs">
<button class="tab-btn active" data-tab="canvas-editor">画布编辑</button>
<button class="tab-btn" data-tab="timeline">时间轴</button>
<button class="tab-btn" data-tab="export">导出选项</button>
</div>
</div>
<div class="editor-main">
<div class="tool-panel">
<div class="tool-section">
<h3>绘图工具</h3>
<div class="tool-buttons">
<button class="tool-btn active" data-tool="select">选择</button>
<button class="tool-btn" data-tool="brush">画笔</button>
<button class="tool-btn" data-tool="shape">形状</button>
<button class="tool-btn" data-tool="text">文字</button>
<button class="tool-btn" data-tool="eraser">橡皮擦</button>
</div>
</div>
<div class="tool-section">
<h3>属性设置</h3>
<div class="property-controls">
<div class="control-group">
<label>画笔大小:</label>
<input type="range" id="brush-size" min="1" max="50" value="5">
<span id="brush-size-value">5px</span>
</div>
<div class="control-group">
<label>颜色:</label>
<input type="color" id="brush-color" value="#ff0000">
</div>
<div class="control-group">
<label>不透明度:</label>
<input type="range" id="brush-opacity" min="0" max="100" value="100">
<span id="opacity-value">100%</span>
</div>
</div>
</div>
</div>
<div class="canvas-area">
<div class="canvas-container">
<canvas id="animation-canvas" width="800" height="600"></canvas>
<div class="canvas-overlay">
<div class="canvas-grid"></div>
</div>
</div>
<div class="canvas-controls">
<button id="clear-canvas">清空画布</button>
<button id="undo-action">撤销</button>
<button id="redo-action">重做</button>
<button id="add-frame">添加帧</button>
</div>
</div>
<div class="timeline-panel">
<div class="frames-container">
<div class="frames-list" id="frames-list">
<!-- 帧缩略图将通过JS动态生成 -->
</div>
<div class="timeline-controls">
<button id="play-animation">播放</button>
<input type="range" id="frame-speed" min="1" max="30" value="12">
<span>帧速率: <span id="fps-value">12</span> fps</span>
</div>
</div>
</div>
</div>
<div class="export-panel">
<h3>导出选项</h3>
<div class="export-options">
<div class="export-format">
<label><input type="radio" name="export-format" value="gif" checked> GIF动画</label>
<label><input type="radio" name="export-format" value="apng"> APNG</label>
<label><input type="radio" name="export-format" value="spritesheet"> 精灵图</label>
</div>
<div class="export-settings">
<div class="setting-group">
<label>循环次数:</label>
<select id="loop-count">
<option value="0">无限循环</option>
<option value="1">1次</option>
<option value="3">3次</option>
<option value="5">5次</option>
</select>
</div>
<div class="setting-group">
<label>质量:</label>
<input type="range" id="export-quality" min="1" max="100" value="80">
<span id="quality-value">80%</span>
</div>
</div>
<div class="export-actions">
<button id="preview-export">预览</button>
<button id="generate-export" class="button-primary">生成并下载</button>
<button id="save-to-media">保存到媒体库</button>
</div>
</div>
</div>
</div>
<?php get_footer(); ?>
创建动画编辑器的主要JavaScript功能:
// animation-editor.js
document.addEventListener('DOMContentLoaded', function() {
// 初始化变量
const canvas = document.getElementById('animation-canvas');
const ctx = canvas.getContext('2d');
const framesList = document.getElementById('frames-list');
let currentTool = 'brush';
let brushSize = 5;
let brushColor = '#ff0000';
let brushOpacity = 1.0;
let frames = [];
let currentFrameIndex = 0;
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let undoStack = [];
let redoStack = [];
// 初始化第一帧
function initializeFirstFrame() {
const frameData = {
id: Date.now(),
imageData: null,
thumbnail: null,
delay: 100 // 默认每帧延迟100ms
};
frames.push(frameData);
updateFrameDisplay();
saveFrameState();
}
// 保存当前帧状态
function saveFrameState() {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
frames[currentFrameIndex].imageData = imageData;
// 创建缩略图
const thumbnailCanvas = document.createElement('canvas');
thumbnailCanvas.width = 80;
thumbnailCanvas.height = 60;
const thumbnailCtx = thumbnailCanvas.getContext('2d');
thumbnailCtx.drawImage(canvas, 0, 0, 80, 60);
frames[currentFrameIndex].thumbnail = thumbnailCanvas.toDataURL();
updateFrameDisplay();
}
// 更新帧显示
function updateFrameDisplay() {
framesList.innerHTML = '';
frames.forEach((frame, index) => {
const frameElement = document.createElement('div');
frameElement.className = `frame-thumb ${index === currentFrameIndex ? 'active' : ''}`;
frameElement.innerHTML = `
<div class="frame-number">${index + 1}</div>
<img src="${frame.thumbnail || ''}" alt="帧 ${index + 1}">
<div class="frame-actions">
<button class="frame-delete" data-index="${index}">删除</button>
</div>
`;
frameElement.addEventListener('click', () => {
switchToFrame(index);
});
framesList.appendChild(frameElement);
});
}
// 切换到指定帧
function switchToFrame(index) {
saveFrameState(); // 保存当前帧
currentFrameIndex = index;
// 恢复帧内容
const frame = frames[index];
if (frame.imageData) {
ctx.putImageData(frame.imageData, 0, 0);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
updateFrameDisplay();
}
// 添加新帧
document.getElementById('add-frame').addEventListener('click', function() {
saveFrameState();
const newFrame = {
id: Date.now(),
imageData: null,
thumbnail: null,
delay: 100
};
frames.splice(currentFrameIndex + 1, 0, newFrame);
currentFrameIndex++;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
updateFrameDisplay();
saveFrameState();
});
// 绘图功能
function startDrawing(e) {
if (currentTool === 'select') return;
isDrawing = true;
[lastX, lastY] = getMousePos(canvas, e);
// 开始新路径
if (currentTool === 'brush' || currentTool === 'eraser') {
ctx.beginPath();
ctx.moveTo(lastX, lastY);
}
}
function draw(e) {
if (!isDrawing) return;
const [x, y] = getMousePos(canvas, e);
switch(currentTool) {
case 'brush':
ctx.lineTo(x, y);
ctx.strokeStyle = brushColor;
ctx.lineWidth = brushSize;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.globalAlpha = brushOpacity;
ctx.stroke();
break;
case 'eraser':
ctx.lineTo(x, y);
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = brushSize;
ctx.lineCap = 'round';
ctx.stroke();
break;
case 'shape':
// 绘制形状逻辑
break;
case 'text':
// 文本输入逻辑
break;
}
[lastX, lastY] = [x, y];
}
function stopDrawing() {
if (!isDrawing) return;
isDrawing = false;
ctx.closePath();
saveFrameState();
}
// 获取鼠标位置
function getMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
return [
evt.clientX - rect.left,
evt.clientY - rect.top
];
}
// 工具选择
document.querySelectorAll('.tool-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.tool-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
currentTool = this.dataset.tool;
});
});
// 属性控制
document.getElementById('brush-size').addEventListener('input', function() {
brushSize = this.value;
document.getElementById('brush-size-value').textContent = brushSize + 'px';
});
document.getElementById('brush-color').addEventListener('input', function() {
brushColor = this.value;
});
document.getElementById('brush-opacity').addEventListener('input', function() {
brushOpacity = this.value / 100;
document.getElementById('opacity-value').textContent = this.value + '%';
});
// 画布事件监听
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
// 清空画布
document.getElementById('clear-canvas').addEventListener('click', function() {
if (confirm('确定要清空当前帧吗?')) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
saveFrameState();
}
});
// 初始化
initializeFirstFrame();
});
// 在animation-editor.js中添加动画播放功能
let animationInterval = null;
let isPlaying = false;
function playAnimation() {
if (frames.length < 2) {
alert('至少需要两帧才能播放动画');
return;
}
if (isPlaying) {
stopAnimation();
return;
}
isPlaying = true;
document.getElementById('play-animation').textContent = '停止';
let frameIndex = 0;
const fps = parseInt(document.getElementById('frame-speed').value);
const delay = 1000 / fps;
animationInterval = setInterval(() => {
switchToFrame(frameIndex);
frameIndex = (frameIndex + 1) % frames.length;
}, delay);
}
function stopAnimation() {
isPlaying = false;
document.getElementById('play-animation').textContent = '播放';
clearInterval(animationInterval);
}
// 播放按钮事件
document.getElementById('play-animation').addEventListener('click', playAnimation);
// 帧速率控制
document.getElementById('frame-speed').addEventListener('input', function() {
document.getElementById('fps-value').textContent = this.value;
// 如果正在播放,重新开始以应用新速度
if (isPlaying) {
stopAnimation();
playAnimation();
}
});
// 在animation-editor.js中添加动画播放功能
let animationInterval = null;
let isPlaying = false;
function playAnimation() {
if (frames.length < 2) {
alert('至少需要两帧才能播放动画');
return;
}
if (isPlaying) {
stopAnimation();
return;
}
isPlaying = true;
document.getElementById('play-animation').textContent = '停止';
let frameIndex = 0;
const fps = parseInt(document.getElementById('frame-speed').value);
const delay = 1000 / fps;
animationInterval = setInterval(() => {
switchToFrame(frameIndex);
frameIndex = (frameIndex + 1) % frames.length;
}, delay);
}
function stopAnimation() {
isPlaying = false;
document.getElementById('play-animation').textContent = '播放';
clearInterval(animationInterval);
}
// 播放按钮事件
document.getElementById('play-animation').addEventListener('click', playAnimation);
// 帧速率控制
document.getElementById('frame-speed').addEventListener('input', function() {
document.getElementById('fps-value').textContent = this.value;
// 如果正在播放,重新开始以应用新速度
if (isPlaying) {
stopAnimation();
playAnimation();
}
});
首先,下载并引入GIF.js库:
// 在functions.php中注册GIF.js
add_action('wp_enqueue_scripts', 'enqueue_animation_tools_scripts');
function enqueue_animation_tools_scripts() {
if (is_page_template('animation-editor.php')) {
wp_enqueue_script('gif-js', get_stylesheet_directory_uri() . '/animation-tools/lib/gif.js', array(), '1.0.0', true);
wp_enqueue_script('animation-editor', get_stylesheet_directory_uri() . '/animation-tools/js/animation-editor.js', array('jquery', 'gif-js'), '1.0.0', true);
wp_enqueue_script('gif-generator', get_stylesheet_directory_uri() . '/animation-tools/js/gif-generator.js', array('animation-editor'), '1.0.0', true);
wp_enqueue_style('animation-editor-style', get_stylesheet_directory_uri() . '/animation-tools/css/animation-editor.css', array(), '1.0.0');
}
}
// gif-generator.js
class GIFGenerator {
constructor(options = {}) {
this.options = {
quality: options.quality || 10,
width: options.width || 800,
height: options.height || 600,
workerScript: options.workerScript || '/wp-content/themes/your-child-theme/animation-tools/lib/gif.worker.js'
};
this.gif = new GIF({
workers: 2,
quality: this.options.quality,
// gif-generator.js
class GIFGenerator {
constructor(options = {}) {
this.options = {
quality: options.quality || 10,
width: options.width || 800,
height: options.height || 600,
workerScript: options.workerScript || '/wp-content/themes/your-child-theme/animation-tools/lib/gif.worker.js'
};
this.gif = new GIF({
workers: 2,
quality: this.options.quality,
width: this.options.width,
height: this.options.height,
workerScript: this.options.workerScript
});
this.frames = [];
this.onProgress = null;
this.onFinished = null;
}
addFrame(canvas, delay) {
this.gif.addFrame(canvas, {
delay: delay || 100,
copy: true
});
}
setOptions(options) {
if (options.quality) this.gif.setOption('quality', options.quality);
if (options.repeat !== undefined) this.gif.setOption('repeat', options.repeat);
}
generate() {
return new Promise((resolve, reject) => {
this.gif.on('progress', (progress) => {
if (this.onProgress) {
this.onProgress(progress);
}
});
this.gif.on('finished', (blob) => {
if (this.onFinished) {
this.onFinished(blob);
}
resolve(blob);
});
this.gif.render();
});
}
abort() {
this.gif.abort();
}
}
// 导出功能实现
document.addEventListener('DOMContentLoaded', function() {
const generateExportBtn = document.getElementById('generate-export');
const previewExportBtn = document.getElementById('preview-export');
const saveToMediaBtn = document.getElementById('save-to-media');
const exportQuality = document.getElementById('export-quality');
const loopCount = document.getElementById('loop-count');
let currentGIFBlob = null;
// 更新质量显示
exportQuality.addEventListener('input', function() {
document.getElementById('quality-value').textContent = this.value + '%';
});
// 生成GIF
async function generateGIF() {
if (frames.length === 0) {
alert('请先创建至少一帧动画');
return;
}
generateExportBtn.disabled = true;
generateExportBtn.textContent = '生成中...';
try {
// 创建临时画布用于生成GIF
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const tempCtx = tempCanvas.getContext('2d');
// 初始化GIF生成器
const gifGenerator = new GIFGenerator({
quality: parseInt(exportQuality.value),
width: canvas.width,
height: canvas.height
});
// 设置循环次数
const repeat = parseInt(loopCount.value);
gifGenerator.setOptions({ repeat: repeat === 0 ? 0 : repeat - 1 });
// 添加进度监听
gifGenerator.onProgress = (progress) => {
console.log(`生成进度: ${Math.round(progress * 100)}%`);
};
// 添加所有帧
for (let i = 0; i < frames.length; i++) {
const frame = frames[i];
if (frame.imageData) {
tempCtx.putImageData(frame.imageData, 0, 0);
gifGenerator.addFrame(tempCanvas, frame.delay);
}
}
// 生成GIF
currentGIFBlob = await gifGenerator.generate();
// 创建下载链接
const url = URL.createObjectURL(currentGIFBlob);
const a = document.createElement('a');
a.href = url;
a.download = `animation-${Date.now()}.gif`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
alert('GIF生成完成并已开始下载!');
} catch (error) {
console.error('GIF生成失败:', error);
alert('GIF生成失败,请重试');
} finally {
generateExportBtn.disabled = false;
generateExportBtn.textContent = '生成并下载';
}
}
// 预览GIF
async function previewGIF() {
if (frames.length === 0) {
alert('请先创建至少一帧动画');
return;
}
previewExportBtn.disabled = true;
previewExportBtn.textContent = '预览生成中...';
try {
// 创建临时画布
const tempCanvas = document.createElement('canvas');
tempCanvas.width = 400; // 预览尺寸较小
tempCanvas.height = 300;
const tempCtx = tempCanvas.getContext('2d');
// 创建预览GIF生成器
const previewGenerator = new GIFGenerator({
quality: 20, // 预览质量较低
width: 400,
height: 300
});
// 添加所有帧(缩放)
for (let i = 0; i < frames.length; i++) {
const frame = frames[i];
if (frame.imageData) {
// 创建临时画布绘制并缩放
const frameCanvas = document.createElement('canvas');
frameCanvas.width = canvas.width;
frameCanvas.height = canvas.height;
const frameCtx = frameCanvas.getContext('2d');
frameCtx.putImageData(frame.imageData, 0, 0);
// 缩放到预览尺寸
tempCtx.clearRect(0, 0, 400, 300);
tempCtx.drawImage(frameCanvas, 0, 0, 400, 300);
previewGenerator.addFrame(tempCanvas, frame.delay);
}
}
// 生成预览GIF
const previewBlob = await previewGenerator.generate();
const previewUrl = URL.createObjectURL(previewBlob);
// 显示预览
showPreviewModal(previewUrl);
} catch (error) {
console.error('预览生成失败:', error);
alert('预览生成失败');
} finally {
previewExportBtn.disabled = false;
previewExportBtn.textContent = '预览';
}
}
// 显示预览模态框
function showPreviewModal(gifUrl) {
// 移除现有模态框
const existingModal = document.querySelector('.preview-modal');
if (existingModal) {
existingModal.remove();
}
// 创建模态框
const modal = document.createElement('div');
modal.className = 'preview-modal';
modal.innerHTML = `
<div class="preview-modal-content">
<div class="preview-modal-header">
<h3>GIF预览</h3>
<button class="close-preview">×</button>
</div>
<div class="preview-modal-body">
<img src="${gifUrl}" alt="GIF预览" class="preview-gif">
<div class="preview-info">
<p>尺寸: 400x300 (预览)</p>
<p>帧数: ${frames.length}</p>
<p>文件大小: ${Math.round(currentGIFBlob?.size / 1024) || '未知'} KB</p>
</div>
</div>
<div class="preview-modal-footer">
<button id="download-preview">下载预览</button>
<button id="use-full-quality">使用高质量生成</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 关闭按钮事件
modal.querySelector('.close-preview').addEventListener('click', function() {
modal.remove();
URL.revokeObjectURL(gifUrl);
});
// 点击外部关闭
modal.addEventListener('click', function(e) {
if (e.target === modal) {
modal.remove();
URL.revokeObjectURL(gifUrl);
}
});
// 下载预览
modal.querySelector('#download-preview').addEventListener('click', function() {
const a = document.createElement('a');
a.href = gifUrl;
a.download = `preview-${Date.now()}.gif`;
a.click();
});
// 使用高质量生成
modal.querySelector('#use-full-quality').addEventListener('click', function() {
modal.remove();
URL.revokeObjectURL(gifUrl);
generateGIF();
});
}
// 保存到媒体库
async function saveToMediaLibrary() {
if (!currentGIFBlob) {
alert('请先生成GIF');
return;
}
saveToMediaBtn.disabled = true;
saveToMediaBtn.textContent = '上传中...';
try {
// 创建FormData
const formData = new FormData();
formData.append('action', 'save_animation_to_media');
formData.append('security', animationToolsAjax.nonce);
formData.append('gif_data', currentGIFBlob, `animation-${Date.now()}.gif`);
formData.append('title', `动画作品 ${new Date().toLocaleDateString()}`);
// 发送AJAX请求
const response = await fetch(animationToolsAjax.ajax_url, {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
alert(`动画已保存到媒体库!n文件ID: ${result.data.attachment_id}n查看: ${result.data.edit_link}`);
} else {
throw new Error(result.data || '上传失败');
}
} catch (error) {
console.error('保存到媒体库失败:', error);
alert('保存失败: ' + error.message);
} finally {
saveToMediaBtn.disabled = false;
saveToMediaBtn.textContent = '保存到媒体库';
}
}
// 绑定事件
generateExportBtn.addEventListener('click', generateGIF);
previewExportBtn.addEventListener('click', previewGIF);
saveToMediaBtn.addEventListener('click', saveToMediaLibrary);
});
## 第五部分:后端处理与媒体库集成
### 5.1 创建AJAX处理函数
// 在functions.php中添加AJAX处理
add_action('wp_ajax_save_animation_to_media', 'handle_save_animation_to_media');
add_action('wp_ajax_nopriv_save_animation_to_media', 'handle_save_animation_to_media');
function handle_save_animation_to_media() {
// 安全检查
check_ajax_referer('animation_tools_nonce', 'security');
// 验证用户权限
if (!current_user_can('upload_files')) {
wp_die('权限不足');
}
// 检查文件上传
if (!isset($_FILES['gif_data']) || $_FILES['gif_data']['error'] !== UPLOAD_ERR_OK) {
wp_send_json_error('文件上传失败');
}
$file = $_FILES['gif_data'];
// 验证文件类型
$allowed_types = array('image/gif');
$filetype = wp_check_filetype(basename($file['name']));
if (!in_array($filetype['type'], $allowed_types)) {
wp_send_json_error('仅支持GIF格式');
}
// 准备上传文件
$upload_overrides = array(
'test_form' => false,
'mimes' => array('gif' => 'image/gif')
);
// 上传文件
$upload = wp_handle_upload($file, $upload_overrides);
if (isset($upload['error'])) {
wp_send_json_error($upload['error']);
}
// 准备附件数据
$attachment = array(
'post_mime_type' => $filetype['type'],
'post_title' => sanitize_text_field($_POST['title'] ?? '动画作品'),
'post_content' => '',
'post_status' => 'inherit',
'post_author' => get_current_user_id()
);
// 插入附件到媒体库
$attachment_id = wp_insert_attachment($attachment, $upload['file']);
if (is_wp_error($attachment_id)) {
wp_send_json_error('插入媒体库失败');
}
// 生成附件元数据
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attachment_data = wp_generate_attachment_metadata($attachment_id, $upload['file']);
wp_update_attachment_metadata($attachment_id, $attachment_data);
// 返回成功响应
wp_send_json_success(array(
'attachment_id' => $attachment_id,
'url' => wp_get_attachment_url($attachment_id),
'edit_link' => admin_url('post.php?post=' . $attachment_id . '&action=edit')
));
}
// 注册AJAX脚本
add_action('wp_enqueue_scripts', 'register_animation_tools_ajax');
function register_animation_tools_ajax() {
if (is_page_template('animation-editor.php')) {
wp_localize_script('gif-generator', 'animationToolsAjax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('animation_tools_nonce')
));
}
}
### 5.2 创建短代码功能
// 添加短代码,允许在文章/页面中插入动画工具
add_shortcode('animation_tool', 'animation_tool_shortcode');
function animation_tool_shortcode($atts) {
$atts = shortcode_atts(array(
'width' => '100%',
'height' => '600px',
'mode' => 'editor' // editor, preview, simple
), $atts);
ob_start();
if ($atts['mode'] === 'simple') {
// 简化版动画工具
?>
<div class="simple-animation-tool" style="width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>;">
<div class="simple-tool-header">
<h4>简易动画制作</h4>
</div>
<div class="simple-canvas-container">
<canvas class="simple-animation-canvas"></canvas>
</div>
<div class="simple-controls">
<button class="simple-draw-btn">绘制</button>
<button class="simple-clear-btn">清空</button>
<button class="simple-save-btn">保存为GIF</button>
</div>
</div>
<?php
} else {
// 完整版工具链接
$editor_url = home_url('/animation-editor/');
?>
<div class="animation-tool-link">
<h3>动画制作工具</h3>
<p>使用我们的内置工具创建自定义动画和GIF</p>
<a href="<?php echo esc_url($editor_url); ?>" class="button button-primary">
打开动画编辑器
</a>
</div>
<?php
}
return ob_get_clean();
}
## 第六部分:样式设计与优化
### 6.1 基础样式设计
/ animation-editor.css /
.animation-editor-container {
max-width: 1400px;
margin: 20px auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.editor-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
}
.editor-header h1 {
margin: 0 0 20px 0;
font-size: 28px;
}
.editor-tabs {
display: flex;
gap: 10px;
}
.tab-btn {
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.tab-btn.active {
background: rgba(255,255,255,0.4);
}
.tab-btn:hover {
background: rgba(255,255,255,0.3);
}
.editor-main {
display: grid;
grid-template-columns: 250px 1fr 300px;
min-height: 600px;
}
.tool-panel {
background: #f8f9fa;
border-right: 1px solid #dee2e6;
padding: 20px;
}
.tool-section {
margin-bottom: 30px;
}
.tool-section h3 {
margin-top: 0;
color: #495057;
font-size: 16px;
border-bottom: 2px solid #667eea;
padding-bottom: 5px;
}
.tool-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.tool-btn {
background: white;
border: 2px solid #dee2e6;
padding: 10px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.tool-btn.active {
border-color: #667eea;
background: #667eea;
color: white;
}
.tool-btn:hover {
border-color: #495057;
}
.property-controls {
display: flex;
flex-direction: column;
gap: 15px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 5px;
}
.control-group label {
font-size: 14px;
color: #6c757d;
}
.control-group input[type="range"] {
width: 100%;
}
.canvas-area {
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.canvas-container {
position: relative;
border: 2px dashed #dee2e6;
border-radius: 4px;
margin-bottom: 20px;
}
display: block;
background: white;
cursor: crosshair;
display: block;
background: white;
cursor: crosshair;
}
.canvas-overlay {
position: absolute;
top: 0;
left: 0;
right:


