【问题标题】:Make File Uploader/Preview Handle Multiple Files使文件上传器/预览处理多个文件
【发布时间】:2021-09-25 16:54:39
【问题描述】:

我已经构建了一个文件上传器(在后端的 php 上运行),可以在上传之前预览图像文件。

我遇到的问题是我无法使用多个文件。

它基于我看过的教程,问题的症结在于updateThumbnail 函数。当为多个文件上传调用此函数时,我想我需要将第二个参数从 fileUploader.files[0] 更改为 fileUploader.files,但我正在努力处理实际函数本身。

我显然需要在 updateThumbnail 函数中运行一个 foreach 循环(或类似的循环),但我无法让它发挥作用。

注意:似乎 CodePen 不允许拖放功能,但有一个输入文件元素也分配给隐藏在 HTML 中的拖放区,显示为:无.这使用了一个单击事件侦听器和fileUploader.click(),因此当您单击拖放区时,您可以调出文件选择器窗口。

Codepen:https://codepen.io/pauljohnknight/pen/JjNNyzO

// hidden on the form, but has drag & drop files assigned to it
var fileUploader = document.getElementById("standard-upload-files");

var dropZone = document.getElementById("drop-zone");
var showSelectedImages = document.getElementById("show-selected-images");

dropZone.addEventListener("click", (e) => {
//assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
  fileUploader.click();
});

fileUploader.addEventListener("change", (e) => {
  if (fileUploader.files.length) {
    // this function is further down but declared here and shows a thumbnail of the image
    updateThumbnail(dropZone, fileUploader.files[0]);
  }
});

dropZone.addEventListener('dragover', e => {
    e.preventDefault()
})

dropZone.addEventListener('dragend', e => {
    e.preventDefault()
})

// When the files are dropped in the 'drop-zone'

dropZone.addEventListener("drop", (e) => {
  e.preventDefault();

  // assign dropped files to the hidden input element
  if (e.dataTransfer.files.length) {
    fileUploader.files = e.dataTransfer.files;
  }
  // function is declared here but written further down
  updateThumbnail(dropZone, e.dataTransfer.files[0]);
});

// updateThumbnail function that needs to be able to handle multiple files
function updateThumbnail(dropZone, file) {
  var thumbnailElement = document.querySelector(".drop-zone__thumb");

  if (!thumbnailElement) {
    thumbnailElement = document.createElement("img");
    thumbnailElement.classList.add("drop-zone__thumb");

    // append to showSelectedImages div
    showSelectedImages.appendChild(thumbnailElement);
  }

  if (file.type.startsWith("image/")) {
    var reader = new FileReader();

    reader.readAsDataURL(file);

    reader.onload = () => {
      thumbnailElement.src = reader.result;
    };
  } else {
    thumbnailElement.src = null;
  }
  
} // end of 'updateThumbnail' function
body {
  margin: 0;
  display: flex;
  justify-content: center;
  width: 100%;
}

form {
  width: 30%;
}

#drop-zone {
  border: 1px dashed;
  width: 100%;
  padding: 1rem;
  margin-bottom: 1rem;
}

.select-files {
  text-decoration: underline;
  cursor: pointer;
}

/* image that is preview prior to form submit*/
.drop-zone__thumb {
  width: 200px;
  height: auto;
  display: block;
}

#submit-images {
  margin-top: 1rem;
}
<form id="upload-images-form" enctype="multipart/form-data" method="post">
  <h1>Upload Your Images</h1>
  <div id="drop-zone" class="drop-zone flex">
    <p class="td text-center">DRAG AND DROP IMAGES HERE</p>
    <p>Or</p>
    <p class="select-files">Select Files</p>
  </div>
  <div id="show-selected-images"></div>
  <div class="inner-input-wrapper">
    <div class="upload-label-wrapper">
      <input id="standard-upload-files" style="display:none" type="file" name="standard-upload-files[]" multiple>
    </div>
    <input type="submit" name="submit-images" id="submit-images" value="SUBMIT IMAGES">
  </div>
</form>

