You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

遮罩图片上传保存异常:仅最新图片存入服务器,需保存全部上传图

解决多遮罩图片上传仅最后一张保存的问题

嘿,我看了你的代码,问题很明确:你现在用单个全局变量file存储上传的文件,每次新上传都会覆盖它,而且保存时只取了固定id的canvas,自然只有最后一张能存到服务器。下面我给你一步步修复:

问题根源拆解

  1. 全局变量覆盖file是单个变量,每次上传新图片都会把之前的文件覆盖,最后只有最后一次的文件被提交。
  2. 固定canvas获取test()里硬编码取id="1"的canvas,只能拿到第二个遮罩的内容,完全忽略了第一个遮罩的上传。
  3. 无批量存储机制:没有记录每个遮罩对应的上传文件和canvas信息,无法一次性提交所有内容。

修复方案

1. 新增批量存储数组

把单个filetarget替换成数组,用来记录每个遮罩的上传文件和对应的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

火山引擎 最新活动