Qwen-Image-Edit前端集成:JavaScript实现实时编辑预览
Qwen-Image-Edit前端集成:JavaScript实现实时编辑预览
1. 为什么需要在浏览器里直接编辑图片
你有没有遇到过这样的场景:设计同事发来一张海报,需要把上面的活动时间从"8月15日"改成"9月20日",但你手边没有Photoshop,临时装个专业软件又太麻烦?或者电商运营要批量修改几十张商品图的文字信息,每张都得打开编辑软件、找字体、调大小、对齐位置,一上午就过去了。
Qwen-Image-Edit这个模型真正解决了这类问题——它能让AI理解你的自然语言指令,直接在图片上做精准修改。但光有强大的后端模型还不够,如果每次都要上传图片、等待API响应、再下载结果,体验就大打折扣了。真正的效率提升,应该发生在用户点击"修改"按钮的那一刻,就能看到实时预览效果。
这就是我们今天要做的:用纯JavaScript把Qwen-Image-Edit集成到网页中,让用户拖拽一张图片,输入一句"把标题改成'秋季特惠',字体变大加粗",就能立刻看到修改后的效果。整个过程不需要跳转页面,不依赖复杂环境,打开浏览器就能用。
整个方案的核心思路很朴素:前端负责友好的交互和即时反馈,后端提供稳定可靠的图像处理能力,两者通过标准API无缝协作。下面我们就一步步实现这个目标。
2. 前端环境准备与基础架构
2.1 创建最小可行项目结构
首先,我们创建一个极简的HTML页面作为起点。不需要任何构建工具或框架,纯原生JavaScript就能搞定:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Qwen-Image-Edit在线编辑器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Segoe UI", system-ui, sans-serif; line-height: 1.6; color: #333; background: #f8f9fa; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.editor-section { display: flex; gap: 20px; margin-bottom: 30px; flex-wrap: wrap; }
.panel { flex: 1; min-width: 300px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); overflow: hidden; }
.panel-header { padding: 16px 20px; background: #4a6fa5; color: white; font-weight: 600; }
.panel-body { padding: 20px; }
.drop-area { border: 2px dashed #4a6fa5; border-radius: 6px; padding: 40px 20px; text-align: center; cursor: pointer; transition: all 0.3s; }
.drop-area:hover, .drop-area.dragover { background: #eef3ff; border-color: #2c5282; }
.btn { background: #4a6fa5; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn:hover { background: #2c5282; }
.btn:disabled { background: #a0aec0; cursor: not-allowed; }
.image-preview { max-width: 100%; height: auto; border-radius: 4px; margin-top: 15px; }
.input-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 6px; font-weight: 500; }
input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
textarea { min-height: 100px; resize: vertical; }
.result-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.result-item { background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.result-header { padding: 12px 16px; background: #f1f5f9; font-size: 14px; color: #4a5568; }
.result-content { padding: 16px; }
.loading { text-align: center; padding: 20px; color: #4a5568; }
.spinner { border: 3px solid #f3f3f3; border-top: 3px solid #4a6fa5; border-radius: 50%; width: 20px; height: 20px; animation: spin 1s linear infinite; margin: 0 auto 10px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="container">
<h1>Qwen-Image-Edit在线编辑器</h1>
<p>用自然语言指令编辑图片,无需安装任何软件</p>
<div class="editor-section">
<div class="panel">
<div class="panel-header">原始图片</div>
<div class="panel-body">
<div class="drop-area" id="dropArea">
<p> 拖拽图片到这里</p>
<p>或点击选择文件</p>
<input type="file" id="fileInput" accept="image/*" style="display: none;">
<button class="btn" id="browseBtn">选择图片</button>
</div>
<img id="originalImage" class="image-preview" style="display: none;" alt="原始图片">
</div>
</div>
<div class="panel">
<div class="panel-header">编辑设置</div>
<div class="panel-body">
<div class="input-group">
<label for="promptInput">编辑指令</label>
<textarea id="promptInput" placeholder="例如:把标题文字改为'新品上市',背景换成蓝色渐变,添加公司logo水印">把标题文字改为'秋季特惠',字体变大加粗,背景换成浅灰色</textarea>
</div>
<div class="input-group">
<label for="outputSize">输出尺寸</label>
<select id="outputSize">
<option value="1024x1024">1024×1024(正方形)</option>
<option value="1024x1536">1024×1536(竖版)</option>
<option value="1536x1024">1536×1024(横版)</option>
<option value="original">保持原图尺寸</option>
</select>
</div>
<button class="btn" id="editBtn">开始编辑</button>
</div>
</div>
</div>
<div class="panel">
<div class="panel-header">编辑结果</div>
<div class="panel-body">
<div id="resultContainer" class="result-grid">
<div class="result-item">
<div class="result-header">处理中...</div>
<div class="result-content">
<div class="loading">
<div class="spinner"></div>
<p>正在调用AI模型处理图片,请稍候</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 我们将在下面逐步添加JavaScript代码
</script>
</body>
</html>
这个HTML文件已经包含了完整的UI结构:左侧是图片上传区域,中间是编辑指令输入框,右侧是结果展示区。所有样式都是内联的,确保复制粘贴就能运行,不需要额外的CSS文件。
2.2 图片上传与预览功能实现
接下来,我们为图片上传区域添加交互逻辑。这段代码会处理用户拖拽图片、点击选择、以及图片预览:
// 获取DOM元素
const dropArea = document.getElementById('dropArea');
const fileInput = document.getElementById('fileInput');
const browseBtn = document.getElementById('browseBtn');
const originalImage = document.getElementById('originalImage');
const promptInput = document.getElementById('promptInput');
const editBtn = document.getElementById('editBtn');
const resultContainer = document.getElementById('resultContainer');
// 处理文件选择
function handleFileSelect(file) {
if (!file || !file.type.match('image.*')) {
alert('请选择一张图片文件');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
originalImage.src = e.target.result;
originalImage.style.display = 'block';
// 保存原始图片数据供后续使用
window.originalImageData = {
file: file,
url: e.target.result,
name: file.name
};
};
reader.readAsDataURL(file);
}
// 拖拽事件处理
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropArea.classList.add('dragover');
}
function unhighlight() {
dropArea.classList.remove('dragover');
}
// 处理拖拽放置
dropArea.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length) {
handleFileSelect(files[0]);
}
}
// 处理点击选择
browseBtn.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleFileSelect(e.target.files[0]);
}
});
// 初始化时设置默认提示词
promptInput.value = "把标题文字改为'秋季特惠',字体变大加粗,背景换成浅灰色";
这段代码实现了完整的图片上传流程:
- 支持拖拽图片到指定区域
- 支持点击按钮选择文件
- 实时预览上传的图片
- 自动保存图片元数据供后续处理
现在你可以保存这个HTML文件,在浏览器中打开,拖拽一张图片进去,就能看到即时预览效果了。
3. JavaScript与Qwen-Image-Edit API的对接
3.1 API调用封装与错误处理
Qwen-Image-Edit提供了标准的REST API接口,我们需要用JavaScript封装一个安全可靠的调用方法。这里的关键是处理各种可能的错误情况,给用户提供清晰的反馈:
// API配置 - 这里使用阿里云百炼平台的API端点
const API_CONFIG = {
baseUrl: 'https://dashscope.aliyuncs.com/api/v1',
model: 'qwen-image-edit-plus',
timeout: 300000 // 5分钟超时,因为图像处理需要时间
};
// API调用函数
async function callQwenImageEditAPI(imageData, prompt, size = '1024x1024') {
// 验证必要参数
if (!imageData || !imageData.url || !prompt.trim()) {
throw new Error('缺少必要的参数:图片或编辑指令');
}
// 构建请求体
const requestBody = {
model: API_CONFIG.model,
input: {
messages: [
{
role: "user",
content: [
{ "image": imageData.url },
{ "text": prompt }
]
}
]
},
parameters: {
n: 1,
watermark: false,
negative_prompt: "",
prompt_extend: true,
size: size
}
};
try {
// 检查API密钥是否已配置
const apiKey = getApiKey();
if (!apiKey) {
throw new Error('未配置API密钥,请在控制台设置DASHSCOPE_API_KEY环境变量,或在代码中配置');
}
const response = await fetch(`${API_CONFIG.baseUrl}/services/aigc/multimodal-generation/generation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify(requestBody),
signal: AbortSignal.timeout(API_CONFIG.timeout)
});
if (!response.ok) {
const errorData = await response.json();
let errorMessage = `API请求失败:${response.status} ${response.statusText}`;
if (errorData.message) {
errorMessage += ` - ${errorData.message}`;
}
throw new Error(errorMessage);
}
const result = await response.json();
// 验证响应格式
if (!result.output || !result.output.choices || result.output.choices.length === 0) {
throw new Error('API响应格式异常:缺少有效输出');
}
const imageUrls = [];
const content = result.output.choices[0].message.content;
if (Array.isArray(content)) {
content.forEach(item => {
if (item.image && typeof item.image === 'string') {
imageUrls.push(item.image);
}
});
}
if (imageUrls.length === 0) {
throw new Error('API返回了空的图片URL列表');
}
return {
success: true,
imageUrls: imageUrls,
requestId: result.request_id || 'unknown',
usage: result.usage || {}
};
} catch (error) {
// 处理不同类型的错误
if (error.name === 'AbortError') {
throw new Error('请求超时:图像处理时间过长,请尝试简化编辑指令或降低输出尺寸');
}
if (error.message.includes('401')) {
throw new Error('认证失败:请检查API密钥是否正确且未过期');
}
if (error.message.includes('403')) {
throw new Error('权限不足:当前API密钥没有调用Qwen-Image-Edit的权限');
}
if (error.message.includes('429')) {
throw new Error('请求过于频繁:请稍后再试,或检查API配额');
}
throw error;
}
}
// 获取API密钥的辅助函数
function getApiKey() {
// 首先尝试从环境变量获取(Node.js环境)
if (typeof process !== 'undefined' && process.env) {
return process.env.DASHSCOPE_API_KEY;
}
// 浏览器环境中,可以从localStorage或全局变量获取
// 注意:生产环境中不应在前端存储敏感密钥,这里仅为演示
const apiKey = localStorage.getItem('DASHSCOPE_API_KEY') ||
window.DASHSCOPE_API_KEY ||
'';
return apiKey;
}
// 设置API密钥的辅助函数
function setApiKey(key) {
if (key && key.trim()) {
localStorage.setItem('DASHSCOPE_API_KEY', key.trim());
return true;
}
return false;
}
这个API封装函数包含了企业级应用所需的关键特性:
- 完整的错误处理和用户友好的错误消息
- 请求超时控制,避免界面长时间无响应
- 参数验证,防止无效请求
- 响应格式验证,确保数据完整性
- 灵活的API密钥管理方式
3.2 前端API密钥管理方案
在生产环境中,直接在前端暴露API密钥存在安全风险。但为了教学演示的目的,我们提供一个安全的密钥管理方案:
// 在页面加载完成后初始化密钥管理
document.addEventListener('DOMContentLoaded', () => {
// 检查是否有已保存的密钥
const savedKey = localStorage.getItem('DASHSCOPE_API_KEY');
if (savedKey) {
document.getElementById('apiKeyStatus').textContent = ' API密钥已配置';
document.getElementById('apiKeyStatus').style.color = '#38a169';
} else {
document.getElementById('apiKeyStatus').textContent = ' 请先配置API密钥';
document.getElementById('apiKeyStatus').style.color = '#e53e3e';
}
});
// 添加API密钥配置弹窗
function showApiKeyModal() {
const modalHtml = `
<div id="apiKeyModal" style="
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); display: flex; align-items: center;
justify-content: center; z-index: 1000;
">
<div style="
background: white; padding: 24px; border-radius: 8px;
width: 90%; max-width: 500px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
">
<h3 style="margin-bottom: 16px;">配置API密钥</h3>
<p style="margin-bottom: 16px; color: #4a5568;">
请从阿里云百炼平台获取您的API密钥,然后粘贴到下方输入框中。
</p>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 6px; font-weight: 500;">API密钥</label>
<input type="password" id="apiKeyInput"
style="width: 100%; padding: 10px; border: 1px solid #ddd;
border-radius: 4px; font-size: 14px;"
placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button onclick="closeApiKeyModal()"
style="padding: 8px 16px; background: #e2e8f0; border: none;
border-radius: 4px; cursor: pointer;">取消</button>
<button onclick="saveApiKey()"
style="padding: 8px 16px; background: #4a6fa5; color: white;
border: none; border-radius: 4px; cursor: pointer;">保存</button>
</div>
<p style="margin-top: 16px; font-size: 12px; color: #718096;">
提示:API密钥仅保存在您当前浏览器的本地存储中,不会发送到任何服务器。
</p>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
}
function closeApiKeyModal() {
const modal = document.getElementById('apiKeyModal');
if (modal) modal.remove();
}
function saveApiKey() {
const input = document.getElementById('apiKeyInput');
const key = input.value.trim();
if (!key) {
alert('API密钥不能为空');
return;
}
if (setApiKey(key)) {
alert('API密钥保存成功!');
document.getElementById('apiKeyStatus').textContent = ' API密钥已配置';
document.getElementById('apiKeyStatus').style.color = '#38a169';
closeApiKeyModal();
} else {
alert('保存API密钥失败,请重试');
}
}
// 在编辑按钮点击事件中添加密钥检查
editBtn.addEventListener('click', async () => {
const apiKey = getApiKey();
if (!apiKey) {
showApiKeyModal();
return;
}
// 继续执行编辑逻辑...
});
这个密钥管理方案既保证了教学演示的可行性,又提醒了用户生产环境中的安全最佳实践。
4. 实时编辑预览功能实现
4.1 编辑流程状态管理
为了让用户清楚地了解当前处理状态,我们需要实现一个完整的工作流状态管理系统:
// 编辑状态枚举
const EDIT_STATUS = {
IDLE: 'idle',
UPLOADING: 'uploading',
PROCESSING: 'processing',
SUCCESS: 'success',
ERROR: 'error'
};
// 当前编辑状态
let currentStatus = EDIT_STATUS.IDLE;
// 状态更新函数
function updateStatus(status, message = '') {
currentStatus = status;
// 更新UI状态
switch(status) {
case EDIT_STATUS.IDLE:
editBtn.disabled = false;
editBtn.textContent = '开始编辑';
break;
case EDIT_STATUS.UPLOADING:
editBtn.disabled = true;
editBtn.textContent = '上传中...';
break;
case EDIT_STATUS.PROCESSING:
editBtn.disabled = true;
editBtn.textContent = '处理中...';
break;
case EDIT_STATUS.SUCCESS:
editBtn.disabled = false;
editBtn.textContent = '编辑完成';
break;
case EDIT_STATUS.ERROR:
editBtn.disabled = false;
editBtn.textContent = '重新编辑';
break;
}
// 显示状态消息
if (message) {
showStatusMessage(message, status === EDIT_STATUS.ERROR ? 'error' : 'info');
}
}
// 显示状态消息的辅助函数
function showStatusMessage(message, type = 'info') {
// 创建临时消息元素
const messageEl = document.createElement('div');
messageEl.className = 'status-message';
messageEl.style.cssText = `
position: fixed; top: 20px; right: 20px;
padding: 12px 20px; border-radius: 4px;
color: white; font-size: 14px;
z-index: 1001;
transform: translateX(120%);
transition: transform 0.3s ease-out;
`;
if (type === 'error') {
messageEl.style.background = '#e53e3e';
messageEl.style.boxShadow = '0 2px 8px rgba(229, 62, 62, 0.3)';
} else {
messageEl.style.background = '#4a6fa5';
messageEl.style.boxShadow = '0 2px 8px rgba(74, 111, 165, 0.3)';
}
messageEl.textContent = message;
document.body.appendChild(messageEl);
// 动画显示
setTimeout(() => {
messageEl.style.transform = 'translateX(0)';
}, 10);
// 自动隐藏
setTimeout(() => {
messageEl.style.transform = 'translateX(120%)';
setTimeout(() => {
if (messageEl.parentNode) {
messageEl.parentNode.removeChild(messageEl);
}
}, 300);
}, 3000);
}
这个状态管理系统为整个编辑流程提供了清晰的用户反馈,避免了用户在等待时的焦虑感。
4.2 核心编辑功能实现
现在我们把所有组件组合起来,实现核心的编辑功能:
// 主编辑函数
async function performImageEdit() {
// 验证前置条件
if (currentStatus !== EDIT_STATUS.IDLE) return;
if (!window.originalImageData) {
updateStatus(EDIT_STATUS.ERROR, '请先上传一张图片');
return;
}
const prompt = promptInput.value.trim();
if (!prompt) {
updateStatus(EDIT_STATUS.ERROR, '请输入编辑指令');
return;
}
const sizeSelect = document.getElementById('outputSize');
const outputSize = sizeSelect.value === 'original'
? `${window.originalImageData.file.width || 1024}x${window.originalImageData.file.height || 1024}`
: sizeSelect.value;
try {
updateStatus(EDIT_STATUS.PROCESSING, '正在调用AI模型处理图片...');
// 调用API
const result = await callQwenImageEditAPI(
window.originalImageData,
prompt,
outputSize
);
// 处理成功结果
updateStatus(EDIT_STATUS.SUCCESS, '图片编辑完成!');
// 渲染结果
renderResults(result);
} catch (error) {
console.error('编辑失败:', error);
updateStatus(EDIT_STATUS.ERROR, error.message || '图片编辑失败,请重试');
}
}
// 渲染结果的函数
function renderResults(result) {
// 清空结果容器
resultContainer.innerHTML = '';
// 显示原始图片对比
const originalItem = document.createElement('div');
originalItem.className = 'result-item';
originalItem.innerHTML = `
<div class="result-header">原始图片</div>
<div class="result-content">
<img src="${window.originalImageData.url}"
alt="原始图片"
class="image-preview"
style="max-width: 100%; border-radius: 4px;">
</div>
`;
resultContainer.appendChild(originalItem);
// 显示编辑结果
result.imageUrls.forEach((url, index) => {
const resultItem = document.createElement('div');
resultItem.className = 'result-item';
resultItem.innerHTML = `
<div class="result-header">编辑结果 #${index + 1}</div>
<div class="result-content">
<img src="${url}"
alt="编辑结果"
class="image-preview"
style="max-width: 100%; border-radius: 4px;">
<div style="margin-top: 12px; display: flex; gap: 8px;">
<button class="btn" style="flex: 1; padding: 6px 12px; font-size: 12px;"
onclick="downloadImage('${url}', 'edited_image_${index + 1}.png')">下载</button>
<button class="btn" style="flex: 1; padding: 6px 12px; font-size: 12px; background: #38a169;"
onclick="compareWithOriginal('${url}')">对比</button>
</div>
</div>
`;
resultContainer.appendChild(resultItem);
});
// 添加使用统计信息
const statsItem = document.createElement('div');
statsItem.className = 'result-item';
statsItem.innerHTML = `
<div class="result-header">处理信息</div>
<div class="result-content">
<p><strong>请求ID:</strong>${result.requestId}</p>
<p><strong>处理时间:</strong>${new Date().toLocaleTimeString()}</p>
<p><strong>输出尺寸:</strong>${result.usage.width || '1024'}×${result.usage.height || '1024'}</p>
<p><strong>图片数量:</strong>${result.imageUrls.length}</p>
</div>
`;
resultContainer.appendChild(statsItem);
}
// 下载图片的辅助函数
function downloadImage(url, filename) {
fetch(url)
.then(response => response.blob())
.then(blob => {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(error => {
console.error('下载失败:', error);
showStatusMessage('图片下载失败,请重试', 'error');
});
}
// 对比功能
function compareWithOriginal(editedUrl) {
// 创建对比视图
const compareHtml = `
<div id="compareModal" style="
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8); display: flex; align-items: center;
justify-content: center; z-index: 1000; padding: 20px;
">
<div style="
background: white; border-radius: 8px; width: 100%; max-width: 1200px;
max-height: 90vh; overflow: hidden; display: flex; flex-direction: column;
">
<div style="
padding: 16px 20px; background: #4a6fa5; color: white;
display: flex; justify-content: space-between; align-items: center;
">
<h3>图片对比视图</h3>
<button onclick="closeCompareModal()"
style="background: none; border: none; color: white; font-size: 18px; cursor: pointer;">✕</button>
</div>
<div style="display: flex; flex: 1; overflow: auto;">
<div style="flex: 1; padding: 20px; border-right: 1px solid #eee;">
<h4 style="margin-bottom: 12px; color: #4a6fa5;">原始图片</h4>
<img src="${window.originalImageData.url}"
alt="原始图片"
style="max-width: 100%; max-height: 60vh; object-fit: contain;">
</div>
<div style="flex: 1; padding: 20px;">
<h4 style="margin-bottom: 12px; color: #4a6fa5;">编辑结果</h4>
<img src="${editedUrl}"
alt="编辑结果"
style="max-width: 100%; max-height: 60vh; object-fit: contain;">
</div>
</div>
<div style="padding: 16px 20px; background: #f8f9fa; text-align: center;">
<p style="color: #4a5568; margin: 0;">拖动图片可查看细节,滚动鼠标可缩放</p>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', compareHtml);
// 添加图片缩放功能
const images = document.querySelectorAll('#compareModal img');
images.forEach(img => {
img.style.cursor = 'zoom-in';
img.addEventListener('click', function() {
this.style.transform = this.style.transform === 'scale(2)' ? 'scale(1)' : 'scale(2)';
this.style.transition = 'transform 0.3s ease';
});
});
}
function closeCompareModal() {
const modal = document.getElementById('compareModal');
if (modal) modal.remove();
}
// 绑定事件监听器
editBtn.addEventListener('click', performImageEdit);
// 添加键盘快捷键支持
document.addEventListener('keydown', (e) => {
// Ctrl+Enter 或 Cmd+Enter 快速编辑
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
performImageEdit();
}
});
// 页面卸载前清理
window.addEventListener('beforeunload', () => {
// 清理可能的内存引用
if (window.originalImageData && window.originalImageData.url) {
URL.revokeObjectURL(window.originalImageData.url);
}
});
这个核心编辑功能实现了完整的用户体验闭环:
- 键盘快捷键支持(Ctrl+Enter快速编辑)
- 图片对比功能,方便用户直观评估编辑效果
- 下载功能,让用户可以保存结果
- 详细的处理信息展示
- 响应式的图片预览和缩放
5. 实用技巧与进阶功能
5.1 提示词优化建议
Qwen-Image-Edit对自然语言指令的理解能力很强,但好的提示词能让效果更上一层楼。我们在前端添加了一个智能提示词助手:
// 提示词模板库
const PROMPT_TEMPLATES = {
textEdit: [
"把图片中的文字'{{oldText}}'改为'{{newText}}',保持原有字体和风格",
"修改标题文字为'{{newText}}',字体加大加粗,颜色改为深蓝色",
"删除图片中的所有文字,只保留背景和图像内容"
],
background: [
"把背景替换为{{style}}风格的{{scene}},保持主体人物不变",
"将背景改为纯色{{color}},添加轻微模糊效果",
"把背景换成{{location}}场景,保持真实感和光影一致性"
],
style: [
"将图片转换为{{style}}艺术风格,如梵高、毕加索或水墨画",
"增强图片的{{effect}}效果,如胶片颗粒、柔焦或HDR",
"调整图片色调为{{tone}},使整体氛围更{{mood}}"
],
enhancement: [
"提高图片分辨率至4K,增强细节和清晰度",
"修复图片中的划痕和噪点,使画面更干净",
"调整图片亮度和对比度,使主体更突出"
]
};
// 提示词生成器
function generatePrompt(templateType, options = {}) {
const templates = PROMPT_TEMPLATES[templateType] || [];
if (templates.length === 0) return '';
const template = templates[Math.floor(Math.random() * templates.length)];
// 替换占位符
let result = template;
Object.keys(options).forEach(key => {
const regex = new RegExp(`{{${key}}}`, 'g');
result = result.replace(regex, options[key]);
});
return result;
}
// 在页面中添加提示词助手
function addPromptHelper() {
const helperHtml = `
<div style="
margin-top: 15px; padding: 12px; background: #ebf8ff;
border-left: 4px solid #4a6fa5; border-radius: 0 4px 4px 0;
">
<h4 style="margin-bottom: 8px; color: #2c5282;"> 提示词小贴士</h4>
<ul style="margin: 0; padding-left: 20px; color: #4a5568;">
<li>尽量具体:说"把标题改为'秋季特惠'"比"修改文字"效果更好</li>
<li>描述细节:加上"字体加大加粗"、"颜色改为深蓝色"等要求</li>
<li>保持简洁:一句话说明核心需求,避免过多修饰词</li>
<li>参考示例:<em>"把LOGO换成新设计的蓝色圆形图标,位置移到右上角"</em></li>
</ul>
</div>
`;
const promptGroup = document.querySelector('.input-group:nth-child(1)');
if (promptGroup) {
promptGroup.insertAdjacentHTML('afterend', helperHtml);
}
}
// 添加常用提示词按钮
function addQuickPromptButtons() {
const promptGroup = document.querySelector('.input-group:nth-child(1)');
if (!promptGroup)更多推荐




所有评论(0)