711 lines
20 KiB
Markdown
711 lines
20 KiB
Markdown
|
|
# 海报Fabric.js前端对接文档
|
|||
|
|
|
|||
|
|
## 📋 概述
|
|||
|
|
|
|||
|
|
本文档详细说明如何在前端使用新的海报生成API返回的Fabric.js JSON数据,包括图层加载、图片替换、图层管理等功能。
|
|||
|
|
|
|||
|
|
## 🔄 核心变化
|
|||
|
|
|
|||
|
|
### **从PSD到Fabric.js JSON**
|
|||
|
|
- **原先**: 返回PSD文件的base64编码
|
|||
|
|
- **现在**: 返回Fabric.js JSON文件的base64编码 + 原始JSON数据
|
|||
|
|
- **优势**: 可以直接在前端使用,支持实时编辑和图片替换
|
|||
|
|
|
|||
|
|
## 🎯 API接口说明
|
|||
|
|
|
|||
|
|
### 1. **生成海报接口**
|
|||
|
|
|
|||
|
|
#### **请求示例**
|
|||
|
|
```javascript
|
|||
|
|
const generatePosterRequest = {
|
|||
|
|
posterNumber: 1,
|
|||
|
|
posters: [{
|
|||
|
|
templateId: "vibrant",
|
|||
|
|
imagesBase64: "图片ID列表",
|
|||
|
|
contentId: "内容ID",
|
|||
|
|
productId: "产品ID",
|
|||
|
|
generatePsd: true, // 现在实际生成JSON
|
|||
|
|
numVariations: 1
|
|||
|
|
}]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const response = await fetch('/poster/generate', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/json'},
|
|||
|
|
body: JSON.stringify(generatePosterRequest)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### **响应结构**
|
|||
|
|
```javascript
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"message": "操作成功",
|
|||
|
|
"data": [{
|
|||
|
|
"requestId": "poster-20250104-123456",
|
|||
|
|
"templateId": "vibrant",
|
|||
|
|
"resultImagesBase64": [{
|
|||
|
|
"id": "vibrant_v1",
|
|||
|
|
"image": "base64编码的PNG图片",
|
|||
|
|
"format": "PNG",
|
|||
|
|
"size": [1350, 1800]
|
|||
|
|
}],
|
|||
|
|
"psdFiles": [{ // 现在是JSON文件
|
|||
|
|
"id": "vibrant_v1_json",
|
|||
|
|
"filename": "template_fabric_v1_20250104.json",
|
|||
|
|
"data": "base64编码的JSON文件",
|
|||
|
|
"size": 15672,
|
|||
|
|
"format": "JSON",
|
|||
|
|
"jsonData": { // 原始JSON数据,可直接使用
|
|||
|
|
"version": "5.3.0",
|
|||
|
|
"objects": [...],
|
|||
|
|
"background": "white",
|
|||
|
|
"width": 1350,
|
|||
|
|
"height": 1800
|
|||
|
|
}
|
|||
|
|
}]
|
|||
|
|
}]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. **获取Fabric.js JSON数据**
|
|||
|
|
```javascript
|
|||
|
|
// 获取指定海报的JSON数据
|
|||
|
|
const fabricJson = await fetch(`/poster/fabric-json/${posterId}`)
|
|||
|
|
.then(res => res.json())
|
|||
|
|
.then(data => data.data);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. **替换底层图片**
|
|||
|
|
```javascript
|
|||
|
|
// 单个图片替换
|
|||
|
|
const replaceImage = async (posterId, newImageBase64) => {
|
|||
|
|
const formData = new FormData();
|
|||
|
|
formData.append('posterId', posterId);
|
|||
|
|
formData.append('newImageBase64', newImageBase64);
|
|||
|
|
|
|||
|
|
const response = await fetch('/poster/replace-image', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: formData
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return response.json();
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. **获取图层信息**
|
|||
|
|
```javascript
|
|||
|
|
// 获取图层管理信息
|
|||
|
|
const layers = await fetch(`/poster/layers/${posterId}`)
|
|||
|
|
.then(res => res.json())
|
|||
|
|
.then(data => data.data);
|
|||
|
|
|
|||
|
|
// 图层信息结构
|
|||
|
|
// [{
|
|||
|
|
// "name": "图片层",
|
|||
|
|
// "type": "image",
|
|||
|
|
// "level": 0,
|
|||
|
|
// "visible": true,
|
|||
|
|
// "selectable": true,
|
|||
|
|
// "replaceable": true
|
|||
|
|
// }]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🛠️ 前端集成实现
|
|||
|
|
|
|||
|
|
### 1. **基础环境准备**
|
|||
|
|
|
|||
|
|
#### **HTML结构**
|
|||
|
|
```html
|
|||
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
<head>
|
|||
|
|
<meta charset="utf-8">
|
|||
|
|
<title>海报编辑器</title>
|
|||
|
|
<script src="https://cdn.jsdelivr.net/npm/fabric@5.3.0/dist/fabric.min.js"></script>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div id="poster-editor">
|
|||
|
|
<!-- 画布容器 -->
|
|||
|
|
<div class="canvas-container">
|
|||
|
|
<canvas id="poster-canvas" width="1350" height="1800"></canvas>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 图层管理面板 -->
|
|||
|
|
<div class="layer-panel">
|
|||
|
|
<h3>图层管理</h3>
|
|||
|
|
<div id="layer-list"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 图片替换面板 -->
|
|||
|
|
<div class="image-replace-panel">
|
|||
|
|
<h3>图片替换</h3>
|
|||
|
|
<input type="file" id="image-upload" accept="image/*">
|
|||
|
|
<button id="replace-btn">替换底层图片</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### **CSS样式**
|
|||
|
|
```css
|
|||
|
|
#poster-editor {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 20px;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.canvas-container {
|
|||
|
|
flex: 1;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 10px;
|
|||
|
|
background: #f5f5f5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.layer-panel, .image-replace-panel {
|
|||
|
|
width: 250px;
|
|||
|
|
border: 1px solid #ddd;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
padding: 15px;
|
|||
|
|
background: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.layer-item {
|
|||
|
|
padding: 8px;
|
|||
|
|
margin: 5px 0;
|
|||
|
|
border: 1px solid #eee;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.layer-item:hover {
|
|||
|
|
background: #f0f0f0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.layer-item.active {
|
|||
|
|
background: #e3f2fd;
|
|||
|
|
border-color: #2196f3;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. **JavaScript核心实现**
|
|||
|
|
|
|||
|
|
#### **初始化画布**
|
|||
|
|
```javascript
|
|||
|
|
class PosterEditor {
|
|||
|
|
constructor(canvasId) {
|
|||
|
|
this.canvas = new fabric.Canvas(canvasId, {
|
|||
|
|
preserveObjectStacking: true,
|
|||
|
|
selection: true
|
|||
|
|
});
|
|||
|
|
this.currentPosterId = null;
|
|||
|
|
this.layers = [];
|
|||
|
|
|
|||
|
|
this.initEventListeners();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载海报JSON数据
|
|||
|
|
async loadPosterFromJson(fabricJsonData, posterId) {
|
|||
|
|
try {
|
|||
|
|
console.log('开始加载Fabric.js JSON数据...');
|
|||
|
|
|
|||
|
|
// 清空画布
|
|||
|
|
this.canvas.clear();
|
|||
|
|
this.currentPosterId = posterId;
|
|||
|
|
|
|||
|
|
// 加载JSON数据到画布
|
|||
|
|
await new Promise((resolve, reject) => {
|
|||
|
|
this.canvas.loadFromJSON(fabricJsonData, () => {
|
|||
|
|
console.log('Fabric.js JSON数据加载成功');
|
|||
|
|
this.canvas.renderAll();
|
|||
|
|
resolve();
|
|||
|
|
}, (error) => {
|
|||
|
|
console.error('加载JSON数据失败:', error);
|
|||
|
|
reject(error);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 加载图层信息
|
|||
|
|
await this.loadLayerInfo(posterId);
|
|||
|
|
|
|||
|
|
console.log('海报加载完成');
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载海报失败:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从API获取并加载海报
|
|||
|
|
async loadPosterFromApi(posterId) {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`/poster/fabric-json/${posterId}`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0) {
|
|||
|
|
await this.loadPosterFromJson(result.data, posterId);
|
|||
|
|
} else {
|
|||
|
|
throw new Error(result.message || '获取海报数据失败');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('从API加载海报失败:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载图层信息
|
|||
|
|
async loadLayerInfo(posterId) {
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`/poster/layers/${posterId}`);
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0) {
|
|||
|
|
this.layers = result.data;
|
|||
|
|
this.renderLayerPanel();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载图层信息失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 渲染图层管理面板
|
|||
|
|
renderLayerPanel() {
|
|||
|
|
const layerList = document.getElementById('layer-list');
|
|||
|
|
layerList.innerHTML = '';
|
|||
|
|
|
|||
|
|
this.layers.forEach((layer, index) => {
|
|||
|
|
const layerItem = document.createElement('div');
|
|||
|
|
layerItem.className = 'layer-item';
|
|||
|
|
layerItem.innerHTML = `
|
|||
|
|
<div class="layer-info">
|
|||
|
|
<span class="layer-name">${layer.name}</span>
|
|||
|
|
<span class="layer-type">(${layer.type})</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="layer-controls">
|
|||
|
|
<label>
|
|||
|
|
<input type="checkbox" ${layer.visible ? 'checked' : ''}
|
|||
|
|
onchange="editor.toggleLayerVisibility(${index})">
|
|||
|
|
显示
|
|||
|
|
</label>
|
|||
|
|
${layer.replaceable ? `
|
|||
|
|
<button onclick="editor.selectLayerForReplace(${index})">
|
|||
|
|
替换图片
|
|||
|
|
</button>
|
|||
|
|
` : ''}
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
layerList.appendChild(layerItem);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 替换底层图片
|
|||
|
|
async replaceBackgroundImage(imageFile) {
|
|||
|
|
try {
|
|||
|
|
if (!this.currentPosterId) {
|
|||
|
|
throw new Error('没有加载的海报');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换图片为base64
|
|||
|
|
const imageBase64 = await this.fileToBase64(imageFile);
|
|||
|
|
|
|||
|
|
// 调用后端API替换图片
|
|||
|
|
const formData = new FormData();
|
|||
|
|
formData.append('posterId', this.currentPosterId);
|
|||
|
|
formData.append('newImageBase64', imageBase64);
|
|||
|
|
|
|||
|
|
const response = await fetch('/poster/replace-image', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: formData
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
if (result.code === 0) {
|
|||
|
|
// 重新加载画布
|
|||
|
|
await this.loadPosterFromJson(result.data, this.currentPosterId);
|
|||
|
|
console.log('图片替换成功');
|
|||
|
|
} else {
|
|||
|
|
throw new Error(result.message || '图片替换失败');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('替换图片失败:', error);
|
|||
|
|
alert('替换图片失败: ' + error.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 文件转base64
|
|||
|
|
fileToBase64(file) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = () => {
|
|||
|
|
// 移除data:image/xxx;base64,前缀
|
|||
|
|
const base64 = reader.result.split(',')[1];
|
|||
|
|
resolve(base64);
|
|||
|
|
};
|
|||
|
|
reader.onerror = reject;
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 切换图层可见性
|
|||
|
|
toggleLayerVisibility(layerIndex) {
|
|||
|
|
const layer = this.layers[layerIndex];
|
|||
|
|
if (layer) {
|
|||
|
|
layer.visible = !layer.visible;
|
|||
|
|
// 这里可以实现图层显示/隐藏的逻辑
|
|||
|
|
// 需要根据具体的图层结构来实现
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化事件监听
|
|||
|
|
initEventListeners() {
|
|||
|
|
// 图片上传事件
|
|||
|
|
document.getElementById('image-upload').addEventListener('change', (e) => {
|
|||
|
|
const file = e.target.files[0];
|
|||
|
|
if (file) {
|
|||
|
|
this.replaceBackgroundImage(file);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 替换按钮事件
|
|||
|
|
document.getElementById('replace-btn').addEventListener('click', () => {
|
|||
|
|
document.getElementById('image-upload').click();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出当前画布为图片
|
|||
|
|
exportAsImage(format = 'png', quality = 1.0) {
|
|||
|
|
return this.canvas.toDataURL({
|
|||
|
|
format: format,
|
|||
|
|
quality: quality,
|
|||
|
|
multiplier: 1
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前的Fabric.js JSON数据
|
|||
|
|
getCurrentJson() {
|
|||
|
|
return this.canvas.toJSON();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化编辑器
|
|||
|
|
const editor = new PosterEditor('poster-canvas');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. **完整使用示例**
|
|||
|
|
|
|||
|
|
#### **生成海报并加载到编辑器**
|
|||
|
|
```javascript
|
|||
|
|
// 完整的海报生成和加载流程
|
|||
|
|
async function generateAndLoadPoster() {
|
|||
|
|
try {
|
|||
|
|
// 1. 生成海报
|
|||
|
|
const generateRequest = {
|
|||
|
|
posterNumber: 1,
|
|||
|
|
posters: [{
|
|||
|
|
templateId: "vibrant",
|
|||
|
|
imagesBase64: "123,456", // 图片ID列表
|
|||
|
|
generatePsd: true,
|
|||
|
|
numVariations: 1
|
|||
|
|
}]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
console.log('正在生成海报...');
|
|||
|
|
const generateResponse = await fetch('/poster/generate', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/json'},
|
|||
|
|
body: JSON.stringify(generateRequest)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const generateResult = await generateResponse.json();
|
|||
|
|
|
|||
|
|
if (generateResult.code !== 0) {
|
|||
|
|
throw new Error('海报生成失败: ' + generateResult.message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const posterData = generateResult.data[0];
|
|||
|
|
const fabricJsonData = posterData.psdFiles[0].jsonData;
|
|||
|
|
const posterId = posterData.psdFiles[0].id;
|
|||
|
|
|
|||
|
|
console.log('海报生成成功,开始加载到编辑器...');
|
|||
|
|
|
|||
|
|
// 2. 加载到编辑器
|
|||
|
|
await editor.loadPosterFromJson(fabricJsonData, posterId);
|
|||
|
|
|
|||
|
|
console.log('海报加载到编辑器成功!');
|
|||
|
|
|
|||
|
|
// 3. 显示预览图
|
|||
|
|
const previewImage = posterData.resultImagesBase64[0].image;
|
|||
|
|
showPreviewImage(previewImage);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('生成和加载海报失败:', error);
|
|||
|
|
alert('操作失败: ' + error.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示预览图
|
|||
|
|
function showPreviewImage(base64Image) {
|
|||
|
|
const previewDiv = document.createElement('div');
|
|||
|
|
previewDiv.innerHTML = `
|
|||
|
|
<h3>生成的海报预览</h3>
|
|||
|
|
<img src="data:image/png;base64,${base64Image}"
|
|||
|
|
style="max-width: 300px; border: 1px solid #ddd; border-radius: 8px;">
|
|||
|
|
`;
|
|||
|
|
document.body.appendChild(previewDiv);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### **图片替换功能示例**
|
|||
|
|
```javascript
|
|||
|
|
// 高级图片替换功能
|
|||
|
|
class ImageReplacer {
|
|||
|
|
constructor(editor) {
|
|||
|
|
this.editor = editor;
|
|||
|
|
this.setupUI();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setupUI() {
|
|||
|
|
const replacePanel = document.querySelector('.image-replace-panel');
|
|||
|
|
replacePanel.innerHTML = `
|
|||
|
|
<h3>图片替换</h3>
|
|||
|
|
<div class="upload-area" id="upload-area">
|
|||
|
|
<p>拖拽图片到这里或点击选择</p>
|
|||
|
|
<input type="file" id="image-upload" accept="image/*" style="display: none;">
|
|||
|
|
</div>
|
|||
|
|
<div class="image-preview" id="image-preview" style="display: none;">
|
|||
|
|
<img id="preview-img" style="max-width: 100%; border-radius: 4px;">
|
|||
|
|
<button id="confirm-replace">确认替换</button>
|
|||
|
|
<button id="cancel-replace">取消</button>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
this.bindEvents();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bindEvents() {
|
|||
|
|
const uploadArea = document.getElementById('upload-area');
|
|||
|
|
const fileInput = document.getElementById('image-upload');
|
|||
|
|
const previewDiv = document.getElementById('image-preview');
|
|||
|
|
const previewImg = document.getElementById('preview-img');
|
|||
|
|
|
|||
|
|
// 点击上传
|
|||
|
|
uploadArea.addEventListener('click', () => {
|
|||
|
|
fileInput.click();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 拖拽上传
|
|||
|
|
uploadArea.addEventListener('dragover', (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
uploadArea.style.backgroundColor = '#f0f0f0';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
uploadArea.addEventListener('dragleave', () => {
|
|||
|
|
uploadArea.style.backgroundColor = '';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
uploadArea.addEventListener('drop', (e) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
uploadArea.style.backgroundColor = '';
|
|||
|
|
|
|||
|
|
const files = e.dataTransfer.files;
|
|||
|
|
if (files.length > 0) {
|
|||
|
|
this.handleFileSelect(files[0]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 文件选择
|
|||
|
|
fileInput.addEventListener('change', (e) => {
|
|||
|
|
const file = e.target.files[0];
|
|||
|
|
if (file) {
|
|||
|
|
this.handleFileSelect(file);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 确认替换
|
|||
|
|
document.getElementById('confirm-replace').addEventListener('click', () => {
|
|||
|
|
this.confirmReplace();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 取消
|
|||
|
|
document.getElementById('cancel-replace').addEventListener('click', () => {
|
|||
|
|
this.cancelReplace();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
handleFileSelect(file) {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = (e) => {
|
|||
|
|
const previewImg = document.getElementById('preview-img');
|
|||
|
|
const previewDiv = document.getElementById('image-preview');
|
|||
|
|
const uploadArea = document.getElementById('upload-area');
|
|||
|
|
|
|||
|
|
previewImg.src = e.target.result;
|
|||
|
|
previewDiv.style.display = 'block';
|
|||
|
|
uploadArea.style.display = 'none';
|
|||
|
|
|
|||
|
|
this.selectedFile = file;
|
|||
|
|
};
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async confirmReplace() {
|
|||
|
|
try {
|
|||
|
|
await this.editor.replaceBackgroundImage(this.selectedFile);
|
|||
|
|
this.cancelReplace();
|
|||
|
|
alert('图片替换成功!');
|
|||
|
|
} catch (error) {
|
|||
|
|
alert('图片替换失败: ' + error.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cancelReplace() {
|
|||
|
|
document.getElementById('image-preview').style.display = 'none';
|
|||
|
|
document.getElementById('upload-area').style.display = 'block';
|
|||
|
|
this.selectedFile = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化图片替换器
|
|||
|
|
const imageReplacer = new ImageReplacer(editor);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚀 快速开始
|
|||
|
|
|
|||
|
|
### **1. 基础集成**
|
|||
|
|
```html
|
|||
|
|
<!DOCTYPE html>
|
|||
|
|
<html>
|
|||
|
|
<head>
|
|||
|
|
<script src="https://cdn.jsdelivr.net/npm/fabric@5.3.0/dist/fabric.min.js"></script>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<canvas id="canvas" width="1350" height="1800"></canvas>
|
|||
|
|
<button onclick="loadDemo()">加载示例海报</button>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
const canvas = new fabric.Canvas('canvas');
|
|||
|
|
|
|||
|
|
async function loadDemo() {
|
|||
|
|
// 从API获取海报数据
|
|||
|
|
const response = await fetch('/poster/fabric-json/demo-poster-id');
|
|||
|
|
const result = await response.json();
|
|||
|
|
|
|||
|
|
// 加载到画布
|
|||
|
|
canvas.loadFromJSON(result.data, () => {
|
|||
|
|
canvas.renderAll();
|
|||
|
|
console.log('海报加载完成!');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **2. 图片替换示例**
|
|||
|
|
```javascript
|
|||
|
|
// 简单的图片替换
|
|||
|
|
async function replaceImage() {
|
|||
|
|
const input = document.createElement('input');
|
|||
|
|
input.type = 'file';
|
|||
|
|
input.accept = 'image/*';
|
|||
|
|
|
|||
|
|
input.onchange = async (e) => {
|
|||
|
|
const file = e.target.files[0];
|
|||
|
|
if (!file) return;
|
|||
|
|
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = async (event) => {
|
|||
|
|
const base64 = event.target.result.split(',')[1];
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch('/poster/replace-image', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
|||
|
|
body: `posterId=your-poster-id&newImageBase64=${encodeURIComponent(base64)}`
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.code === 0) {
|
|||
|
|
// 重新加载画布
|
|||
|
|
canvas.loadFromJSON(result.data, () => {
|
|||
|
|
canvas.renderAll();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('替换失败:', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
input.click();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ⚠️ 注意事项
|
|||
|
|
|
|||
|
|
### **1. 浏览器兼容性**
|
|||
|
|
- 需要支持Canvas API
|
|||
|
|
- 需要支持File API
|
|||
|
|
- 建议使用现代浏览器(Chrome 80+, Firefox 75+, Safari 13+)
|
|||
|
|
|
|||
|
|
### **2. 性能优化**
|
|||
|
|
```javascript
|
|||
|
|
// 优化Canvas性能
|
|||
|
|
canvas.set({
|
|||
|
|
renderOnAddRemove: false, // 添加/删除对象时不自动渲染
|
|||
|
|
skipTargetFind: true // 跳过目标查找(如果不需要交互)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 批量操作后手动渲染
|
|||
|
|
canvas.renderAll();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **3. 错误处理**
|
|||
|
|
```javascript
|
|||
|
|
// 完善的错误处理
|
|||
|
|
try {
|
|||
|
|
await editor.loadPosterFromApi(posterId);
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error.message.includes('404')) {
|
|||
|
|
console.error('海报不存在');
|
|||
|
|
} else if (error.message.includes('网络')) {
|
|||
|
|
console.error('网络错误,请重试');
|
|||
|
|
} else {
|
|||
|
|
console.error('未知错误:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### **4. 内存管理**
|
|||
|
|
```javascript
|
|||
|
|
// 清理资源
|
|||
|
|
function cleanup() {
|
|||
|
|
canvas.dispose(); // 销毁画布
|
|||
|
|
canvas = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面卸载时清理
|
|||
|
|
window.addEventListener('beforeunload', cleanup);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📞 技术支持
|
|||
|
|
|
|||
|
|
如在集成过程中遇到问题:
|
|||
|
|
- **API问题**: 检查请求格式和响应状态码
|
|||
|
|
- **Canvas问题**: 查看浏览器控制台错误信息
|
|||
|
|
- **性能问题**: 检查图片大小和Canvas尺寸
|
|||
|
|
- **兼容性问题**: 确认Fabric.js版本和浏览器支持
|
|||
|
|
|
|||
|
|
## 🔗 相关链接
|
|||
|
|
|
|||
|
|
- [Fabric.js官方文档](http://fabricjs.com/docs/)
|
|||
|
|
- [Canvas API文档](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)
|
|||
|
|
- [File API文档](https://developer.mozilla.org/en-US/docs/Web/API/File)
|