【问题讨论】:

    标签: javascript forms upload drag-and-drop filereader


    【解决方案1】:

    我通过你的 Codepen 快速完成了 Codesandbox example

    是的,您只需要遍历您的文件并为每个文件添加一个预览。你可以使用for循环或者只使用Array.from然后.forEach(因为FileList不是一个真正的数组,你需要先将它转换为数组才能使用数组内置方法)

        Array.from(fileUploader.files).forEach((file) => {
          updateThumbnail(dropZone, file);
        });
    

    至于预览和updateThumbnail 功能 - 这一切都取决于您想如何使用它。如果您希望用户能够在第一次选择后添加更多文件,那么您可以附加新的预览。如果您想在用户选择新预览时清除旧预览,那么您需要删除旧预览。或者,您可以为每个预览添加“删除”按钮,以便用户在添加后删除其中一个。

    当您只想附加新预览时,这里是变体:

    function updateThumbnail(dropZone, file) {
      if (file.type.startsWith('image/')) {
        var reader = new FileReader();
    
        reader.readAsDataURL(file);
    
        reader.onload = () => {
          var thumbnailElement = document.createElement('img');
          thumbnailElement.classList.add('drop-zone__thumb');
          thumbnailElement.src = reader.result;
          showSelectedImages.appendChild(thumbnailElement);
        };
      }
    }
    

    对于 drop 你基本上也是这样做的:

    dropZone.addEventListener('drop', (e) => {
      e.preventDefault();
    
      // .. do whatever you want or need here
    
      Array.from(e.dataTransfer.files).forEach((file) => {
        updateThumbnail(dropZone, file);
      });
    });
    

    正如您所见,处理dropselect 的函数非常相似,您甚至可以创建单独的函数来接受fileList,然后对其进行处理,因此无需为这两种情况复制代码.

    【讨论】:

    • 谢谢!如果问题得到解决,如果您还可以将答案标记为已接受,那就太好了。所以它不会在未回答的问题过滤器中可见
    • 嗨@danila,我的同事一直在进行下一阶段的工作,但遇到了一个问题 - 当从预览中删除图像时,通过删除“x”上的点击事件,图像会留在内存并在提交表单时提交。 Theve done a question here 如果您知道如何解决该问题,它将获得丰厚的回报。 stackoverflow.com/questions/68492190/…
    【解决方案2】:

    对@Danila 的版本做了一些小改动

    最显着的区别是使用 es6 和使用 URL.createObjectURL 更快地加载图像的方法。当原型本身上有对象 url + 基于新承诺的读取方法时,文件阅读器现在几乎是一个遗留物。使用 bae64 对 base64 进行解码/编码是浪费时间

    https://codesandbox.io/s/httpsstackoverflowcomquestions68416563-forked-o5spy?file=/src/index.js

    import "./styles.css";
    
    // Query all needed elements in one go
    const [dropZone, showSelectedImages, fileUploader] = document.querySelectorAll(
      "#standard-upload-files, #drop-zone, #show-selected-images"
    );
    
    dropZone.addEventListener("click", (evt) => {
      // assigns the dropzone to the hidden input element so when you click 'select files' it brings up a file picker window
      fileUploader.click();
    });
    
    // Prevent browser default when draging over
    dropZone.addEventListener("dragover", (evt) => {
      evt.preventDefault();
    });
    
    fileUploader.addEventListener("change", (evt) => {
      // Clear the already selected images
      showSelectedImages.innerHTML = "";
      // this function is further down but declared here and shows a thumbnail of the image
      [...fileUploader.files].forEach(updateThumbnail);
    });
    
    dropZone.addEventListener("drop", (evt) => {
      evt.preventDefault();
      // Clear the already selected images
      showSelectedImages.innerHTML = "";
    
      // assign dropped files to the hidden input element
      if (evt.dataTransfer.files.length) {
        fileUploader.files = evt.dataTransfer.files;
      }
    
      // function is declared here but written further down
      [...evt.dataTransfer.files].forEach(updateThumbnail);
    });
    
    // updateThumbnail function that needs to be able to handle multiple files
    function updateThumbnail(file) {
      if (file.type.startsWith("image/")) {
        const thumbnailElement = new Image();
        thumbnailElement.classList.add("drop-zone__thumb");
        thumbnailElement.src = URL.createObjectURL(file);
        showSelectedImages.append(thumbnailElement);
      }
    } // end of 'updateThumbnail' function
    

    【讨论】:

    • 嗨@Endless 我在下一阶段使用了这个答案的一个版本,我遇到了下面问题中概述的问题(接下来的几个有 250 赏金天)你认为你能帮忙吗?我从我的同事那里接手了这个。非常感谢任何帮助。 stackoverflow.com/questions/68492190/…
    • 我的建议是在哪里使用 new DataTransfer() 创建文件列表并更新文件输入列表并使用 fetch + FormData 作为后备,但 vanowm 已经建议了。不要认为有其他更好的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-15
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多