⚠️ this article for nuggets community first contract article, not authorized to forbid reprint

File upload is a very common function in daily work. In the course of project development, we usually use some mature upload components to achieve the corresponding function. In general, a mature upload component will not only provide a nice UI or a good interactive experience, but will also provide a variety of different upload methods to meet the needs of different scenarios.

In our work, we mainly involve 8 scenarios of file upload, each of which uses different technologies, and there are many details that need our extra attention. Today, I’m going to take you through these 8 scenarios to better understand the capabilities that mature upload components provide. After reading this article, you will know the following:

  • Single file upload: use the Accept attribute of input element to limit the type of the uploaded file, use JS to detect the type of the file and use Koa to achieve the function of single file upload;

  • Multi-file upload: The multiple attribute of the input element can be used to select multiple files and Koa can be used to realize multi-file upload.

  • Directory upload: use the WebKitDirectory attribute on the input element to support the function of directory upload and use Koa to achieve directory upload and store the function according to the file directory structure;

  • Compressed directory upload: On the basis of directory upload, JSZip is used to realize the function of compressed directory upload.

  • Drag upload: Use drag event and DataTransfer object to achieve drag upload function;

  • Clipboard upload: use Clipboard events and Clipboard API to achieve the function of Clipboard upload;

  • Slice, SparkMD5 and third-party library Async-pool are used to implement concurrent upload of large files.

  • Server upload: Using the third-party library form-data to achieve the function of streaming server file upload.

1. Single file upload

For the single file upload scenario, the most common is the picture upload scenario, so we take the picture upload as an example, first to introduce the basic process of the single file upload.

1.1 Front-end Code

html

