Realistic Vision V5.1 虚拟摄影棚:JavaScript交互式Prompt构建器开发指南

张开发
2026/4/10 8:59:57 15 分钟阅读

分享文章

Realistic Vision V5.1 虚拟摄影棚:JavaScript交互式Prompt构建器开发指南
Realistic Vision V5.1 虚拟摄影棚JavaScript交互式Prompt构建器开发指南你是不是也遇到过这样的烦恼用Realistic Vision V5.1这类强大的图像生成模型时脑子里有绝妙的画面但写出来的提示词Prompt总是差那么点意思。要么风格不对味要么光线太死板要么构图平平无奇。每次生成都要手动敲一大堆复杂的参数试错成本高灵感也容易被打断。今天咱们不聊怎么调参也不讲复杂的模型原理。我们来点更酷的——自己动手用最基础的JavaScript打造一个专属于你的“虚拟摄影棚”Prompt构建器。想象一下像搭积木一样通过拖拽几个标签就能组合出专业级的摄影参数像浏览历史记录一样一键复用之前成功的配方。这不仅能极大提升你的创作效率更能让你深入理解提示词工程背后的逻辑。这篇教程就是带你从零开始实现这个想法。即使你JavaScript刚入门跟着步骤走也能搞定。我们会聚焦三个核心功能可拖拽的标签组件、输入历史管理以及与后端AI绘图API的通信。让我们开始吧。1. 项目准备与环境搭建首先我们得把“摄影棚”的场地给准备好。这个项目只需要一个浏览器和一个代码编辑器比如VS Code、Sublime Text甚至记事本都行。我们创建一个简单的项目文件夹就叫ai-prompt-builder吧。在里面新建三个文件index.html网页的结构骨架。style.css让我们的构建器看起来更美观。script.js所有交互逻辑和魔法发生的地方。先来看看index.html的骨架结构!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleRealistic Vision 虚拟摄影棚 - Prompt构建器/title link relstylesheet hrefstyle.css link relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css /head body div classcontainer header h1i classfas fa-camera-retro/i Realistic Vision V5.1 虚拟摄影棚/h1 p classsubtitle像导演一样用可视化组件构建你的完美画面提示词。/p /header main section classbuilder-section h21. 摄影参数构建区/h2 !-- 这里将放置可拖拽的标签和画布 -- div classworkspace div classpalette idcomponentPalette !-- 预设的标签组件会通过JS动态添加到这里 -- /div div classcanvas idpromptCanvas p将右侧的标签拖拽到这里开始构建你的Prompt.../p /div /div /section section classcontrol-section h22. 最终Prompt与生成控制/h2 div classfinal-prompt-area textarea idfinalPrompt readonly placeholder组合好的Prompt将在这里显示.../textarea div classbutton-group button idgenerateBtni classfas fa-bolt/i 生成图像/button button idcopyBtni classfar fa-copy/i 复制Prompt/button button idclearBtni classfas fa-broom/i 清空画布/button /div /div /section section classhistory-section h23. 生成历史与配方库/h2 div classhistory-list idhistoryList !-- 历史记录会通过JS动态添加到这里 -- p classempty-hint暂无历史记录。生成一次图像后配方会保存在这里。/p /div /section /main /div script srcscript.js/script /body /html接下来给这个骨架穿上基础的衣服。在style.css里我们先写一些保证布局和基础样式的代码让界面不至于太难看* { box-sizing: border-box; margin: 0; padding: 0; font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); color: #333; line-height: 1.6; padding: 20px; min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; background-color: white; border-radius: 20px; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); overflow: hidden; padding: 30px; } header { text-align: center; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #eaeaea; } h1 { color: #2c3e50; margin-bottom: 10px; } .subtitle { color: #7f8c8d; font-size: 1.1em; } main section { margin-bottom: 40px; background: #f8f9fa; padding: 25px; border-radius: 15px; border: 1px solid #e9ecef; } h2 { color: #3498db; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px dashed #bdc3c7; } /* 构建区样式 */ .workspace { display: flex; gap: 30px; min-height: 400px; } .palette { flex: 1; background: white; border: 2px dashed #bdc3c7; border-radius: 10px; padding: 20px; display: flex; flex-direction: column; gap: 15px; } .canvas { flex: 2; background: white; border: 2px dashed #3498db; border-radius: 10px; padding: 25px; min-height: 350px; } /* 控制区样式 */ .final-prompt-area { display: flex; flex-direction: column; gap: 20px; } #finalPrompt { width: 100%; height: 150px; padding: 15px; border: 1px solid #ccc; border-radius: 8px; font-size: 16px; line-height: 1.5; resize: vertical; background-color: #fafafa; } .button-group { display: flex; gap: 15px; flex-wrap: wrap; } button { padding: 12px 25px; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; gap: 8px; } #generateBtn { background: linear-gradient(to right, #3498db, #2ecc71); color: white; } #copyBtn { background-color: #f39c12; color: white; } #clearBtn { background-color: #e74c3c; color: white; } button:hover { transform: translateY(-3px); box-shadow: 0 7px 14px rgba(0, 0, 0, 0.1); } /* 历史区样式 */ .history-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; } .empty-hint { text-align: center; color: #95a5a6; font-style: italic; grid-column: 1 / -1; padding: 40px; }好了场地和基础装修完毕。现在打开index.html你应该能看到一个结构清晰但还缺少灵魂的页面。接下来我们就要用JavaScript注入灵魂。2. 核心功能一可拖拽的标签组件我们的“虚拟摄影棚”里要有各种“摄影器材”比如“风格滤镜”、“灯光设备”、“构图道具”。我们用可拖拽的标签Tag来代表它们。首先在script.js的开头我们定义这些预设的组件。为了贴合Realistic Vision V5.1的特点我们分几个类别// 预设的Prompt组件库 const promptComponents { // 风格与画质 style: [ { id: style_photorealistic, text: 摄影写实风格, weight: 1.2 }, { id: style_cinematic, text: 电影感, weight: 1.1 }, { id: style_35mm, text: 35mm胶片质感, weight: 1.0 }, { id: style_high_details, text: 超高细节, weight: 1.3 }, ], // 光照与氛围 lighting: [ { id: light_dramatic, text: 戏剧性光线, weight: 1.1 }, { id: light_soft_window, text: 柔和窗光, weight: 1.0 }, { id: light_golden_hour, text: 黄金时刻, weight: 1.2 }, { id: light_neon_night, text: 霓虹夜景, weight: 1.1 }, ], // 构图与视角 composition: [ { id: comp_close_up, text: 特写镜头, weight: 1.0 }, { id: comp_wide_shot, text: 广角镜头, weight: 1.0 }, { id: comp_dutch_angle, text: 倾斜构图, weight: 0.9 }, { id: comp_rule_of_thirds, text: 三分法构图, weight: 1.0 }, ], // 主体描述 (示例) subject: [ { id: subj_portrait, text: 人物肖像, weight: 1.5 }, { id: subj_cyberpunk, text: 赛博朋克角色, weight: 1.3 }, { id: subj_fantasy_landscape, text: 奇幻风景, weight: 1.4 }, ] }; // 初始化函数将预设组件渲染到左侧面板 function initComponentPalette() { const palette document.getElementById(componentPalette); palette.innerHTML ; // 清空 for (const category in promptComponents) { // 创建分类标题 const categoryTitle document.createElement(h3); let displayName; switch(category) { case style: displayName 风格与画质; break; case lighting: displayName 光照与氛围; break; case composition: displayName 构图与视角; break; case subject: displayName 主体描述; break; default: displayName category; } categoryTitle.textContent displayName; palette.appendChild(categoryTitle); // 创建该分类下的所有标签 promptComponents[category].forEach(comp { const tag document.createElement(div); tag.className draggable-tag; tag.id comp.id; tag.draggable true; // 使其可拖拽 tag.dataset.text comp.text; tag.dataset.weight comp.weight; tag.innerHTML span${comp.text}/span small权重: ${comp.weight}/small ; // 为拖拽事件添加监听器 tag.addEventListener(dragstart, handleDragStart); palette.appendChild(tag); }); } } // 处理拖拽开始 function handleDragStart(event) { // 将被拖拽元素的id存入dataTransfer对象以便在放置时识别 event.dataTransfer.setData(text/plain, event.target.id); event.dataTransfer.effectAllowed move; // 可以添加一些视觉反馈比如改变透明度 event.target.style.opacity 0.6; } // 处理拖拽元素进入画布区域 function handleDragOver(event) { event.preventDefault(); // 必须阻止默认行为才能成为有效的放置目标 event.dataTransfer.dropEffect move; // 可以给画布加个高亮边框作为视觉反馈 event.currentTarget.style.borderColor #2ecc71; event.currentTarget.style.borderStyle solid; } // 处理拖拽离开画布区域 function handleDragLeave(event) { event.currentTarget.style.borderColor #3498db; event.currentTarget.style.borderStyle dashed; } // 处理放置事件 - 这是核心 function handleDrop(event) { event.preventDefault(); // 恢复画布边框样式 event.currentTarget.style.borderColor #3498db; event.currentTarget.style.borderStyle dashed; const id event.dataTransfer.getData(text/plain); const draggedElement document.getElementById(id); if (draggedElement !draggedElement.closest(#promptCanvas)) { // 如果拖拽的元素来自面板则在画布内创建一个它的副本 const tagClone draggedElement.cloneNode(true); tagClone.style.opacity 1; // 恢复透明度 tagClone.classList.add(in-canvas); // 添加一个类用于样式区分 tagClone.draggable true; // 副本也可拖拽用于在画布内重新排序 tagClone.addEventListener(dragstart, handleDragStart); // 为画布内的标签添加删除按钮 const deleteBtn document.createElement(span); deleteBtn.innerHTML i classfas fa-times/i; deleteBtn.className delete-tag; deleteBtn.onclick function() { tagClone.remove(); updateFinalPrompt(); // 删除后更新最终Prompt }; tagClone.appendChild(deleteBtn); event.currentTarget.appendChild(tagClone); // 移除画布内的初始提示文字 const hint event.currentTarget.querySelector(p); if (hint) hint.remove(); updateFinalPrompt(); // 每次放置后更新最终Prompt } // 恢复原始标签的样式 if (draggedElement) draggedElement.style.opacity 1; } // 初始化拖拽功能 function initDragAndDrop() { const canvas document.getElementById(promptCanvas); canvas.addEventListener(dragover, handleDragOver); canvas.addEventListener(dragleave, handleDragLeave); canvas.addEventListener(drop, handleDrop); }为了让拖拽体验更好我们还需要在style.css里补充一些样式.draggable-tag { background: linear-gradient(135deg, #74b9ff, #0984e3); color: white; padding: 12px 18px; border-radius: 50px; cursor: move; /* 鼠标显示为可移动 */ user-select: none; display: flex; justify-content: space-between; align-items: center; transition: all 0.2s ease; box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11); } .draggable-tag:hover { transform: translateY(-2px); box-shadow: 0 7px 14px rgba(50, 50, 93, 0.1); } .draggable-tag.in-canvas { background: linear-gradient(135deg, #00b894, #00a085); margin-bottom: 10px; position: relative; } .delete-tag { cursor: pointer; margin-left: 10px; opacity: 0.7; transition: opacity 0.2s; } .delete-tag:hover { opacity: 1; } .palette h3 { color: #2d3436; margin-top: 20px; margin-bottom: 10px; font-size: 1.1em; border-left: 4px solid #3498db; padding-left: 10px; }现在刷新页面你应该能看到左侧面板里分类排列的各种标签。尝试把它们拖到右边的蓝色虚线框里一个基本的拖拽功能就实现了标签被拖进画布后还会出现一个小叉号用于删除。3. 核心功能二动态Prompt生成与历史管理标签拖来拖去很酷但我们的目标是生成Realistic Vision能看懂的提示词。接下来我们要把画布里的标签组合成一个格式正确的Prompt字符串并实现历史记录功能。3.1 动态生成最终Prompt在script.js中继续添加// 更新最终Prompt显示区域 function updateFinalPrompt() { const canvas document.getElementById(promptCanvas); const tagsInCanvas canvas.querySelectorAll(.draggable-tag.in-canvas); const finalPromptTextarea document.getElementById(finalPrompt); if (tagsInCanvas.length 0) { finalPromptTextarea.value ; // 如果画布空了重新显示提示文字 if (!canvas.querySelector(p)) { const hint document.createElement(p); hint.textContent 将右侧的标签拖拽到这里开始构建你的Prompt...; canvas.appendChild(hint); } return; } // 构建Prompt字符串 let promptParts []; tagsInCanvas.forEach(tag { const text tag.dataset.text; const weight parseFloat(tag.dataset.weight); // 根据权重格式化例如权重1.2变成 (text:1.2) let formattedPart weight ! 1.0 ? (${text}:${weight.toFixed(1)}) : text; promptParts.push(formattedPart); }); // 用逗号和空格连接形成最终Prompt const finalPrompt promptParts.join(, ); finalPromptTextarea.value finalPrompt; }现在每当你拖入或删除一个标签updateFinalPrompt函数就会被调用下方的文本区域会实时显示组合好的提示词并且自动处理了权重语法比如(电影感:1.1)。3.2 实现历史记录本地存储成功生成一次图像后我们希望能保存这个“配方”方便以后一键复用。这里我们用浏览器的localStorage来实现。// 历史记录相关功能 const HISTORY_KEY ai_prompt_history; // 保存当前Prompt到历史记录 function saveToHistory(promptText, imageDataUrl null) { if (!promptText.trim()) return; let history getHistory(); const newEntry { id: Date.now(), // 用时间戳作为唯一ID prompt: promptText, timestamp: new Date().toLocaleString(zh-CN), preview: imageDataUrl // 可以存储生成图片的Base64预览图这里先留空 }; // 避免重复添加完全相同的最近记录 if (history.length 0 history[0].prompt promptText) { console.log(与最近记录相同不重复添加); return; } // 将新记录添加到数组开头 history.unshift(newEntry); // 只保留最近20条记录 if (history.length 20) { history history.slice(0, 20); } localStorage.setItem(HISTORY_KEY, JSON.stringify(history)); renderHistoryList(); } // 从localStorage获取历史记录 function getHistory() { const historyJson localStorage.getItem(HISTORY_KEY); return historyJson ? JSON.parse(historyJson) : []; } // 渲染历史记录列表 function renderHistoryList() { const historyListEl document.getElementById(historyList); const history getHistory(); if (history.length 0) { historyListEl.innerHTML p classempty-hint暂无历史记录。生成一次图像后配方会保存在这里。/p; return; } historyListEl.innerHTML ; // 清空现有内容 history.forEach(item { const historyItem document.createElement(div); historyItem.className history-item; historyItem.innerHTML div classhistory-prompt strong${item.prompt.substring(0, 80)}${item.prompt.length 80 ? ... : }/strong /div div classhistory-meta span classhistory-time${item.timestamp}/span div classhistory-actions button classbtn-apply>.history-item { background: white; border-radius: 10px; padding: 20px; border-left: 5px solid #3498db; box-shadow: 0 3px 10px rgba(0,0,0,0.08); transition: transform 0.3s ease; } .history-item:hover { transform: translateX(5px); } .history-prompt { margin-bottom: 15px; color: #2c3e50; line-height: 1.5; } .history-meta { display: flex; justify-content: space-between; align-items: center; font-size: 0.9em; color: #7f8c8d; } .history-actions { display: flex; gap: 10px; } .btn-apply, .btn-delete { padding: 6px 12px; font-size: 0.85em; border-radius: 5px; border: none; cursor: pointer; transition: background 0.2s; } .btn-apply { background-color: #2ecc71; color: white; } .btn-delete { background-color: #e74c3c; color: white; }4. 核心功能三与后端API通信及界面整合最后我们要把构建好的Prompt发送给后端的图像生成API例如Stable Diffusion的WebUI API并处理返回的结果。这里我们使用fetchAPI进行异步通信。4.1 模拟API调用与界面交互由于直接调用真实的AI绘图API需要后端服务本教程先实现一个模拟流程并预留好接口。在script.js中添加// 模拟或真实的图像生成函数 async function generateImage() { const finalPrompt document.getElementById(finalPrompt).value.trim(); if (!finalPrompt) { alert(请先构建Prompt); return; } const generateBtn document.getElementById(generateBtn); const originalText generateBtn.innerHTML; // 禁用按钮并显示加载中 generateBtn.disabled true; generateBtn.innerHTML i classfas fa-spinner fa-spin/i 生成中...; // 这里是你需要替换的部分调用真实的后端API // 示例假设你的后端API端点位于 /api/generate const apiUrl YOUR_BACKEND_API_ENDPOINT_HERE; // 替换为你的真实API地址 const requestBody { prompt: finalPrompt, negative_prompt: 低质量模糊畸形, // 可以扩展UI让用户输入负面提示词 steps: 20, cfg_scale: 7, width: 512, height: 768 }; try { // 真实调用示例 (注释掉) /* const response await fetch(apiUrl, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(requestBody) }); if (!response.ok) throw new Error(HTTP error! status: ${response.status}); const data await response.json(); // 假设返回 { image: base64_string } const imageUrl data:image/png;base64,${data.image}; */ // --- 模拟成功响应用于演示--- await new Promise(resolve setTimeout(resolve, 2000)); // 模拟2秒网络延迟 const imageUrl https://picsum.photos/512/768?random${Date.now()}; // 使用随机图片占位 // --- 模拟结束 --- // 1. 保存到历史记录 (暂时不存图片只存Prompt) saveToHistory(finalPrompt /*, imageUrl 可以存base64 */); // 2. 显示生成结果例如弹窗或新建一个展示区域 showGeneratedImage(imageUrl, finalPrompt); alert(图像生成成功已保存至历史记录。); } catch (error) { console.error(生成失败:, error); alert(生成图像时出错: ${error.message}); } finally { // 恢复按钮状态 generateBtn.disabled false; generateBtn.innerHTML originalText; } } // 展示生成的图片简单弹窗示例 function showGeneratedImage(imageUrl, prompt) { // 你可以设计更酷的展示方式这里用一个简单的弹窗 const overlay document.createElement(div); overlay.style.cssText position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.8); display:flex; justify-content:center; align-items:center; z-index:1000; ; const modal document.createElement(div); modal.style.cssText background: white; padding: 30px; border-radius: 15px; max-width: 90%; max-height: 90%; overflow: auto; text-align:center; ; modal.innerHTML h3生成结果预览/h3 pemPrompt: ${prompt.substring(0, 100)}.../em/p img src${imageUrl} stylemax-width:100%; max-height:400px; border-radius:10px; margin:20px 0; / div button idcloseModal stylepadding:10px 20px; background:#3498db; color:white; border:none; border-radius:5px; cursor:pointer;关闭/button /div ; overlay.appendChild(modal); document.body.appendChild(overlay); overlay.addEventListener(click, function(e) { if (e.target overlay || e.target.id closeModal) { document.body.removeChild(overlay); } }); } // 复制Prompt到剪贴板 function copyPromptToClipboard() { const finalPrompt document.getElementById(finalPrompt); finalPrompt.select(); finalPrompt.setSelectionRange(0, 99999); // 用于移动设备 try { document.execCommand(copy); // 给用户一个反馈 const originalText document.getElementById(copyBtn).innerHTML; document.getElementById(copyBtn).innerHTML i classfas fa-check/i 已复制; setTimeout(() { document.getElementById(copyBtn).innerHTML originalText; }, 2000); } catch (err) { console.error(复制失败:, err); alert(复制失败请手动选择文本复制。); } } // 清空画布 function clearCanvas() { if (!confirm(清空画布将丢失当前组合确定吗)) return; const canvas document.getElementById(promptCanvas); canvas.innerHTML p将右侧的标签拖拽到这里开始构建你的Prompt.../p; document.getElementById(finalPrompt).value ; }4.2 绑定所有事件并初始化最后我们需要一个初始化函数在页面加载时把所有的功能“焊接”起来。// 页面加载完成后初始化一切 function initApp() { // 1. 初始化组件面板 initComponentPalette(); // 2. 初始化拖拽功能 initDragAndDrop(); // 3. 渲染历史记录 renderHistoryList(); // 4. 绑定按钮事件 document.getElementById(generateBtn).addEventListener(click, generateImage); document.getElementById(copyBtn).addEventListener(click, copyPromptToClipboard); document.getElementById(clearBtn).addEventListener(click, clearCanvas); // 5. 可选可以添加一个清空所有历史的按钮 // const clearHistoryBtn document.createElement(button); // clearHistoryBtn.textContent 清空所有历史; // clearHistoryBtn.addEventListener(click, clearAllHistory); // document.querySelector(.history-section).appendChild(clearHistoryBtn); } // 当DOM内容加载完毕时运行初始化 document.addEventListener(DOMContentLoaded, initApp);大功告成现在你的虚拟摄影棚已经具备了所有核心功能拖拽构建从左侧选择风格、光照等标签拖到右侧画布组合。实时预览下方文本区实时显示格式化后的Prompt。历史记忆每次“生成”后Prompt配方会自动保存可以一键复用或删除。API通信点击“生成图像”按钮会尝试调用后端API示例中为模拟。5. 总结与下一步跟着走完这一趟你应该已经拥有了一个完全在浏览器里运行、功能完整的Realistic Vision V5.1 Prompt可视化构建器。它虽然界面简单但核心逻辑——拖拽交互、状态管理、本地存储、网络通信——都已经实现。实际使用时你需要把generateImage函数里的模拟部分替换成对你真实后端AI绘图API比如Automatic1111的WebUI API、ComfyUI API等的调用。你可能还需要增加更多参数控件如负面提示词输入框、图片尺寸选择、采样步数滑块等让这个“摄影棚”的控制台更加专业。这个项目的价值不仅仅在于工具本身更在于开发过程。你亲手实践了如何用原生JavaScript处理DOM操作、拖拽API、本地存储和异步请求这些都是前端开发中非常实用的技能。希望这个“虚拟摄影棚”能成为你探索AI绘画的好帮手也为你自己的项目开发打开一扇窗。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章