Uploading images is a common requirement in projects. This article will introduce 4 different methods of uploading images, which are summarized and recorded here.

Functions without business scenarios are hooliganism, so let’s first simulate a business scenario that needs to be implemented. Suppose we want to make a background system to add a page for goods, with some fields such as product name, information, and the need to upload a wheel map of goods.

Let’s talk about how to do this using Vue, Element-UI, and wrapped components as examples. Other frameworks or implementations without frameworks are similar, and this article will focus on implementation ideas.

Cloud storage

Common qiuniuyun, OSS (Ali Cloud), these cloud platforms provide API interface, call the corresponding interface, after the file upload will return the picture stored on the server path, the front end can get the path to save and submit to the back end. This process is relatively simple to handle.

The main steps

  1. Sends a request to the backend to obtain OSS configuration data
  2. File upload, call OSS to provide interfaces
  3. Path where the uploaded file is stored on the server
  4. Stores the returned path into the form object

Code example

We implemented it with Ali’s OSS service. We tried to encapsulate an OSS image upload component.

Customize our file uploads via the HTTP-request parameter of the Element-UI upLoad component, using only other component styles, and other pre-upload hooks (controlling image size, upLoad limit, etc.).

<template>
  <el-upload
    list-type="picture-card"
    action="" '"
    :http-request="upload"
    :before-upload="beforeAvatarUpload">
    <i class="el-icon-plus"></i>
  </el-upload>
</template>

<script>
  import {getAliOSSCreds} from '@/api/common' // The backend obtains the OSS secret key information
  import {createId} from '@/utils' // a method to produce a unique ID
  import OSS from 'ali-oss'

  export default {
    name: 'imgUpload',
    data () {
      return{}},methods: {
      // Verify images before uploading
      beforeAvatarUpload (file) {
        const isLt2M = file.size / 1024 / 1024 < 2
        if(! isLt2M) {this.$message.error('Upload profile picture size cannot exceed 2MB! ')}return isLt2M
      },
      // Upload the image to OSS and send an event to the parent component to listen
      upload (item) {
        getAliOSSCreds().then(res= > { // Send a request to the background to pull OSS configurations
          let creds = res.body.data
          let client = new OSS.Wrapper({
            region: 'oss-cn-beijing'.// Server cluster region
            accessKeyId: creds.accessKeyId, / / OSS account
            accessKeySecret: creds.accessKeySecret, / / OSS code
            stsToken: creds.securityToken, / / signature token
            bucket: 'imgXXXX' // Bucket stored on Ali cloud
          })
          let key = 'resource/' + localStorage.userId + '/images/' + createId() + '.jpg'  // Store the path and give the image a unique name
          return client.put(key, item.file) Upload / / OSS
        }).then(res= > {
          console.log(res.url)
          this.$emit('on-success', res.url) // Return the path where the image is stored
        }).catch(err= > {
          console.log(err)
        })
      }
    }
  }
</script>
Copy the code

Traditional file servers upload images

This method is to upload the file to the hard disk of your own file server or cloud host by submitting formData. The specific idea is similar to the cloud file server.

The main steps

  1. Set the upload path, field name, header, and data parameters of the uploaded file
  2. After the upload is successful, return to the server storage path
  3. The returned image path is stored in the form submission object

Code sample

This type of image upLoad can be achieved by passing in the relevant fields of the back-end convention according to the upLoad component of elent-UI. Using element JS also generates formData objects, and uploading via Ajax is similar.

Here is just a simple example, see the el-upload component documentation for details

<template>
  <el-upload
    ref="imgUpload"
    :on-success="imgSuccess"
    :on-remove="imgRemove"
    accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
    :headers="headerMsg"
    :action="upLoadUrl"
    multiple>
    <el-button type="primary">To upload pictures</el-button>
  </el-upload>
</template>

