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
- Sends a request to the backend to obtain OSS configuration data
- File upload, call OSS to provide interfaces
- Path where the uploaded file is stored on the server
- 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
- Set the upload path, field name, header, and data parameters of the uploaded file
- After the upload is successful, return to the server storage path
- 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
- Create a new file that encapsulates the method of asynchronous transfer to Base64
- Select local file when adding goods and select Save the entire File object with objects
- 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
- Utils. Js file adds an online image-to-base64 method, using canvas
- Edit the product, first pull the original product information display to the page
- 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:
- Listening to the
input
Events forfile
object - through
new FileReader()
The fileinto
base64` - through
new Image()
Local loadbase64
The picture - After the image is loaded, pass
naturalWidth
naturalHeight
Calculate the number of cutting sheets and the width and height of each cut - Begin to use
canvas
Looping image generation -
- In the loop => draw the picture every time the position is moved
canvas.toDataURL
Image transferbase64
base64
转blob
blob
转file
file
Request interface file upload- Saved after upload returned
url
- 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.