开发指南:打造网站内嵌的在线简易图片编辑与美化工具
引言:为什么网站需要内置图片编辑工具?
在当今视觉内容主导的互联网环境中,图片已成为网站内容不可或缺的组成部分。无论是博客文章、产品展示还是社交媒体分享,高质量的图片都能显著提升用户体验和内容吸引力。然而,许多网站运营者面临一个共同挑战:用户上传的图片往往需要调整大小、裁剪、添加水印或进行简单美化,而传统的解决方案要么功能过于复杂,要么需要跳转到外部工具,导致用户体验中断。
WordPress作为全球最流行的内容管理系统,拥有强大的扩展能力。通过代码二次开发,我们可以在WordPress网站中嵌入一个轻量级的在线图片编辑与美化工具,让用户在不离开网站的情况下完成基本的图片处理操作。这不仅提升了用户体验,还能增加用户粘性和内容生产效率。
本文将详细介绍如何通过WordPress代码二次开发,实现一个功能完善但操作简单的内嵌图片编辑工具,涵盖从需求分析、技术选型到具体实现的完整流程。
一、需求分析与功能规划
1.1 核心功能需求
在开始开发之前,我们需要明确工具应具备的核心功能:
-
基础编辑功能:
- 图片裁剪与调整大小
- 旋转与翻转
- 亮度、对比度、饱和度调整
- 锐化与模糊效果
-
美化增强功能:
- 滤镜与特效应用
- 文字添加与样式设置
- 贴图与形状叠加
- 边框与阴影效果
-
实用工具:
- 图片压缩与格式转换
- 水印添加与管理
- 批量处理基础功能
- 撤销/重做操作
-
用户体验需求:
- 响应式设计,适配不同设备
- 直观的操作界面
- 实时预览效果
- 快速导出与保存
1.2 技术可行性分析
基于WordPress平台,我们有多种技术方案可选:
-
前端技术栈:
- HTML5 Canvas:用于图片处理的核心技术
- JavaScript(ES6+):实现交互逻辑
- CSS3:界面样式与动画效果
-
图片处理库选择:
- Fabric.js:功能强大的Canvas库,适合交互式图片编辑
- Caman.js:专注于滤镜和颜色调整
- 原生Canvas API:更轻量,但开发复杂度较高
-
WordPress集成方案:
- 短代码(Shortcode)嵌入
- Gutenberg块编辑器集成
- 独立管理页面
- 媒体库扩展
综合考虑开发效率与功能需求,我们选择Fabric.js作为核心图片处理库,通过短代码和媒体库扩展的方式集成到WordPress中。
二、开发环境搭建与准备工作
2.1 开发环境配置
-
本地WordPress环境:
- 安装Local by Flywheel或XAMPP
- 配置PHP 7.4+环境
- 确保启用GD库和ImageMagick扩展
-
代码编辑器准备:
- VS Code或PHPStorm
- 安装WordPress开发相关插件
-
版本控制:
- 初始化Git仓库
- 建立合理的分支管理策略
2.2 WordPress插件基础结构
创建插件目录结构:
wp-image-editor-tool/
├── wp-image-editor-tool.php # 主插件文件
├── includes/ # 核心功能文件
│ ├── class-editor-core.php # 编辑器核心类
│ ├── class-image-processor.php # 图片处理类
│ └── class-ajax-handler.php # AJAX处理类
├── admin/ # 后台管理文件
│ ├── css/
│ ├── js/
│ └── class-admin-settings.php
├── public/ # 前端文件
│ ├── css/
│ ├── js/
│ └── templates/
├── assets/ # 静态资源
│ ├── fonts/
│ ├── icons/
│ └── images/
└── vendor/ # 第三方库
└── fabric.js/
2.3 插件主文件配置
<?php
/**
* Plugin Name: WordPress图片编辑工具
* Plugin URI: https://yourwebsite.com/
* Description: 在WordPress网站中嵌入在线图片编辑与美化工具
* Version: 1.0.0
* Author: 你的名字
* License: GPL v2 or later
* Text Domain: wp-image-editor
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('WPIET_VERSION', '1.0.0');
define('WPIET_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPIET_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
require_once WPIET_PLUGIN_DIR . 'includes/class-plugin-init.php';
三、核心编辑器实现
3.1 Canvas编辑器初始化
创建编辑器核心类,初始化Fabric.js画布:
// public/js/editor-core.js
class ImageEditor {
constructor(canvasId, options = {}) {
this.canvas = new fabric.Canvas(canvasId, {
backgroundColor: '#f5f5f5',
preserveObjectStacking: true,
...options
});
this.history = [];
this.historyIndex = -1;
this.currentImage = null;
this.initEvents();
}
initEvents() {
// 保存操作历史
this.canvas.on('object:modified', () => this.saveState());
this.canvas.on('object:added', () => this.saveState());
this.canvas.on('object:removed', () => this.saveState());
}
saveState() {
// 限制历史记录数量
if (this.history.length > 20) {
this.history.shift();
}
this.history.push(JSON.stringify(this.canvas.toJSON()));
this.historyIndex = this.history.length - 1;
}
loadImage(url) {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(url, (img) => {
this.canvas.clear();
this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas), {
scaleX: this.canvas.width / img.width,
scaleY: this.canvas.height / img.height
});
this.currentImage = img;
this.saveState();
resolve(img);
}, {
crossOrigin: 'anonymous'
});
});
}
}
3.2 基础编辑功能实现
3.2.1 裁剪功能
class CropTool {
constructor(editor) {
this.editor = editor;
this.isCropping = false;
this.cropRect = null;
}
startCrop() {
this.isCropping = true;
this.cropRect = new fabric.Rect({
left: 100,
top: 100,
width: 200,
height: 200,
fill: 'rgba(0,0,0,0.3)',
stroke: '#ffffff',
strokeWidth: 2,
strokeDashArray: [5, 5],
selectable: true,
hasControls: true,
hasBorders: true
});
this.editor.canvas.add(this.cropRect);
this.editor.canvas.setActiveObject(this.cropRect);
}
applyCrop() {
if (!this.cropRect || !this.editor.currentImage) return;
const canvas = this.editor.canvas;
const rect = this.cropRect;
// 计算裁剪区域
const scaleX = this.editor.currentImage.scaleX;
const scaleY = this.editor.currentImage.scaleY;
const cropData = {
left: (rect.left - this.editor.currentImage.left) / scaleX,
top: (rect.top - this.editor.currentImage.top) / scaleY,
width: rect.width / scaleX,
height: rect.height / scaleY
};
// 创建临时canvas进行裁剪
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = cropData.width;
tempCanvas.height = cropData.height;
tempCtx.drawImage(
canvas.lowerCanvasEl,
cropData.left, cropData.top,
cropData.width, cropData.height,
0, 0,
cropData.width, cropData.height
);
// 加载裁剪后的图片
fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
canvas.clear();
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
this.editor.currentImage = img;
this.editor.saveState();
this.isCropping = false;
this.cropRect = null;
});
}
}
3.2.2 调整工具
class AdjustmentTool {
constructor(editor) {
this.editor = editor;
}
adjustBrightness(value) {
const filters = this.editor.currentImage.filters || [];
const brightnessFilter = new fabric.Image.filters.Brightness({
brightness: value / 100
});
// 查找并更新或添加亮度滤镜
const existingIndex = filters.findIndex(f => f.type === 'Brightness');
if (existingIndex >= 0) {
filters[existingIndex] = brightnessFilter;
} else {
filters.push(brightnessFilter);
}
this.applyFilters(filters);
}
adjustContrast(value) {
const filters = this.editor.currentImage.filters || [];
const contrastFilter = new fabric.Image.filters.Contrast({
contrast: value / 100
});
const existingIndex = filters.findIndex(f => f.type === 'Contrast');
if (existingIndex >= 0) {
filters[existingIndex] = contrastFilter;
} else {
filters.push(contrastFilter);
}
this.applyFilters(filters);
}
applyFilters(filters) {
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
3.3 美化功能实现
3.3.1 滤镜系统
class FilterSystem {
constructor(editor) {
this.editor = editor;
this.presets = {
vintage: {
brightness: -0.05,
saturation: 0.1,
sepia: 0.3
},
blackWhite: {
saturation: -1
},
cool: {
brightness: 0.05,
saturation: 0.2,
tint: {
color: '#0099ff',
opacity: 0.1
}
},
warm: {
brightness: 0.05,
saturation: 0.1,
tint: {
color: '#ff9900',
opacity: 0.1
}
}
};
}
applyPreset(presetName) {
const preset = this.presets[presetName];
if (!preset) return;
const filters = [];
if (preset.brightness !== undefined) {
filters.push(new fabric.Image.filters.Brightness({
brightness: preset.brightness
}));
}
if (preset.saturation !== undefined) {
filters.push(new fabric.Image.filters.Saturation({
saturation: preset.saturation
}));
}
if (preset.sepia !== undefined) {
filters.push(new fabric.Image.filters.Sepia({
amount: preset.sepia
}));
}
if (preset.tint) {
filters.push(new fabric.Image.filters.BlendColor({
color: preset.tint.color,
mode: 'tint',
alpha: preset.tint.opacity
}));
}
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
applyCustomFilter(filterConfig) {
// 实现自定义滤镜组合
const filters = [];
Object.keys(filterConfig).forEach(key => {
switch(key) {
case 'brightness':
filters.push(new fabric.Image.filters.Brightness(filterConfig[key]));
break;
case 'contrast':
filters.push(new fabric.Image.filters.Contrast(filterConfig[key]));
break;
case 'saturation':
filters.push(new fabric.Image.filters.Saturation(filterConfig[key]));
break;
// 更多滤镜类型...
}
});
this.editor.currentImage.filters = filters;
this.editor.currentImage.applyFilters();
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
3.3.2 文字添加功能
class TextTool {
constructor(editor) {
this.editor = editor;
this.defaultStyles = {
fontSize: 24,
fontFamily: 'Arial',
fill: '#000000',
fontWeight: 'normal',
textAlign: 'left'
};
}
addText(content, options = {}) {
const textOptions = {
...this.defaultStyles,
...options,
left: this.editor.canvas.width / 2,
top: this.editor.canvas.height / 2,
editable: true
};
const text = new fabric.Textbox(content, textOptions);
text.setControlsVisibility({
mt: false, // 隐藏上中控制点
mb: false // 隐藏下中控制点
});
this.editor.canvas.add(text);
this.editor.canvas.setActiveObject(text);
this.editor.saveState();
return text;
}
updateTextStyle(textObject, styles) {
textObject.set(styles);
this.editor.canvas.renderAll();
this.editor.saveState();
}
addTextShadow(textObject, shadowConfig) {
textObject.set({
shadow: new fabric.Shadow({
color: shadowConfig.color || 'rgba(0,0,0,0.5)',
blur: shadowConfig.blur || 5,
offsetX: shadowConfig.offsetX || 2,
offsetY: shadowConfig.offsetY || 2
})
});
this.editor.canvas.renderAll();
this.editor.saveState();
}
}
四、WordPress集成与后端处理
4.1 短代码集成
创建短代码,让用户可以在文章或页面中嵌入图片编辑器:
// includes/class-shortcode-handler.php
class WPIET_Shortcode_Handler {
public static function init() {
add_shortcode('wp_image_editor', [__CLASS__, 'render_editor']);
}
public static function render_editor($atts) {
$atts = shortcode_atts([
'width' => '800',
'height' => '600',
'image_id' => '',
'toolbar' => 'basic'
], $atts);
// 加载必要资源
wp_enqueue_style('wpiet-editor-style');
wp_enqueue_script('fabric-js');
wp_enqueue_script('wpiet-editor-script');
// 获取图片URL
$image_url = '';
if (!empty($atts['image_id'])) {
$image_url = wp_get_attachment_url($atts['image_id']);
}
// 渲染编辑器HTML
ob_start();
?>
<div class="wpiet-editor-container" data-config="<?php echo esc_attr(json_encode($atts)); ?>">
<div class="wpiet-toolbar">
<!-- 工具栏内容 -->
</div>
<div class="wpiet-canvas-container">
<canvas id="wpiet-canvas"
width="<?php echo esc_attr($atts['width']); ?>"
height="<?php echo esc_attr($atts['height']); ?>">
</canvas>
</div>
<div class="wpiet-sidebar">
<!-- 侧边栏工具 -->
</div>
<div class="wpiet-controls">
<button class="wpiet-btn-save">保存图片</button>
<button class="wpiet-btn-reset">重置</button>
</div>
</div>
<?php
return ob_get_clean();
}
}
4.2 媒体库集成
扩展WordPress媒体库,添加"编辑图片"选项:
// includes/class-media-library-integration.php
class WPIET_Media_Library_Integration {
public static function init() {
// 在媒体库列表添加编辑链接
add_filter('media_row_actions', [__CLASS__, 'add_edit_action'], 10, 2);
// 在附件详情页添加编辑按钮
add_action('attachment_submitbox_misc_actions', [__CLASS__, 'add_edit_button']);
// 添加媒体库模态框中的编辑选项
add_action('print_media_templates', [__CLASS__, 'add_media_template']);
}
public static function add_edit_action($actions, $post) {
if (wp_attachment_is_image($post)) {
$edit_url = admin_url('admin.php?page=wpiet-edit&image_id=' . $post->ID);
$actions['wpiet_edit'] = sprintf(
'<a href="%s" target="_blank">%s</a>',
esc_url($edit_url),
__('编辑图片', 'wp-image-editor')
);
}
return $actions;
}
public static function add_edit_button() {
global $post;
if (!wp_attachment_is_image($post->ID)) {
return;
}
?>
<div class="misc-pub-section">
<a href="<?php echo admin_url('admin.php?page=wpiet-edit&image_id=' . $post->ID); ?>"
class="button button-large"
target="_blank">
开发指南:打造网站内嵌的在线简易图片编辑与美化工具(续)
4.3 AJAX图片处理与保存
4.3.1 后端图片处理类
// includes/class-image-processor.php
class WPIET_Image_Processor {
private $allowed_mime_types = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp'
];
private $max_file_size = 5242880; // 5MB
public function process_image($image_data, $operations = []) {
// 验证图片数据
if (!$this->validate_image_data($image_data)) {
return new WP_Error('invalid_image', '无效的图片数据');
}
// 创建临时文件
$temp_file = $this->create_temp_file($image_data);
if (is_wp_error($temp_file)) {
return $temp_file;
}
// 应用图片操作
$processed_image = $this->apply_operations($temp_file, $operations);
// 清理临时文件
unlink($temp_file);
return $processed_image;
}
private function validate_image_data($image_data) {
// 检查数据格式
if (!is_string($image_data) || empty($image_data)) {
return false;
}
// 检查是否为有效的base64或URL
if (strpos($image_data, 'data:image') === 0) {
// base64格式
$parts = explode(',', $image_data);
if (count($parts) !== 2) {
return false;
}
// 解码并验证
$decoded = base64_decode($parts[1]);
if ($decoded === false) {
return false;
}
// 检查文件大小
if (strlen($decoded) > $this->max_file_size) {
return false;
}
// 检查MIME类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_buffer($finfo, $decoded);
finfo_close($finfo);
if (!in_array($mime_type, $this->allowed_mime_types)) {
return false;
}
}
return true;
}
private function create_temp_file($image_data) {
$temp_dir = get_temp_dir();
$temp_file = tempnam($temp_dir, 'wpiet_');
if (strpos($image_data, 'data:image') === 0) {
// base64数据
$parts = explode(',', $image_data);
$image_binary = base64_decode($parts[1]);
file_put_contents($temp_file, $image_binary);
} else {
// URL或文件路径
$response = wp_remote_get($image_data);
if (is_wp_error($response)) {
return $response;
}
$image_binary = wp_remote_retrieve_body($response);
file_put_contents($temp_file, $image_binary);
}
return $temp_file;
}
private function apply_operations($image_path, $operations) {
$editor = wp_get_image_editor($image_path);
if (is_wp_error($editor)) {
return $editor;
}
// 应用各项操作
foreach ($operations as $operation) {
switch ($operation['type']) {
case 'crop':
$editor->crop(
$operation['x'],
$operation['y'],
$operation['width'],
$operation['height']
);
break;
case 'resize':
$editor->resize(
$operation['width'],
$operation['height'],
$operation['crop'] ?? false
);
break;
case 'rotate':
$editor->rotate($operation['angle']);
break;
case 'flip':
$editor->flip(
$operation['direction'] === 'horizontal' ? 'horiz' : 'vert'
);
break;
case 'filter':
$this->apply_filter($editor, $operation);
break;
}
}
// 生成新文件名
$filename = 'edited-' . time() . '-' . wp_basename($image_path);
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['path'] . '/' . $filename;
// 保存图片
$result = $editor->save($file_path);
if (is_wp_error($result)) {
return $result;
}
return [
'path' => $result['path'],
'url' => $upload_dir['url'] . '/' . $result['file'],
'width' => $result['width'],
'height' => $result['height'],
'size' => filesize($result['path'])
];
}
private function apply_filter($editor, $filter) {
// 使用ImageMagick或GD应用滤镜
$image_path = $editor->get_file();
if (extension_loaded('imagick')) {
$this->apply_imagick_filter($image_path, $filter);
} else {
$this->apply_gd_filter($image_path, $filter);
}
}
private function apply_imagick_filter($image_path, $filter) {
$imagick = new Imagick($image_path);
switch ($filter['name']) {
case 'brightness':
$imagick->modulateImage(
$filter['value'] + 100,
100,
100
);
break;
case 'contrast':
$imagick->sigmoidalContrastImage(
true,
$filter['value'] / 10,
0
);
break;
case 'saturation':
$imagick->modulateImage(
100,
$filter['value'] + 100,
100
);
break;
case 'sepia':
$imagick->sepiaToneImage($filter['value']);
break;
case 'blur':
$imagick->gaussianBlurImage(
$filter['radius'],
$filter['sigma']
);
break;
}
$imagick->writeImage($image_path);
$imagick->destroy();
}
}
4.3.2 AJAX处理器
// includes/class-ajax-handler.php
class WPIET_Ajax_Handler {
public static function init() {
// 保存图片
add_action('wp_ajax_wpiet_save_image', [__CLASS__, 'save_image']);
add_action('wp_ajax_nopriv_wpiet_save_image', [__CLASS__, 'save_image_nopriv']);
// 获取图片信息
add_action('wp_ajax_wpiet_get_image_info', [__CLASS__, 'get_image_info']);
// 批量处理
add_action('wp_ajax_wpiet_batch_process', [__CLASS__, 'batch_process']);
}
public static function save_image() {
// 验证nonce
if (!check_ajax_referer('wpiet_editor_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
// 验证权限
if (!current_user_can('upload_files')) {
wp_die('权限不足', 403);
}
// 获取数据
$image_data = isset($_POST['image_data']) ? $_POST['image_data'] : '';
$operations = isset($_POST['operations']) ? json_decode(stripslashes($_POST['operations']), true) : [];
$filename = isset($_POST['filename']) ? sanitize_file_name($_POST['filename']) : '';
if (empty($image_data)) {
wp_send_json_error('没有图片数据');
}
// 处理图片
$processor = new WPIET_Image_Processor();
$result = $processor->process_image($image_data, $operations);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
// 创建媒体库附件
$attachment_id = self::create_attachment($result['path'], $filename);
if (is_wp_error($attachment_id)) {
wp_send_json_error($attachment_id->get_error_message());
}
// 返回结果
wp_send_json_success([
'attachment_id' => $attachment_id,
'url' => $result['url'],
'edit_url' => get_edit_post_link($attachment_id),
'size' => size_format($result['size'])
]);
}
public static function save_image_nopriv() {
// 非登录用户处理
if (!get_option('wpiet_allow_guest_upload', false)) {
wp_die('请登录后操作', 403);
}
// 验证reCAPTCHA(如果启用)
if (get_option('wpiet_enable_recaptcha', false)) {
$recaptcha_response = isset($_POST['g-recaptcha-response']) ? $_POST['g-recaptcha-response'] : '';
if (!self::verify_recaptcha($recaptcha_response)) {
wp_send_json_error('验证码验证失败');
}
}
// 继续处理图片
self::save_image();
}
private static function create_attachment($file_path, $filename) {
$file_type = wp_check_filetype($filename, null);
$attachment = [
'post_mime_type' => $file_type['type'],
'post_title' => preg_replace('/.[^.]+$/', '', $filename),
'post_content' => '',
'post_status' => 'inherit',
'guid' => wp_get_upload_dir()['url'] . '/' . $filename
];
$attachment_id = wp_insert_attachment($attachment, $file_path);
if (is_wp_error($attachment_id)) {
return $attachment_id;
}
// 生成附件元数据
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attachment_data = wp_generate_attachment_metadata($attachment_id, $file_path);
wp_update_attachment_metadata($attachment_id, $attachment_data);
return $attachment_id;
}
private static function verify_recaptcha($response) {
$secret_key = get_option('wpiet_recaptcha_secret_key', '');
if (empty($secret_key)) {
return true;
}
$verify_url = 'https://www.google.com/recaptcha/api/siteverify';
$verify_data = [
'secret' => $secret_key,
'response' => $response,
'remoteip' => $_SERVER['REMOTE_ADDR']
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $verify_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($verify_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$result = curl_exec($ch);
curl_close($ch);
$result_data = json_decode($result, true);
return isset($result_data['success']) && $result_data['success'] === true;
}
}
五、前端界面与用户体验优化
5.1 响应式编辑器界面
// public/js/editor-ui.js
class EditorUI {
constructor(editorInstance) {
this.editor = editorInstance;
this.uiState = {
activeTool: null,
isMobile: window.innerWidth < 768,
isFullscreen: false,
showSidebar: true
};
this.initUI();
this.bindEvents();
this.adaptLayout();
}
initUI() {
// 创建工具栏
this.createToolbar();
// 创建侧边栏
this.createSidebar();
// 创建底部控制栏
this.createControls();
// 创建模态框
this.createModals();
}
createToolbar() {
const toolbarHTML = `
<div class="wpiet-toolbar">
<div class="toolbar-section file">
<button class="toolbar-btn" data-action="open">
<i class="icon-folder-open"></i>
<span>打开</span>
</button>
<button class="toolbar-btn" data-action="save">
<i class="icon-save"></i>
<span>保存</span>
</button>
<button class="toolbar-btn" data-action="export">
<i class="icon-download"></i>
<span>导出</span>
</button>
</div>
<div class="toolbar-section edit">
<button class="toolbar-btn" data-tool="crop">
<i class="icon-crop"></i>
<span>裁剪</span>
</button>
<button class="toolbar-btn" data-tool="rotate">
<i class="icon-rotate-right"></i>
<span>旋转</span>
</button>
<button class="toolbar-btn" data-tool="flip">
<i class="icon-flip-horizontal"></i>
<span>翻转</span>
</button>
<button class="toolbar-btn" data-tool="adjust">
<i class="icon-sliders"></i>
<span>调整</span>
</button>
</div>
<div class="toolbar-section effects">
<button class="toolbar-btn" data-tool="filter">
<i class="icon-filter"></i>
<span>滤镜</span>
</button>
<button class="toolbar-btn" data-tool="text">
<i class="icon-type"></i>
<span>文字</span>
</button>
<button class="toolbar-btn" data-tool="sticker">
<i class="icon-sticker"></i>
<span>贴图</span>
</button>
<button class="toolbar-btn" data-tool="frame">
<i class="icon-square"></i>
<span>边框</span>
</button>
</div>
<div class="toolbar-section view">
<button class="toolbar-btn" data-action="zoom-in">
<i class="icon-zoom-in"></i>
</button>
<button class="toolbar-btn" data-action="zoom-out">
<i class="icon-zoom-out"></i>
</button>
<button class="toolbar-btn" data-action="fullscreen">
<i class="icon-maximize"></i>
</button>
<button class="toolbar-btn" data-action="toggle-sidebar">
<i class="icon-sidebar"></i>
</button>
</div>
</div>
`;
document.querySelector('.wpiet-editor-container').insertAdjacentHTML('afterbegin', toolbarHTML);
}
createSidebar() {
const sidebarHTML = `
<div class="wpiet-sidebar ${this.uiState.showSidebar ? 'active' : ''}">
<div class="sidebar-header">
<h3>工具选项</h3>
<button class="sidebar-close">×</button>
</div>
<div class="sidebar-content">
<!-- 动态内容 -->
<div class="tool-options" id="tool-options">
<div class="empty-state">
<i class="icon-tool"></i>
<p>选择一个工具开始编辑</p>
</div>
</div>
<!-- 历史记录 -->
<div class="history-section">
<h4>历史记录</h4>
<div class="history-list" id="history-list"></div>
<div class="history-controls">
<button class="btn-small" id="undo-btn" disabled>
<i class="icon-undo"></i> 撤销
</button>
<button class="btn-small" id="redo-btn" disabled>
<i class="icon-redo"></i> 重做
</button>
</div>
</div>
</div>
</div>
`;
document.querySelector('.wpiet-editor-container').insertAdjacentHTML('beforeend', sidebarHTML);
}
bindEvents() {
// 工具栏按钮点击
document.querySelectorAll('.toolbar-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.currentTarget.dataset.action;
const tool = e.currentTarget.dataset.tool;
if (action) {
this.handleAction(action);
} else if (tool) {
this.activateTool(tool);
}
});
});
// 窗口大小变化
window.addEventListener('resize', () => {
this.adaptLayout();
});
// 键盘快捷键
document.addEventListener('keydown', (e) => {
this.handleKeyboardShortcuts(e);
});
// 触摸设备支持
if ('ontouchstart' in window) {
this.enableTouchSupport();
}
}
handleAction(action) {
switch(action) {
case 'open':
this.openImagePicker();
break;
case 'save':
this.saveImage();
break;
case 'export':
this.exportImage();
break;
case 'zoom-in':
this.editor.zoomIn();
break;
case 'zoom-out':
this.editor.zoomOut();
break;
case 'fullscreen':
this.toggleFullscreen();
break;
case 'toggle-sidebar':
this.toggleSidebar();
break;
}
}
activateTool(toolName) {
// 更新UI状态
this.uiState.activeTool = toolName;
// 更新工具栏按钮状态
document.querySelectorAll('.toolbar-btn').forEach(btn => {
