「笨辦法學前端」新增輪子一枚:原生 JS 實現的圖片上傳。javascript
預覽:Image Pickercss
源碼:html
{
let FormData = window.FormData
class ImagePicker {
constructor(options) {
let defaultOptions = {
element: null,
upload: {
url: '',
method: '',
inputName: '',
},
parseResponse: null,
fallbackImage: '',
}
this.options = Object.assign({}, defaultOptions, options)
this.checkOptions()
this.domRefs = {
img: this.options.element.querySelector('img'),
}
this.initHtml()
this.bindEvents()
}
checkOptions() {
let { element, upload: { url, method, inputName } } = this.options
if (!element || !url || !method || !inputName) {
throw new Error('Some option is required')
}
return this
}
initHtml() {
let { element } = this.options
let fileInput = (this.domRefs.fileInput = dom.create('<input type="file"/>'))
dom.append(element, fileInput)
}
willUpload(formData) {
this.options.element.classList.add('uploading')
this.domRefs.fileInput.disabled = true
}
didUpload(formData) {
let { element } = this.options
element.classList.remove('uploading')
this.domRefs.fileInput.disabled = false
this.domRefs.fileInput.value = ''
dom.dispatchEvent(element, 'uploaded')
}
failedUpload(formData) {
this.domRefs.fileInput.disabled = false
this.domRefs.fileInput.value = ''
dom.dispatchEvent(element, 'uploadFailed')
}
willDownload(path) {
this.options.element.classList.add('downloading')
}
didDownload(path) {
this.domRefs.img.src = path
let { element } = this.options
element.classList.remove('downloading')
dom.dispatchEvent(element, 'uploadedImageLoaded')
}
failedDownload(path) {
let { element, fallbackImage } = this.options
element.classList.remove('downloading')
if (fallbackImage) {
this.domRefs.img.src = fallbackImage
}
dom.dispatchEvent(element, 'uploadedImageFailed')
}
upload(formData) {
let { element, upload, parseResponse } = this.options
http(upload.method, upload.url, formData).then(
responseBody => {
let path = parseResponse(responseBody)
this.didUpload(formData)
this.willDownload(path)
prefetch(path).then(
() => {
this.didDownload(path)
},
() => {
this.failedDownload()
}
)
},
() => this.failedUpload(formData)
)
}
bindEvents() {
this.domRefs.fileInput.addEventListener('change', e => {
let { upload } = this.options
let formData = new FormData()
formData.append(upload.inputName, e.target.files[0])
this.willUpload(formData)
this.upload(formData)
})
}
}
window.ImagePicker = ImagePicker
function prefetch(url) {
return new Promise((resolve, reject) => {
let img = new Image()
img.onload = resolve
img.onerror = reject
img.src = url
})
}
function http(method, url, data) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
xhr.open(method, url)
xhr.onload = () => resolve(xhr.responseText, xhr)
xhr.onerror = () => reject(xhr)
xhr.send(data)
})
}
}複製代碼
<!DOCTYPE html>
<html lang="zh-Hans">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Image Picker</title>
<style> *{box-sizing: border-box;} body{ display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #362057; } .card { background: white; width: 20em; height: 80vh; box-shadow: 0 0 5px hsla(0,0%,0%,0.95); border-radius: 2px; display: flex; justify-content: flex-start; align-items: center; padding-top: 3em; flex-direction: column;} </style>
<style> .image-picker{ width: 100px; height: 100px; border-radius: 50%; overflow: hidden; position: relative; } .image-picker::after{ content:''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 50%; box-shadow: inset 0 0 5px hsla(264, 46%, 23%, 0.5); color: white; display: flex; justify-content: center; align-items: center; cursor: pointer; } .image-picker:hover::after{ content:'編輯'; background: hsla(0,0%,0%,0.2); } .image-picker>img{ max-width: 100%; max-height: 100%; } .image-picker>input[type=file]{ position: absolute; right:0; top: 0; width: 300%; height: 100%; z-index: 1; cursor: pointer; opacity: 0; } .image-picker.uploading::after{ content:'上傳中'; background: hsla(0,0%,0%,0.2); } .image-picker.downloading::after{ content:'處理中'; background: hsla(0,0%,0%,0.2); } </style>
<div class="card">
<div class="image-picker">
<img src="https://avatars0.githubusercontent.com/u/839559" width=100 height=100>
</div>
<p>點擊圖片編輯</p>
</div>
<script src="../lib/dom/index.js"></script>
<script src="view-source.js"></script>
<script src="../lib/image-picker/index.js"></script>
<script> new ImagePicker({ element: document.querySelector('.image-picker'), upload: { url: 'https://frankfang.com/image-server/upload', method: 'PUT', inputName: 'file' }, parseResponse: (response) => { response = JSON.parse(response) return `https://frankfang.com/image-server/upload/${response.key}` }, fallbackImage: 'https://avatars0.githubusercontent.com/u/839559' }) </script>
<!--百度統計-->
<script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?950926001a84a4f88cd3e1c7c0bfac08"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script>
</html>複製代碼
const express = require('express')
const multer = require('multer')
const cors = require('cors')
const upload = multer({ dest: 'uploads/' })
const p = require('path')
const app = express()
app.options('/upload', cors())
app.put('/upload', cors(), upload.single('file'), function (req, res, next) {
res.json({key: req.file.filename})
})
app.get('/upload/:key', cors(), function(req, res, next){
res.sendFile(`uploads/${req.params.key}`, {
root: __dirname,
headers:{
'Content-Type': 'image/jpeg',
},
}, (error)=>{
if(error){
res.status(404).send('Not found')
}
})
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})複製代碼
前端沒有任何依賴,沒有 webpack、npm,全手工打造,代碼工整,值得閱讀。前端
若是你對 ES6 不熟,那麼趕忙補課!java
ES 6 新特性彙總(一圖全覽)webpack