遮罩图片上传保存异常:仅最新图片存入服务器,需保存全部上传图
解决多遮罩图片上传仅最后一张保存的问题
嘿,我看了你的代码,问题很明确:你现在用单个全局变量file存储上传的文件,每次新上传都会覆盖它,而且保存时只取了固定id的canvas,自然只有最后一张能存到服务器。下面我给你一步步修复:
问题根源拆解
- 全局变量覆盖:
file是单个变量,每次上传新图片都会把之前的文件覆盖,最后只有最后一次的文件被提交。 - 固定canvas获取:
test()里硬编码取id="1"的canvas,只能拿到第二个遮罩的内容,完全忽略了第一个遮罩的上传。 - 无批量存储机制:没有记录每个遮罩对应的上传文件和canvas信息,无法一次性提交所有内容。
修复方案
1. 新增批量存储数组
把单个file和target替换成数组,用来记录每个遮罩的上传文件和对应的canvas ID:
var uploadedFiles = []; // 存储所有上传的文件+对应canvas ID
2. 修改文件上传逻辑
更新fileup.onchange,把每次上传的文件和对应遮罩的canvas ID存入数组,同时允许重复选择相同文件:
fileup.onchange = function() { if (!target) return; // 确保有选中的遮罩 const file = this.files[0]; const mask2 = table[target]; mask2.loadImage(URL.createObjectURL(file)); // 先移除当前遮罩之前的上传记录(如果有),再添加新记录 uploadedFiles = uploadedFiles.filter(item => item.canvasId !== target); uploadedFiles.push({ canvasId: target, file: file }); // 清空input值,允许重复选择相同文件 this.value = ""; };
3. 重构保存函数test()
现在要遍历所有上传的遮罩,把每个遮罩的合成图片(canvas内容)转成Blob,批量提交到服务器:
function test() { if (uploadedFiles.length === 0) { alert("请先上传图片!"); return; } // 用Promise处理所有canvas转Blob的异步操作 Promise.all(uploadedFiles.map(item => { return new Promise((resolve) => { const canvas = document.getElementById(item.canvasId); canvas.toBlob(function(blob) { // 生成带遮罩ID的文件名,避免重名 const fileName = `mask_${item.canvasId}_${item.file.name}`; resolve({blob, fileName}); }, 'image/png'); }); })).then(blobs => { const formData = new FormData(); // 把所有Blob添加到FormData blobs.forEach(({blob, fileName}) => { formData.append('canvasImages[]', blob, fileName); }); // 提交到服务器 $.ajax({ type: "POST", url: "save.php", cache: false, contentType: false, processData: false, data: formData }).done(function(o) { const response = JSON.parse(o); if (!response.error) { // 展示所有保存成功的图片 response.data.forEach(item => { $('body').prepend(`<img src="test/multiple/${item.fileName}" style="height: 200px; width: auto; margin-right: 10px;">`); }); } else { alert(`上传失败:${response.message}`); } }).fail(function() { alert("网络错误,上传失败!"); }); }); }
4. 升级后端save.php
修改后端代码,支持接收多个文件:
<?php $response = [ 'error' => false, 'message' => null, 'data' => [] ]; $uploadDir = 'test/multiple/'; // 确保上传目录存在 if (!is_dir($uploadDir)) { mkdir($uploadDir, 0777, true); } if (isset($_FILES['canvasImages'])) { $files = $_FILES['canvasImages']; // 遍历所有上传的文件 for ($i = 0; $i < count($files['name']); $i++) { $fileName = $files['name'][$i]; $tmpName = $files['tmp_name'][$i]; $fileError = $files['error'][$i]; if ($fileError === UPLOAD_ERR_OK) { $destination = $uploadDir . $fileName; if (move_uploaded_file($tmpName, $destination)) { $response['data'][] = [ 'msg' => "图片保存成功!", 'fileName' => $fileName ]; } else { $response['error'] = true; $response['message'] = "保存文件失败:" . $fileName; break; } } else { $response['error'] = true; $response['message'] = "文件上传错误:" . $fileName; break; } } } else { $response['error'] = true; $response['message'] = "未接收到任何文件!"; } echo json_encode($response); ?>
完整修改后的前端代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <input id="fileup" name="fileup" type="file" style="display:block; position:relative; top : 1000px; "> <div id="container" class="container"> </div> <button class ="save" onclick="test()">Save image to server</button> <style> .save { font-size:20px; } .container { background: silver; position: relative; } .container img { position: absolute; top: 0; bottom: 250px; left: 0; right: 0; margin: auto; z-index: 999; } .masked-img { overflow: hidden; position: relative; } </style> <script> var target; const imageUrl = ""; var jsonData = { "layers": [{ "x": 0, "height": 600, "layers": [{ "x": 160, "src": "ax0HVTs.png", "y": 281, "height": 296, "width": 429, "name": "mask_1" }, { "x": 25, "src": "hEM2kEP.png", "height": 324, "width": 471, "y": 9, "name": "mask_2" }], "y": 0, "width": 612 }] }; const containerElement = $('#container'); const fileUp = $('#fileup'); let mask; var uploadedFiles = []; // 新增:存储所有上传记录 let table = []; // 移到全局,确保fileup.onchange能访问到 $(function() { // Upload image onclick mask image containerElement.click(function(e) { var res = e.target; target = res.id; if (e.target.getContext) { // click only inside Non Transparent part var pixel = e.target.getContext('2d').getImageData(e.offsetX, e.offsetY, 1, 1).data; if (pixel[3] === 255) { setTimeout(() => { $('#fileup').click(); }, 20); } } }); // Fetch mask images from json file function getAllSrc(layers) { let arr = []; layers.forEach(layer => { if (layer.src) { arr.push({ src: layer.src, x: layer.x, y: layer.y, height: layer.height, width: layer.width, name: layer.name }); } else if (layer.layers) { let newArr = getAllSrc(layer.layers); if (newArr.length > 0) { newArr.forEach(({ src, x, y, height, width, name }) => { arr.push({ src, x: (layer.x + x), y: (layer.y + y), height, width, name: (name) }); }); } } }); return arr; } function json(data) { var width = 0; var height = 0; let arr = getAllSrc(data.layers); let layer1 = data.layers; width = layer1[0].width; height = layer1[0].height; console.log(layer1); let counter = 0; table = []; containerElement.css('width', width + "px").css('height', height + "px").addClass('temp'); for (let { src, x, y, name } of arr) { var ImagePosition = arr; var imageUrl1 = imageUrl; var maskInstance = $(".container").mask({ imageUrl: name.indexOf('mask_') !== -1 ? imageUrl1 : undefined, maskImageUrl: 'https://i.imgur.com/' + src, onMaskImageCreate: function(img) { img.css({ "position": "absolute", "left": x + "px", "top": y + "px" }); }, id: counter }); ImagePosition.map(function(cur, index) { var available = cur.name.includes('mask_'); if (!available) { $('.masked-img' + index).css('pointer-events', 'none'); } }); table.push(maskInstance); counter++; } // 把fileup.onchange移到这里,避免循环覆盖 fileup.onchange = function() { if (!target) return; const file = this.files[0]; const mask2 = table[target]; mask2.loadImage(URL.createObjectURL(file)); uploadedFiles = uploadedFiles.filter(item => item.canvasId !== target); uploadedFiles.push({ canvasId: target, file: file }); this.value = ""; }; return table; } mask = json(jsonData); }); // Image code (function($) { window.JQmasks = []; $.fn.mask = function(options) { const settings = $.extend({ maskImageUrl: undefined, imageUrl: undefined, scale: 1, id: new Date().getUTCMilliseconds().toString(), x: 0, y: 0, onMaskImageCreate: function(div) {}, rotate: 0, }, options); settings.maskImage = new Image settings.image = new Image settings.maskImage.setAttribute('crossOrigin', 'anonymous'); settings.image.setAttribute('crossOrigin', 'anonymous'); settings.maskImage.onload = function() { container.loadImage(settings.imageUrl, true) container.drawMask() } settings.image.onload = function() { container.drawImage() } var container = $(this); let prevX = 0, prevY = 0, draggable = false, img, canvas, context, image, timeout, initImage = false, startX = settings.x, startY = settings.y, scale = settings.scale, div; container.drawMask = function() { if (!settings.maskImage) return true; canvas.width = settings.maskImage.width; canvas.height = settings.maskImage.height; context.save(); context.beginPath(); context.globalCompositeOperation = "source-over"; if (settings.maskImage) context.drawImage(settings.maskImage, 0, 0, settings.maskImage.width, settings.maskImage.height); context.restore() }; container.drawImage = function() { const img = settings.image settings.x = settings.x == 0 && initImage ? (canvas.width - (img.width * settings.scale)) / 2 : settings.x; settings.y = settings.y == 0 && initImage ? (canvas.height - (img.height * settings.scale)) / 2 : settings.y; context.globalCompositeOperation = 'source-atop'; context.save(); context.translate(settings.x + img.width / 2, settings.y + img.height / 2); context.rotate(settings.rotate); context.scale(settings.scale, settings.scale); context.translate(-(settings.x + img.width / 2), -(settings.y + img.height / 2)); let width = img.width, height = img.height; if (img) context.drawImage(img, settings.x, settings.y, width, height); context.restore(); initImage = false; } container.loadImage = function(imageUrl, isMask) { if (!imageUrl) return true; settings.y = startY; settings.x = startX; settings.scale = 1; settings.rotate = 0; prevX = prevY = 0; initImage = true; settings.image.src = imageUrl; if (!isMask) container.data('image_set' + settings.id, true) return settings.id; }; container.loadMaskImage = function(imageUrl, from) { canvas = document.createElement("canvas"); context = canvas.getContext('2d'); canvas.setAttribute("draggable", "true"); canvas.setAttribute("id", settings.id); settings.maskImage.src = imageUrl div = $("<div/>", { "class": "masked-img" }).append(canvas); container.append(div); if (settings.onMaskImageCreate) settings.onMaskImageCreate(div); }; if (settings.maskImageUrl) { container.loadMaskImage(settings.maskImageUrl); } JQmasks.push({ item: container, id: settings.id }) div.addClass('masked-img' + settings.id); div.attr('data-id', settings.id); return container; }; }(jQuery)); //Download image to server function test() { if (uploadedFiles.length === 0) { alert("请先上传图片!"); return; } Promise.all(uploadedFiles.map(item => { return new Promise((resolve) => { const canvas = document.getElementById(item.canvasId); canvas.toBlob(function(blob) { const fileName = `mask_${item.canvasId}_${item.file.name}`; resolve({blob, fileName}); }, 'image/png'); }); })).then(blobs => { const formData = new FormData(); blobs.forEach(({blob, fileName}) => { formData.append('canvasImages[]', blob, fileName); }); $.ajax({ type: "POST", url: "save.php", cache: false, contentType: false, processData: false, data: formData }).done(function(o) { const response = JSON.parse(o); if (!response.error) { response.data.forEach(item => { $('body').prepend(`<img src="test/multiple/${item.fileName}" style="height: 200px; width: auto; margin-right: 10px;">`); }); } else { alert(`上传失败:${response.message}`); } }).fail(function() { alert("网络错误,上传失败!"); }); }); } </script>
这样修改后,每个遮罩上传的图片都会被记录,点击保存时会把所有合成后的图片批量提交到服务器,所有图片都能成功保存啦!
内容的提问来源于stack exchange,提问作者vickey colors