<script>
  import {getAliOSSCreds} from '@/api/common' // The backend obtains the OSS secret key information
  import {createId} from '@/utils' // a method to produce a unique ID
  import OSS from 'ali-oss'

  export default {
    name: 'imgUpload',
    data () {
      return {
          headerMsg: {Token:'XXXXXX'},
          upLoadUrl:'xxxxxxxxxx'}},methods: {
      // The image was uploaded successfully
      imgSuccess (res, file, fileList) {
        console.log(res)
        console.log(file)
        console.log(fileList)   // Information about successful uploads can be obtained here}}}</script>
Copy the code

The image is transferred to base64 and uploaded

Sometimes doing some private projects, or some small picture uploads may take a front-end transfer to Base64 after becoming a string upload. When we have this one requirement, there is a commodity rotation map multiple, turn base64 encoding after removing data:image/ JPEG; Base64, which concatenates strings in comma form to the back end. So how do we do that.

1. How to convert local files to Base64

We can transfer files to Base64 format through readAsDataURL, a new feature of H5. There are multiple rotographs. You can also transfer files to Base64 immediately after clicking.

Specific steps

  1. Create a new file that encapsulates the method of asynchronous transfer to Base64
  2. Select local file when adding goods and select Save the entire File object with objects
  3. Finally, coding is done before submitting the entire item form

Notice here, because the readAsDataURL operation is asynchronous, how do we encode several file objects in the array and store the encoded backend image Base64 string in a new array in the order they were uploaded? The first thing that comes to mind is the chain call to promise, However, it can not be transcoded simultaneously, which is a waste of time. We can use cyclic async functions for concurrency and ordering. See Methods’ submitData method

utils.js

export function uploadImgToBase64 (file) {
  return new Promise((resolve, reject) = > {
    const reader = new FileReader()
    reader.readAsDataURL(file)
    reader.onload = function () {  // When the image is transferred to base64, the reader object is returned
      resolve(reader)
    }
    reader.onerror = reject
  })
}
Copy the code

Add some code to the product page

<template>
  <div>
      <el-upload
        ref="imgBroadcastUpload"
        :auto-upload="false" multiple
        :file-list="diaLogForm.imgBroadcastList"
        list-type="picture-card"
        :on-change="imgBroadcastChange"
        :on-remove="imgBroadcastRemove"
        accept="image/jpg,image/png,image/jpeg"
        action="">
        <i class="el-icon-plus"></i>
        <div slot="tip" class="el-upload__tip">Only JPG/PNG files can be uploaded, and the maximum value is 2 MB</div>
      </el-upload>
      <el-button>submitData</el-button> 
  </div>
</template>

<script>
  import { uploadImgToBase64 } from '@/utils' // Import the local image to base64 method

  export default {
    name: 'imgUpload',
    data () {
      return {
        diaLogForm: {
            goodsName:' '.// Commodity name field
            imgBroadcastList:[],  // Store a list of selected images
            imgsStr:' '     // Back end need multiple map base64 string, split}}},methods: {
      . / / after selecting images stored in diaLogForm imgBroadcastList object
      imgBroadcastChange (file, fileList) {
        const isLt2M = file.size / 1024 / 1024 < 2  // The size of uploaded profile picture cannot exceed 2MB
        if(! isLt2M) {this.diaLogForm.imgBroadcastList = fileList.filter(v= >v.uid ! == file.uid)this.$message.error('Image selection failed, each image size cannot exceed 2MB, please re-select! ')}else {
          this.diaLogForm.imgBroadcastList.push(file)
        }
      },
      // Triggered when images are removed
      imgBroadcastRemove (file, fileList) {
        this.diaLogForm.imgBroadcastList = fileList
      },
      // Submit popover data
      async submitDialogData () {
        const imgBroadcastListBase64 = []
        console.log('Picture to base64 start... ')
        // List => base64
        const filePromises = this.diaLogForm.imgBroadcastList.map(async file => {
          const response = await uploadImgToBase64(file.raw)
          return response.result.replace(/. *; base64,/.' ')  / / remove data: image/jpeg. base64,
        })
        // Output base64 images in sequence
        for (const textPromise of filePromises) {
          imgBroadcastListBase64.push(await textPromise)
        }
        console.log('Image to base64 end... , ', imgBroadcastListBase64)
        this.diaLogForm.imgsStr = imgBroadcastListBase64.join()
        console.log(this.diaLogForm)
        const res = await addCommodity(this.diaLogForm)              // Send the request to submit the form
        if (res.status) {
          this.$message.success('Added item succeeded')
          // Usually after successful submission, the back end will process it and return an image path where the item needs to be displayed}}}},</script>

Copy the code

This local image upload time to base64 upload is completed. But the wheel – cast map can be edited, how should we deal with it? Generally speaking, the product add page and modify page can share a component, so we continue to modify on this page.

Edit the first thing we would pull goods original data, to display, in the modified, returned by the server at this time of the picture is a path such as http://xxx.xxx.xxx/abc.jpg, when we add a picture or as same as the above methods transcoding. But the back end said, did not modify the picture also want to earn base64 turn over, well that do it. This is an online linked image, not a local image, how to do?

2. Transfer online pictures to Base64

Specific steps

  1. Utils. Js file adds an online image-to-base64 method, using canvas
  2. Edit the product, first pull the original product information display to the page
  3. Before submitting the form, transcode online or local images

utils.js

export function uploadImgToBase64 (img) {
  return new Promise((resolve, reject) = > {
    function getBase64Image (img) {
      const canvas = document.createElement('canvas')
      canvas.width = img.width
      canvas.height = img.height
      const ctx = canvas.getContext('2d')
      ctx.drawImage(img, 0.0, canvas.width, canvas.height)
      var dataURL = canvas.toDataURL()
      return dataURL
    }

    const image = new Image()
    image.crossOrigin = The '*'  // Allow cross-domain images
    image.src = img + '? v=' + Math.random()  // Clear the image cache
    image.onload = function () {
      resolve(getBase64Image(image))
    }
    image.onerror = reject
  })
}
Copy the code

Add some code to the product page

<template>
  <div>
      <el-upload
        ref="imgBroadcastUpload"
        :auto-upload="false" multiple
        :file-list="diaLogForm.imgBroadcastList"
        list-type="picture-card"
        :on-change="imgBroadcastChange"
        :on-remove="imgBroadcastRemove"
        accept="image/jpg,image/png,image/jpeg"
        action="">
        <i class="el-icon-plus"></i>
        <div slot="tip" class="el-upload__tip">Only JPG/PNG files can be uploaded, and the maximum value is 2 MB</div>
      </el-upload>
      <el-button>submitData</el-button> 
  </div>
</template>

<script>
    import { uploadImgToBase64, URLImgToBase64 } from '@/utils'
    
  export default {
    name: 'imgUpload',
    data () {
      return {
        diaLogForm: {
            goodsName:' '.// Commodity name field
            imgBroadcastList:[],  // Store a list of selected images
            imgsStr:' '     // Back end need multiple map base64 string, split
        }
      }
    },
    created(){
        this.getGoodsData()
    },
    methods: {
      . / / after selecting images stored in diaLogForm imgBroadcastList object
      imgBroadcastChange (file, fileList) {
        const isLt2M = file.size / 1024 / 1024 < 2  // The size of uploaded profile picture cannot exceed 2MB
        if(! isLt2M) {this.diaLogForm.imgBroadcastList = fileList.filter(v= >v.uid ! == file.uid)this.$message.error('Image selection failed, each image size cannot exceed 2MB, please re-select! ')}else {
          this.diaLogForm.imgBroadcastList.push(file)
        }
      },
      // Triggered when images are removed
      imgBroadcastRemove (file, fileList) {
        this.diaLogForm.imgBroadcastList = fileList
      },
      // Get the original information of the product
      getGoodsData () {
        getCommodityById({ cid: this.diaLogForm.id }).then(res= > {
          if (res.status) {
            Object.assign(this.diaLogForm, res.data)
            / / the 'JPG, 2. JPG, 3. JPG' into [} {url: 'http://xxx.xxx.xx/j.jpg',...] this format is displayed on the upload component. ImgBroadcastList Displays the original images
            this.diaLogForm.imgBroadcastList = this.diaLogForm.imgsStr.split(', ').map(v= > ({ url: this.BASE_URL + '/' + v })) 
          }
        }).catch(err= > {
          console.log(err.data)
        })
      },
      // Submit popover data
      async submitDialogData () {
        const imgBroadcastListBase64 = []
        console.log('Picture to base64 start... ')
        this.dialogFormLoading = true
        // List => base64
        const filePromises = this.diaLogForm.imgBroadcastList.map(async file => {
          if (file.raw) {  // If it is a local file
            const response = await uploadImgToBase64(file.raw)
            return response.result.replace(/. *; base64,/.' ')}else { // If the file is online
            const response = await URLImgToBase64(file.url)
            return response.replace(/. *; base64,/.' ')}})// Output base64 images in sequence
        for (const textPromise of filePromises) {
          imgBroadcastListBase64.push(await textPromise)
        }
        console.log('Image to base64 end... ')
        this.diaLogForm.imgs = imgBroadcastListBase64.join()
        console.log(this.diaLogForm)
        if (!this.isEdit) {  // The new editor has a common component. Distinguish interface calls
          const res = await addCommodity(this.diaLogForm)              // Submit the form
          if (res.status) {
            this.$message.success('Added successfully')}}else {
          const res = await modifyCommodity(this.diaLogForm)            // Submit the form
          if (res.status) {
            this.$router.push('/goods/goods-list')
            this.$message.success('Edit succeeded')}}}}}</script>

Copy the code

Image slice upload

When uploading the picture above, if a whole picture is uploaded, it may take too long for users to load the background picture. We can consider uploading the picture after slicing it.

The operation process is as follows:

  1. Listening to theinputEvents forfileobject
  2. throughnew FileReader()The fileintobase64`
  3. throughnew Image()Local loadbase64The picture
  4. After the image is loaded, passnaturalWidth naturalHeightCalculate the number of cutting sheets and the width and height of each cut
  5. Begin to usecanvasLooping image generation
    • In the loop => draw the picture every time the position is moved
    • canvas.toDataURLImage transferbase64
    • base64blob
    • blobfile
    • fileRequest interface file upload
    • Saved after upload returnedurl
  6. Get the array of images at the end of slice

code

<template>
  <el-upload
    action="" '"
    accept="jpg,jpeg,png,gif"
    :show-file-list="false"
    :auto-upload="false"
    :on-change="handleFileChange"
  >
    <el-button :loading="loading" size="mini" icon="el-icon-upload">To upload pictures</el-button>
    <div slot="tip" class="el-upload__tip">Only JPG/PNG files can be uploaded</div>
  </el-upload>
</template>

<script>
import Apis from '@/apis/common'

export default {
  data() {
    return {
      imgList: [].loading: false,}},methods: {
    async handleFileChange(file) {
      this.loading = true
      const fileObj = file.raw
      try {
        / / file base64
        const source = await this._fileToBase64(fileObj)
        // load img to get the img object
        const img = new Image()
        img.onload = async() = > {// Start cutting images after loading them
          const imgList = await this._createPiece(img)
          this.imgList = imgList
          this.loading = false
        }
        img.src = source
      } catch (e) {
        this.loading = false}},/** cut images to upload to the server * @params img object ** /
    async _createPiece(img) {
      const SPLIT_SIZE = 600 // Split size 600px
      const row = Math.ceil(img.naturalHeight / SPLIT_SIZE)

      let canvas = document.createElement('canvas')
      let ctx = canvas.getContext('2d')

      let wpiece = Math.floor(img.naturalWidth)
      let hpiece = Math.floor(img.naturalHeight / row)
      let src = ' '
      const imgList = []

      canvas.width = wpiece
      canvas.height = hpiece

      for (let i = 0; i < row; i++) {
        / / drawing
        ctx.drawImage(img, 0, i * hpiece, wpiece, hpiece, 0.0, wpiece, hpiece)
        / / canvas base64
        src = canvas.toDataURL()
        / / base64 blob
        const res = await fetch(src)
        const blob = await res.blob()
        // base 64 to file
        const file = new File([blob], 'File name', { type: 'image/png' })
        // file Uploads files to the file server
        const uploadRes = await Apis.UPLOAD_IMAGE(file)
        // Return the url where the image is stored
        const url = uploadRes.data.url
        // Save urls in slice order
        imgList.push(url)
      }
      return imgList
    },
    // Switch local file to base64
    _fileToBase64(file) {
      return new Promise((resolve, reject) = > {
        const reader = new FileReader()
        reader.onload = function (event) {
          let source = event.target.result
          resolve(source)
        }
        reader.onerror = reject
        reader.readAsDataURL(file)
      })
    },
  },
}
</script>

<style lang="scss">.image-src { .el-form-item__content { position: static; }}</style>
Copy the code

conclusion

So far, the four commonly used image uploading methods are introduced. The base64 method is generally used in small projects, and the traditional FormData or cloud service is more suitable for large file uploading. But switching to Base64 also makes it possible to edit images on the front end and preview them without uploading them to the server. The main takeaway is the handling of asynchronous operations. Everyone is welcome to suggest a better way or suggestion.