In the following code, we restrict the type of file to be uploaded through the Accept attribute of the input element. Using image/*, you can only select image files, but you can also set specific types, such as image/ PNG or image/ PNG or image/ JPEG.

<input id="uploadFile" type="file" accept="image/*" />
<button id="submit" onclick="uploadFile()">Upload a file</button>
Copy the code

Note that although we set the Accept attribute of the input element to image/ PNG. However, users can successfully circumvent this restriction by changing the suffix of JPG/JPEG images to.png. To solve this problem, we can identify the correct file type by reading the binary data in the file.

To view the binary data for an image, there are ready-made editors like WinHex for Windows or Synalyze It! For macOS. Pro hexadecimal editor. Here we use Synalyze It! Pro is an editor to view the binary data corresponding to the avatar.

So on the front end, can you read the binary data of the file without the tools? The answer is yes, and I won’t introduce it here. For those interested, you can read how does JavaScript detect file types? This article. Also, note that there are compatibility issues with the Accept attribute of the input element. For example, IE 9 does not support the following, as shown in the following figure:

(Photo credit: caniuse.com/input-file-…

js

const uploadFileEle = document.querySelector("#uploadFile");

const request = axios.create({
  baseURL: "http://localhost:3000/upload".timeout: 60000});async function uploadFile() {
  if(! uploadFileEle.files.length)return;
  const file = uploadFileEle.files[0]; // Get a single file
  // Omit the file verification process, such as file type and size verification
  upload({
    url: "/single",
    file,
  });
}

function upload({ url, file, fieldName = "file" }) {
  let formData = new FormData();
  formData.set(fieldName, file);
  request.post(url, formData, {
    // Monitor the upload progress
    onUploadProgress: function (progressEvent) {
      const percentCompleted = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      );
      console.log(percentCompleted); }}); }Copy the code

In the above code, we first wrap the read File object into a FormData object, and then use the POST method of the Axios instance to implement the File upload function. Before uploading, you can get the upload progress of the file by setting the onUploadProgress property of the request configuration object.

1.2 Server code

Koa is an easy-to-use Web framework characterized by elegance, simplicity, lightweight, and high degree of freedom. Therefore, we choose it to build the file service, and use the following middleware to achieve the corresponding functions:

  • Koa-static: middleware that handles static resources;
  • Koa/CORS: middleware that handles cross-domain requests;
  • @koa/multer: handlingmultipart/form-dataMiddleware;
  • @KOA/Router: Middleware that handles routing.
const path = require("path");
const Koa = require("koa");
const serve = require("koa-static");
const cors = require("@koa/cors");
const multer = require("@koa/multer");
const Router = require("@koa/router");

const app = new Koa();
const router = new Router();
const PORT = 3000;
// The URL of the uploaded resource
const RESOURCE_URL = `http://localhost:${PORT}`;
// The directory for storing the uploaded files
const UPLOAD_DIR = path.join(__dirname, "/public/upload");

const storage = multer.diskStorage({
  destination: async function (req, file, cb) {
    // Set the file storage directory
    cb(null, UPLOAD_DIR);
  },
  filename: function (req, file, cb) {
    // Set the file name
    cb(null.`${file.originalname}`); }});const multerUpload = multer({ storage });

router.get("/".async (ctx) => {
  ctx.body = "Welcome to file Service (by Po)";
});

router.post(
  "/upload/single".async (ctx, next) => {
    try {
      await next();
      ctx.body = {
        code: 1.msg: "File uploaded successfully".url: `${RESOURCE_URL}/${ctx.file.originalname}`}; }catch (error) {
      ctx.body = {
        code: 0.msg: "File upload failed"
      };
    }
  },
  multerUpload.single("file"));// Register the middleware
app.use(cors());
app.use(serve(UPLOAD_DIR));
app.use(router.routes()).use(router.allowedMethods());

app.listen(PORT, () = > {
  console.log(`app starting at port ${PORT}`);
});
Copy the code

The above code is relatively simple, so we won’t go into it. The Koa kernel is simple, and extensions are implemented through middleware. For example, the routing, CORS, static resource processing and other functions used in the examples are implemented through middleware. Therefore, to master the FRAMEWORK of Koa, the core is to master its middleware mechanism. If you want to dig deeper, read how to better understand middleware and the Onion Model. In fact, in addition to single file upload, in the file upload scenario, we can also upload multiple files at the same time.

Example of uploading a single file: single-file-upload

Github.com/semlinker/f…

2. Upload multiple files

To upload multiple files, we first need to allow the user to select multiple files simultaneously. To do this, we can use the Multiple attribute of the input element. As with the Accept attribute, there are compatibility issues with this attribute, as shown in the following figure:

(Photo credit: caniuse.com/mdn-api_htm…

2.1 Front-end Code

html

Compared to single-file upload code, the multiple file upload input element has a multiple attribute:

<input id="uploadFile" type="file" accept="image/*" multiple />
<button id="submit" onclick="uploadFile()">Upload a file</button>
Copy the code

js

For single-file uploads, we get a single file via uploadFileele. files[0]. For multi-file uploads, we need to get a list of the selected files via uploadFileele. files, which returns a FileList object.

async function uploadFile() {
  if(! uploadFileEle.files.length)return;
  const files = Array.from(uploadFileEle.files);
  upload({
    url: "/multiple",
    files,
  });
}
Copy the code

Since we need to support uploading multiple files, we need to update the upload function synchronously. The corresponding logic is to iterate through the list of files and add multiple files using the append method of the FormData object, as shown below:

function upload({ url, files, fieldName = "file" }) {
  let formData = new FormData();
  files.forEach((file) = > {
    formData.append(fieldName, file);
  });
  request.post(url, formData, {
    // Monitor the upload progress
    onUploadProgress: function (progressEvent) {
      const percentCompleted = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      );
      console.log(percentCompleted); }}); }Copy the code

2.2 Server code

In the following code, we define a new route — /upload/multiple to handle multiple file uploads. When all files have been successfully uploaded, a list of urls for uploaded files is returned:

router.post(
  "/upload/multiple".async (ctx, next) => {
    try {
      await next();
      urls = ctx.files.file.map(file= > `${RESOURCE_URL}/${file.originalname}`);
      ctx.body = {
        code: 1.msg: "File uploaded successfully",
        urls
      };
    } catch (error) {
      ctx.body = {
        code: 0.msg: "File upload failed"}; } }, multerUpload.fields([ {name: "file".// Corresponds to the fieldName of the FormData form entry},]));Copy the code

After introducing the functions of single file and multi-file upload, let’s introduce the functions of directory upload.

Example for uploading multiple files: multiple-file-upload

Github.com/semlinker/f…

Iii. Directory upload

In case you didn’t know, there is also a WebKitDirectory attribute on the input element. Once the webKitDirectory property is set, we can select the directory.

<input id="uploadFile" type="file" accept="image/*" webkitdirectory />
Copy the code

When we select the specified directory, such as the images directory on Apog’s desktop, the following confirmation box is displayed:

After clicking the upload button, we can get the list of files. The file object in the list contains a webkitRelativePath property that represents the relative path of the current file.

While the ability to select a directory is easy to implement with the WebKitDirectory attribute, we also need to consider its compatibility in a real project. For example, versions below IE 11 do not support this attribute. The following figure shows the compatibility of other browsers:

(Photo credit: caniuse.com/?search=web…

After understanding the compatibility of the WebKitDirectory attribute, let’s first introduce the front-end implementation code.

3.1 Front-end Code

In order for the server to store the corresponding file according to the actual directory structure, we need to submit the path of the current file to the server when adding the form item. In addition, in order to ensure that @koa/multer handles the file path correctly, we need to do special treatment to the path. Replace the slash with the @ sign. The corresponding processing method is as follows:

function upload({ url, files, fieldName = "file" }) {
  let formData = new FormData();
  files.forEach((file, i) = > {
    formData.append(
      fieldName, 
      files[i],
      files[i].webkitRelativePath.replace(/\//g."@"); ; }); request.post(url, formData);// Omit the upload progress processing
}
Copy the code

3.2 Server Code

The main difference between the server code for directory uploads and multi-file uploads is that the configuration object of @KOA/Multer middleware is different. In the function corresponding to the destination property, we need to restore the @ in the filename to a /, and then generate a directory based on the actual path of the file.

const fse = require("fs-extra");
const storage = multer.diskStorage({
  destination: async function (req, file, cb) {
    // [email protected] => images/image-1.jpeg
    let relativePath = file.originalname.replace(/@/g, path.sep);
    let index = relativePath.lastIndexOf(path.sep);
    let fileDir = path.join(UPLOAD_DIR, relativePath.substr(0, index));
    // Make sure the file directory exists. If not, it will be created automatically
    await fse.ensureDir(fileDir); 
    cb(null, fileDir);
  },
  filename: function (req, file, cb) {
    let parts = file.originalname.split("@");
    cb(null.`${parts[parts.length - 1]}`); }});Copy the code

Now that we have implemented the function of directory upload, can we compress the files in the directory into a compressed package and then upload it? The answer is yes, let’s introduce how to implement the compressed directory upload function.

Example directory upload: directory-upload

Github.com/semlinker/f…

4. Upload the compressed directory

How to unzip ZIP files online in JavaScript? In this article, I showed you how to use the JSZip library to unzip ZIP files online on the browser side. In addition to parsing ZIP files, the JSZip library can also be used to create and edit ZIP files. Using the API provided by the JSZip library, we can compress all the files in the directory into a ZIP file, and then upload the generated ZIP file to the server.

4.1 Front-end Code

The file(name, data [,options]) method on the JSZip instance can add files to the ZIP file. Based on this approach we can encapsulate a generateZipFile function that compresses the list of files in the directory into a ZIP file. The generateZipFile function is implemented as follows:

function generateZipFile(
  zipName, files,
  options = { type: "blob", compression: "DEFLATE" }
) {
  return new Promise((resolve, reject) = > {
    const zip = new JSZip();
    for (let i = 0; i < files.length; i++) {
      zip.file(files[i].webkitRelativePath, files[i]);
    }
    zip.generateAsync(options).then(function (blob) {
      zipName = zipName || Date.now() + ".zip";
      const zipFile = new File([blob], zipName, {
        type: "application/zip"}); resolve(zipFile); }); }); }Copy the code

After creating the generateZipFile function, we need to update the uploadFile function we introduced earlier:

async function uploadFile() {
  let fileList = uploadFileEle.files;
  if(! fileList.length)return;
  let webkitRelativePath = fileList[0].webkitRelativePath;
  let zipFileName = webkitRelativePath.split("/") [0] + ".zip";
  let zipFile = await generateZipFile(zipFileName, fileList);
  upload({
    url: "/single".file: zipFile,
    fileName: zipFileName
  });
}
Copy the code

In the above uploadFile function, we will process the returned FileList by calling generateZipFile to generate the ZIP file. In addition, in order to obtain the fileName when the server receives the compressed file, we add a fileName parameter to the upload function. This parameter is used to set the name of the uploaded file when calling the formdata.append method:

function upload({ url, file, fileName, fieldName = "file" }) {
  if(! url || ! file)return;
  let formData = new FormData();
  formData.append(
    fieldName, file, fileName
  );
  request.post(url, formData); // Omit the upload progress tracking
}
Copy the code

Above is the compressed directory upload, front-end part of the JS code, server side code can refer to the previous single file upload related code.

Example: directory-compress-upload

Github.com/semlinker/f…

Drag and drop upload

To implement drag-and-drop uploads, we need to first understand drag-and-drop related events. Such as drag, dragend, dragenter, dragover, or drop events. Here we only cover the drag-and-drop events that will be used next:

  • dragenter: Fired when an element or selected text is dragged to a releasable target;
  • dragover: Fires when an element or selected text is dragged onto a releasable target (every 100 milliseconds);
  • dragleave: Fired when dragging an element or selected text away from a releasable target;
  • drop: Fired when an element or selected text is released on a releasable target.

Based on these events, we can improve the user’s drag and drop experience. For example, when a user drags an element into the target area, the target area is highlighted. When the user drags an element out of the target area, the highlight is removed. Obviously, when the drop event is triggered and the element is already in the target area, we need to retrieve the corresponding data.

So how do you get the drag-and-drop data? At this point we need to use the DataTransfer object, which holds data during the drag and drop process. It can hold one or more items of data, which can be of one or more data types. If the drag operation involves dragging files, we can get the list of files through the Files property of the DataTransfer object.

After introducing the knowledge related to drag and drop upload, let’s take a look at how to achieve the function of drag and drop upload.

5.1 Front-end Code

html

<div id="dropArea">
   <p>Drag and drop the upload file</p>
   <div id="imagePreview"></div>
</div>
Copy the code

css

#dropArea {
  width: 300px;
  height: 300px;
  border: 1px dashed gray;
  margin-bottom: 20px;
}
#dropArea p {
  text-align: center;
  color: # 999;
}
#dropArea.highlighted {
  background-color: #ddd;
}
#imagePreview {
  max-height: 250px;
  overflow-y: scroll;
}
#imagePreview img {
  width: 100%;
  display: block;
  margin: auto;
}
Copy the code

js

To make the drag-and-drop upload code easier to read, let’s break it down into four parts:

1. Prevent default drag and drop

const dropAreaEle = document.querySelector("#dropArea");
const imgPreviewEle = document.querySelector("#imagePreview");
const IMAGE_MIME_REGEX = /^image\/(jpe? g|gif|png)$/i;

["dragenter"."dragover"."dragleave"."drop"].forEach((eventName) = > {
   dropAreaEle.addEventListener(eventName, preventDefaults, false);
   document.body.addEventListener(eventName, preventDefaults, false);
});

function preventDefaults(e) {
  e.preventDefault();
  e.stopPropagation();
}
Copy the code

2. Switch the highlight state of the target area

["dragenter"."dragover"].forEach((eventName) = > {
    dropAreaEle.addEventListener(eventName, highlight, false);
});
["dragleave"."drop"].forEach((eventName) = > {
    dropAreaEle.addEventListener(eventName, unhighlight, false);
});

// Add the highlight style
function highlight(e) {
  dropAreaEle.classList.add("highlighted");
}

// Remove the highlight style
function unhighlight(e) {
  dropAreaEle.classList.remove("highlighted");
}
Copy the code

3. Handle image previews

dropAreaEle.addEventListener("drop", handleDrop, false);

function handleDrop(e) {
  const dt = e.dataTransfer;
  const files = [...dt.files];
  files.forEach((file) = > {
    previewImage(file, imgPreviewEle);
  });
  // Omit the file upload code
}

function previewImage(file, container) {
  if (IMAGE_MIME_REGEX.test(file.type)) {
    const reader = new FileReader();
    reader.onload = function (e) {
      let img = document.createElement("img"); img.src = e.target.result; container.append(img); }; reader.readAsDataURL(file); }}Copy the code

4. Upload files

function handleDrop(e) {
  const dt = e.dataTransfer;
  const files = [...dt.files];
  // Omit the image preview code
  files.forEach((file) = > {
    upload({
      url: "/single",
      file,
    });
  });
}

const request = axios.create({
  baseURL: "http://localhost:3000/upload".timeout: 60000});function upload({ url, file, fieldName = "file" }) {
  let formData = new FormData();
  formData.set(fieldName, file);
  request.post(url, formData, {
    // Monitor the upload progress
    onUploadProgress: function (progressEvent) {
      const percentCompleted = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      );
      console.log(percentCompleted); }}); }Copy the code

Drag-and-drop upload is a common scenario, and many mature upload components support it. In fact, in addition to drag and drop upload, you can also use the clipboard to achieve the function of copy upload.

Drag and drop upload example: drag-drop-upload

Github.com/semlinker/f…

Upload the clipboard

Before introducing how to implement the Clipboard upload function, we need to understand the Clipboard API. The Clipboard interface implements the Clipboard API, providing read and write access to the system Clipboard if the user grants the right permissions. The Clipboard API can be used to implement cut, copy, and paste functionality in Web applications. The API is used to replace clipboard operations through the Document. execCommand API.

In a real project, we don’t need to manually create the Clipboard object. Instead, we use navigator. Clipboard to get the Clipboard object:

After obtaining the Clipboard object, we can use the API provided by the object to access the Clipboard, such as:

navigator.clipboard.readText().then(
  clipText= > document.querySelector(".editor").innerText = clipText
);
Copy the code

The above code replaces the contents of the first element in HTML containing the.editor class with the contents of the clipboard. If the clipboard is empty or does not contain any text, the contents of the element are emptied. This is because the readText method returns an empty string when the clipboard is empty or contains no text.

Using the Clipboard API, we can easily operate the Clipboard, but we also need to consider its compatibility in the actual project:

(Photo credit: caniuse.com/async-clipb…

To realize the clipboard upload function, can be divided into the following three steps:

  • Listen for container paste events;
  • Read and parse the contents of the clipboard;
  • Dynamically buildFormDataObject and upload it.

With these steps in mind, let’s take a look at the implementation code.

6.1 Front-end Code

html

<div id="uploadArea">
   <p>Please copy the picture before you paste it</p>
</div>
Copy the code

css

#uploadArea {
   width: 400px;
   height: 400px;
   border: 1px dashed gray;
   display: table-cell;
   vertical-align: middle;
}
#uploadArea p {
   text-align: center;
   color: # 999;
}
#uploadArea img {
   max-width: 100%;
   max-height: 100%;
   display: block;
   margin: auto;
}
Copy the code

js

In the following code, we use the addEventListener method to add a paste event to the uploadArea container. In the corresponding event handler, we will prioritize whether the current browser supports the asynchronous Clipboard API. If so, the contents of the clipboard are read through the navigater.clipboard.read method. After reading the content, we use the re to determine whether the clipboard item contains an image resource. If so, we call the previewImage method to preview the image and save the returned BLOB object for subsequent uploads.

const IMAGE_MIME_REGEX = /^image\/(jpe? g|gif|png)$/i;
const uploadAreaEle = document.querySelector("#uploadArea");

uploadAreaEle.addEventListener("paste".async (e) => {
  e.preventDefault();
  const files = [];
  if (navigator.clipboard) {
    let clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        if (IMAGE_MIME_REGEX.test(type)) {
           const blob = awaitclipboardItem.getType(type); insertImage(blob, uploadAreaEle); files.push(blob); }}}}else {
      const items = e.clipboardData.items;
      for (let i = 0; i < items.length; i++) {
        if (IMAGE_MIME_REGEX.test(items[i].type)) {
          letfile = items[i].getAsFile(); insertImage(file, uploadAreaEle); files.push(file); }}}if (files.length > 0) {
    confirm("Clipboard detected image file, do you want to upload?") 
      && upload({
           url: "/multiple", files, }); }});Copy the code

If the current browser does not support the asynchronous Clipboard API, we will try to access the contents of the Clipboard via e.clipboardData.items. Note that we use the getAsFile method to get the contents of the clipboard while iterating through the contents of the clipboard. Of course, this method also has compatibility problems, as shown in the figure below:

(Photo credit: caniuse.com/mdn-api_dat…

As mentioned earlier, when parsed from the clipboard to an image resource, the user is allowed to preview it. This function is based on the FileReader API. The corresponding code is as follows:

function previewImage(file, container) {
  const reader = new FileReader();
  reader.onload = function (e) {
    let img = document.createElement("img");
    img.src = e.target.result;
    container.append(img);
  };
  reader.readAsDataURL(file);
}
Copy the code

After the user previews, we will perform the upload operation if the upload is confirmed. Since the file is read from the clipboard, we will automatically generate a file name for it according to the file type before uploading, specifically in the form of time stamp and file suffix:

function upload({ url, files, fieldName = "file" }) {
  let formData = new FormData();
  files.forEach((file) = > {
    let fileName = +new Date() + "." + IMAGE_MIME_REGEX.exec(file.type)[1];
    formData.append(fieldName, file, fileName);
  });
  request.post(url, formData);
}
Copy the code

Now that we’ve covered various scenarios for file uploads, let’s look at one “special” scenario — large file uploads.

Clipboard upload example: clipboard-upload

Github.com/semlinker/f…

7. Upload large files in blocks

Believe that you may already know big file upload solution, when uploading large files, in order to improve the efficiency of the upload, we usually use the Blob. The slice method to cut large files according to the specified size, and then through the multithreaded partition to upload, after all parts are uploaded successfully, then block merger notification service. The specific solution is shown in the figure below:

Because how do you do concurrent large file uploads in JavaScript? In this article, A baoge has introduced the large file concurrent upload scheme in detail, so I will not expand the introduction here. Let’s just review the complete process for uploading large files concurrently:

All the previous scenarios have introduced client-side file uploading. In fact, there are also server-side file uploading scenarios. For example, the poster is dynamically generated on the server and uploaded to another server or cloud vendor’s Object Storage Service (OSS). Let’s use Node.js as an example to describe how to upload files on the server.

Example of uploading a large file in blocks: big-file-upload

Github.com/semlinker/f…

Viii. Server upload

Server upload is the process of uploading files from one server to another. With the functionality provided by the Form-Data library on Github, we can easily implement server uploads. Here is a brief introduction to the single file and multi-file upload function:

8.1 Uploading a single file

const fs = require("fs");
const path = require("path");
const FormData = require("form-data");

const form1 = new FormData();
form1.append("file", fs.createReadStream(path.join(__dirname, "images/image-1.jpeg")));
form1.submit("http://localhost:3000/upload/single".(error, response) = > {
  if(error) {
    console.log("Single image upload failed");
    return;
  }
  console.log("Single image uploaded successfully");
});
Copy the code

8.2 Uploading multiple Files

const form2 = new FormData();
form2.append("file", fs.createReadStream(path.join(__dirname, "images/image-2.jpeg")));
form2.append("file", fs.createReadStream(path.join(__dirname, "images/image-3.jpeg")));
form2.submit("http://localhost:3000/upload/multiple".(error, response) = > {
  if(error) {
    console.log("Multi-picture upload failed");
    return;
  }
  console.log("Multiple images uploaded successfully");
});
Copy the code

After creating the FormData object, all we need to do is create a readable stream via the Fs. createReadStream API, and then add the form items by calling the Append method on the FormData object. Finally, call the submit method to perform the submission operation.

In addition to ReadableStream, the Append method on the FormData object supports the following types:

const FormData = require('form-data');
const http = require('http');

const form = new FormData();
http.request('http://nodejs.org/images/logo.png'.function(response) {
  form.append('my_field'.'my value');
  form.append('my_buffer'.new Buffer(10));
  form.append('my_logo', response);
});
Copy the code

The content of server file upload is introduced here, about form-data this library other usage, if you are interested, you can read the corresponding usage document. In addition to the eight scenarios described above, you may also use some synchronization tools in your daily work, such as the Syncthing file synchronization tool for file transfer. Ok, so that’s all we have to cover in this article, so let’s finish with a summary.

Server upload example: server-upload

Github.com/semlinker/f…

Nine,

I hope by the end of this article, you have some idea of the technology behind each of the eight scenarios for file uploading. Due to the limited space, I did not expand the contents related to multipart/form-data type, interested partners can learn about it.

In addition, for a real project, you might consider using a mature third-party component directly, such as Github’s 11K+ Star Filepond. This component adopts a plug-in architecture and provides a lot of functions in the way of plug-ins, such as File Encode, File Rename, File Poster, Image Preview and Image Crop, etc. All in all, it’s a great component to try out when you get a chance.

X. Reference resources

  • MDN- Clipboard
  • MDN – DataTransfer
  • JSZip- API
  • How does JavaScript detect file types?
  • How to achieve large file concurrent upload in JavaScript?