導航:
FormData
XMLHttpRequest
上傳前預覽圖片和文件
上傳進度信息
下載進度信息
<input type='file'>
new Image()
最后: 完整示例
(1) FormData()對象
提供一種表示表單數據的鍵值對構造方式,即用鍵值對來模擬一系列表單控件
( 即: 把form中所有表單元素的 name 和 value 組裝成一個queryString )
- 特點:相對于普通的ajax,使用FormData最大的優點是可以 - 異步上傳二進制文件
- 如果表單的
enctype
屬性設置為multipart/form-data
,則會使用表單的submit()方法來發送數據,即會使用和表單一樣的格式 - 可以使用XMLHttpRequest的
send()
方法來異步的提交這個"表單"
FormData對象如何使用?
-
FormData對象的方法都在原型鏈條上,自身沒有任何屬性和方法
FormData對象的操作方法都在原型鏈上
const formData = new FormData() ----------- FormData構造函數生成一個新的 FormData 對象
formData.append('name', 'wang') ----------- 添加數據
//通過append(key, value)來添加數據,如果 key不存在則會新增一條數據,如果key存在,則添加到數據的末尾
formData.get('name') ---------------------- 獲取數據,獲取key=name的第一個值
formData.getAll('name') -------------------- 獲取key=name的所有值
formData.set('name', 'zhang') -------------- 修改數據
formData.has('name') ----------------------- 是否存在
formData.delete('name') -------------------- 刪除數據
https://segmentfault.com/a/1190000006716454
https://blog.csdn.net/zqian1994/article/details/79635413
https://segmentfault.com/a/1190000012327982
https://segmentfault.com/a/1190000004664783
(2) XMLHttpRequest
XMLHttpRequest對象來發送一個Ajax請求,
XMLHttpRequest()構造函數初始化一個 XMLHttpRequest 對象,必須在所有其他方法被調用前調用構造函數。
- 如何獲取response?
xhr.response
和xhr.responseText
和xhr.responseXML
const api = new XMLHttpRequest();
方法:
(1) api.open() -------- 初始化HTTP請求參數(url, http方法等),但并不發送請求!!! 供 send() 方法使用
api.open(method, url, async, username, password)
method參數:是http請求的方法,值包括:GET,POST,HEAD
url參數:請求的地址
async參數:是否異步,默認是true,即異步的發送請求。
false,同步,對send()方法的調用將阻塞,直到響應完全接收
true或者省略,異步,且通常需要調用 onreadystatechange() 方法
(2) api.send() -------- 發送一個http請求,請求參數寫在send()方法的參數中
api.send(body)
get請求:參數可以直接寫在open()方法中
post請求:參數寫在該方法中,即body
-- 注意:body參數的數據類型會影響requestHeader中的 Content-Type 的默認值,如何手動指定則會覆蓋默認值
如果data是 Document 類型,同時也是HTML Document類型,
則content-type默認值為text/html;charset=UTF-8;否則為application/xml;charset=UTF-8;
如果data是 DOMString 類型,content-type默認值為text/plain;charset=UTF-8;
如果data是 FormData 類型,content-type默認值為multipart/form-data; boundary=[xxx]
如果data是其他類型,則不會設置content-type的默認值
(3) api.setRequestHeader() -------- 指定一個http請求的頭部,只有在readyState 為 1 的時候才能調用
api.setRequestHeader('name', 'value')
name參數:頭部名稱
value參數:頭部的值
注意: setRequestHeader()方法可以多次調用,最終的值不是覆蓋override而是追加append
注意: setRequestHeader()方法只有在readyState為1時才能調用,即open()方法之后,send()方法之前
(4) api.getResponseHeader() -------- 返回指定的 HTTP 響應頭部的值
(5) api.abort() -------- 取消當前響應,關閉連接并且結束任何未決的網絡活動
api.abort()將readystate重置為0
應用:如果請求用了太長時間,而且響應不再必要的時候,可以調用這個方法。
abort:是中止的意思
(6) api.onreadystatechange() -------- 在 readyState 狀態改變時觸發
api.onreadystatechage() :當 readyState 為 3 時,它也可能調用多次。
注意:onreadystatechange()所有字符都是小寫,但readyState又是駝峰寫法
readyState狀態:
0 UNSENT ---- xhr對象成功構造,open()方法未被調用
1 OPENED ---- open()被調用,send()還未被調用,setRequestHeader()可以被調用
2 HEADERS_RECEIVED ---- send()方法已經被調用, 響應頭和響應狀態已經返回
3 LOADING ---- 響應體(response entity body)正在下載中,此狀態下通過xhr.response可能已經有了響應數據
4 DONE ---- 整個數據傳輸過程結束,不管本次請求是成功還是失敗
(7)api.onload -------- 請求成功時觸發,此時readyState為4
重要:所有請求成功的回調:
1.除了在api.onreadystatechange指定的回調函數的readyState===4時取值
2.也可以在api.onload事件中取值,如
api.onload = function () {
//如果請求成功
if(api.status == 200){
//do successCallback
}
}
3.注意判斷api.status===200是有坑的,因為成功時返回的狀態碼不止有200,下面的寫法更靠譜
靠譜的寫法:當http狀態碼為2xx或304時才認為成功
api.onload = function () {
//如果請求成功
if((api.status >= 200 && api.status < 300) || api.status == 304){
//do successCallback
}
}
(8)api.timeout --------- timeout屬性用來設置過期時間
問題1:請求的開始時間怎么確定?是api.onloadstart事件觸發的時候,也就是api.send()調用的時候
解析:因為api.open()只是創建了鏈接,當并沒有真正傳輸數據,只有調用api.send()時才真正開始傳輸
問題2:什么時候是請求結束?
解析:api.loadend事件觸發時結束
(9)
api.onprogress 下載進度信息
api.upload.onprogress 上傳進度信息
api.upload.onprogress = function(e) {
if ( e.lengthComputable ) {
const present = e.loaded / e.total * 100;
}
}
-----------------------------------------------------------------------------------------------
1.
問題1:如何獲取response
提供三個屬性來獲取response:( api.response ) 和 ( api.responseText ) 和 ( responseXML )
api.responseText --- api.responseType='text'、''、不設置時,xhr對象上才有此屬性,此時才能調用
api.response --- responseType為""或"text"時,值為"";responseType為其他值時,值為 null
2.
問題2:api.responseType有哪些類型
api.responseType類型有:text, document, json, blob, arrayBuffer
get請求
go() {
console.log('1111111111');
const api = new XMLHttpRequest();
api.open('GET', ----- 初始http請求參數,請求方式,url, 是否異步
'http://image.baidu.com/channel/listjson?pn=0&rn=30&tag1=明星&tag2=全部&ie=utf8', true);
api.responseType = 'text'; ------ 文本格式的響應
api.timeout = 5000; ---- 請求過期時間
api.setRequestHeader('Content-type', 'application/json'); ----- 必須在open()后,send()前設置
api.onreadystatechange = function() { ------ readyState改變時觸發
if ( api.readyState === 4 && this.status === 200) { ---- this指的是api實例
console.log(JSON.parse(this.responseText)) ------ this.response也能拿到同樣的數據
}
}
// 除了在api.onreadystatechange指指定的會調中判斷readyState===4,也可以直接在onload中觸發
// 兩種方法都可以
// 只判斷200狀態碼不完善,應該判斷 2xx 或者 304 則請求成功
api.onload = function() {
if ( api.status >= 200 && api.status < 300 || api.status === 304 ) {
console.log(JSON.parse(api.responseText), 'onload在請求成功時觸發');
}
}
api.send(); ---- 發送數據
}
http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html 大家
https://segmentfault.com/a/1190000004322487重要
http://www.w3school.com.cn/xmldom/dom_http.asp
https://blog.csdn.net/harryhare/article/details/80778066四種 post 請求的寫法
https://www.cnblogs.com/wgbs25673578/p/5056300.html 可以
(3) <input type="file"/>圖片上傳
- type屬性:規定input元素的類型
- multiple屬性:上傳多張圖片或多個文件
- accept屬性:允許上傳的文件類型
--- image/*:上傳所有圖片類型,注意會影響速度
--- image/gif,image/jpeg,image/jpg,image/png ---- 直接寫出來則不會影響速度 - 修改默認樣式,自帶的樣式很丑,且后面帶有未選擇任何文件的文字,選中后的文件名
思路:隱藏<input type="file"/>,使用自定義的button通過$refs去觸發文件上傳,實現自定義顯示
- 上傳同一個文件,不會觸發change事件,即使該文件做過修改
思路:文件上傳之后,處理完文件,將<input type="file" />的value設置為null,這樣下次即使上傳的是同一個文件,仍然會觸發change事件
- display: none不占文檔空間
- visibility: hidden占據空間
<template>
<div class="hello">
<div>圖片上傳</div>
<br>
<div class="upload-file" @click="upFile">
<div>+</div>
</div>
<span v-for="(item, index) in filesName" :key="index" style="margin-right: 10px;">{{item}}</span>
<input
type="file"
class="input-file"
ref="input-file"
@change="upload"
accept="image/gif, image/jpeg"
multiple="multiple"
>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
filesName: []
}
},
methods: {
upFile() {
this.$refs['input-file'].click(); // 點擊觸發input上的click事件,偷梁換柱,觸發文件上傳
},
upload(e) {
const files = e.target.files || e.dataTransfer.files;
if (!files.length) {
this.filesName = [];
return;
}
console.log(files)
this.filesName = Array.from(files).map(v => v.name);
// 走上傳接口
const body = new FormData();
bocy.append('upload-files', files);
// ajax send到接口,此處省略
},
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
.input-file {
display: none;
}
.upload-file {
background: rgb(41, 129, 245);
display: inline-block;
width: 70px;
height: 30px;
line-height: 30px;
margin: 0 auto;
border-radius: 4px;
color: white;
cursor: pointer;
user-select: none;
}
</style>
https://segmentfault.com/a/1190000013799753
https://www.cnblogs.com/fozero/p/8835628.html
http://www.lxweimin.com/p/c619b81e8f04
(4) <input type='file' />上傳前預覽圖片
讀取本地圖片,有兩種方法:FileReader 和 URL.createObjectURL
-----URL.createObjectURL性能更好
objectURL = URL.createObjectURL(blob)
- blob:是用來創建 URL 的 ( File對象 ) 或 ( Blob對象 )
//獲取本地圖片地址
getObjectURL(file) { ---------------- 傳入file對象
let url = null;
if (window.createObjectURL != undefined) { ---------- 判斷不同瀏覽器類型
// basic
url = window.createObjectURL(file);
} else if (window.webkitURL != undefined) {
// webkit or chrome
url = window.webkitURL.createObjectURL(file);
} else if (window.URL != undefined) {
// mozilla(firefox)
url = window.URL.createObjectURL(file);
}
return url;
}
完整案例:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div class="up-btn" @click="upBtn">上傳圖片</div>
<input
type="file"
style="display: none"
accept="image/jpg, image/png, image/jpeg, image/gif"
multiple="multiple"
@change="upImg($event, 'fiel1')"
ref="input-file"
/>
<br>
<div style="color: red">{{errMessage}}</div>
<div class="imgs-wrap" v-for="(item, index) in imgMessage" :key="index">
<span><img :src="item.url" alt="" width="200" height="100"></span>
<span>{{item.name}}</span>
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
imgMessage: [],
errMessage: ''
}
},
methods: {
upBtn() {
this.$refs['input-file'].click()
},
upImg(e, type) {
const files = e.target.files || e.dataTransfer.files;
this.localImg(files);
},
localImg(files) {
this.errMessage = ''; // 清除錯誤信息
const imgs = Array.from(files)
.map((item, index) => {
// 先過濾掉重復上傳的圖片
if ( this.imgMessage.map(item => item.name).includes(item.name) ) {
this.errMessage = '有重復項';
return;
}
// 本地圖片url
const localImgUrl = this.getLocalImgUrl(item);
return {
name: files[index].name,
url: localImgUrl,
}
})
.filter(Boolean); // 過濾掉在map中return回來的undefine
this.imgMessage = this.imgMessage.concat(imgs);
},
// 獲取上傳前圖片url
getLocalImgUrl(file) {
let localImgUrl = null;
if ( window.createObjectURL != undefined ) {
localImgUrl = window.createObjectURL(file);
} else if ( window.webkitURL != undefined ) {
localImgUrl = window.webkitURL.createObjectURL(file);
} else if ( window.URL != undefined ) {
localImgUrl = window.URL.createObjectURL(file);
}
return localImgUrl;
},
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.up-btn {
display: inline-block;
margin: 20px 10px auto;
padding: 6px 20px;
border-radius: 4px;
cursor: pointer;
user-select: none;
color: white;
background: rgb(255, 0, 221);
}
.up-btn:hover {
background: rgb(133, 2, 115);
}
.imgs-wrap {
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
</style>
https://blog.csdn.net/weixin_38023551/article/details/78318532
https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
https://www.cnblogs.com/stephenykk/p/3558887.html
(5) 上傳進度信息和下載進度信息
onprogress事件用來返回進度信息
- xhr.onprogress = updateProgress; -------------------------- 下載進度信息
- xhr.upload.onprogress = updateProgress; ---------------- 上傳進度信息
api.upload.onprogress=function (e){
if(e.lengthComputable){
var precent=100 * e.loaded/e.total;
console.log(precent);
}
}
解析:
1. e.total -------------------- 傳輸的總字節
2. e.loaded ------------------- 已經傳輸的字節
3. e.lengthComputable --------- 指定上傳總數居的大小(上傳文件總大小已知)
4. e.transferSpeed ------------ 傳輸速度
5. e.timeRemaining ------------ 剩余時間
(5) blob對象
blob對象表示一個不可變的,原始數據的類文件對象。File接口基于Blob,Blob對象可以看作是存放二進制數據的容器
##### new Blob(dataArr:Array<any>, opt:{type:string});
第一個參數:是要添加到Bolb對象的數據數組,注意:是一個數組
第二個參數:配置對象
- 構造函數:Blob()
- 返回一個新創建的 Blob 對象,其內容由參數中給定的數組串聯組成
- 屬性:
Blob.size:blob中包含數據的大小
Blob.type:一個字符串,表明該Blob的對象所包含數據MIME類型
http://www.cnblogs.com/hhhyaaon/p/5928152.html 應用場景( 分片上傳 )
https://blog.csdn.net/qq_42842709/article/details/82500029
(6) new Image() ----- 用于生成 HTMLImageElement 實例
參數:new Image(width, height)
---- 接受兩個參數,分別表示寬度和高度
屬性:src,currentSrc,alt,srcset,sizes
注意:用js生成的img實例,并不在文檔中,需要手動插入
事件:
- onload:圖像加載完成,會觸發onload屬性指定的回調函數
- onerr0r:圖像加載完成,同時也會觸發onerror屬性指定的回調函數
注意事項:
生成的image一定要手動插入到文檔,onload和onerror的回調都要指定
mounted() {
const limg = require('../images/1.jpg');
const img = new Image(200, 200); ------------- 參數分別是 width 和 height
img.src = limg; ---------------- 除了src,還有currentSrc表示當前src,因為src可以動態指定
img.onload = function() {
console.log('加載完成');
document.body.appendChild(img); -------------- 插入文檔
}
img.onerror = function() {
console.log('錯誤')
}
}
完整示例
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div class="up-btn" @click="upBtn">上傳圖片</div>
<input
type="file"
style="display: none"
accept="image/jpg, image/png, image/jpeg, image/gif"
multiple="multiple"
@change="upImg($event, 'fiel1')"
ref="input-file"
/>
<br>
<div style="color: red">{{errMessage}}</div>
<div>上傳進度:{{persent}}</div>
<div class="imgs-wrap" v-for="(item, index) in imgMessage" :key="index">
<span><img :src="item.url" alt="" width="200" height="100"></span>
<span>{{item.name}}</span>
</div>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
imgMessage: [],
errMessage: '',
persent: 0,
}
},
methods: {
upBtn() {
this.$refs['input-file'].click()
},
upImg(e, type) {
const files = e.target.files || e.dataTransfer.files;
this.viewLocalImages(files);
this.uploadAllImg(files);
},
// 獲取上傳前圖片url
getLocalImgUrl(file) {
let localImgUrl = null;
if ( window.createObjectURL != undefined ) {
localImgUrl = window.createObjectURL(file);
} else if ( window.webkitURL != undefined ) {
localImgUrl = window.webkitURL.createObjectURL(file);
} else if ( window.URL != undefined ) {
localImgUrl = window.URL.createObjectURL(file);
}
return localImgUrl;
},
// 預覽上傳前圖片
viewLocalImages(files) {
this.errMessage = ''; // 清除錯誤信息
const imgs = Array.from(files)
.map((item, index) => {
// 先過濾掉重復上傳的圖片
if ( this.imgMessage.map(item => item.name).includes(item.name) ) {
this.errMessage = '有重復項';
return;
}
// 本地圖片url
const localImgUrl = this.getLocalImgUrl(item);
return {
name: files[index].name,
url: localImgUrl,
}
})
.filter(Boolean); // 過濾掉在map中return回來的undefine
this.imgMessage = this.imgMessage.concat(imgs);
},
// 上傳圖片
uploadAllImg(files) {
// 構造表單
const form = new FormData();
Array.from(files).forEach((item, index) => {
form.append(`file${index}`, item)
});
// ajax
const api = new XMLHttpRequest();
api.open('POST', '............cmsiw/attachment/upload', true);
// api.setRequestHeader()必須在open()之后,send()之前
api.setRequestHeader('Authorization', 'Bearer .....');
// api.setRequestHeader('Content-type', 'application/json;charset=utf-8');
api.responseType = 'text';
api.timeout = 10000;
// 上傳進度
const that = this;
api.upload.onprogress = function(e) {
if (e.lengthComputable) {
const persent = e.loaded / e.total * 100;
console.log(persent);
that.persent = persent;
}
}
// 上傳成功
api.onreadystatechange = function() {
if (api.readyState === 4 && api.status === 200 ) { // 200用最好 >=200 && <300 || 304代替
console.log(JSON.parse(api.responseText));
}
}
api.send(form);
}
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.up-btn {
display: inline-block;
margin: 20px 10px auto;
padding: 6px 20px;
border-radius: 4px;
cursor: pointer;
user-select: none;
color: white;
background: rgb(255, 0, 221);
}
.up-btn:hover {
background: rgb(133, 2, 115);
}
.imgs-wrap {
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
</style>
復習: 2019/3/22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script>
window.onload = function() {
const fileDom = document.getElementById('file');
// 本地預覽
fileDom.addEventListener('change', function(e) {
const files = e.target.files;
const file = e.target.files[0];
console.log(files);
// 插入名字
const imgName = Array.from(files)[0].name;
const imgNameDom = document.createElement('span')
imgNameDom.innerHTML = imgName;
document.getElementById('content').appendChild(imgNameDom);
// 插入圖片
Array.from(files).forEach(item => {
let url = null;
if (window.createObjectURL) {
url = window.createObjectURL(item);
} else if ( window.URL) {
url = window.URL.createObjectURL(item);
} else if ( window.webkitURL) {
url = window.webkitURL.createObjectURL(item);
}
const img = new Image(40, 40);
img.src = url;
img.onload = function() {
document.getElementById('content').appendChild(img);
}
})
}, false)
};
</script>
</head>
<body>
<div id="file-wrap" style="position: relative">
<div
style="
cursor: pointer;
background:blueviolet;
color: white;
width: 100px;
text-align: center;
margin: 0 auto;
border-radius: 8px;
height: 30px;
line-height: 30px;
">上傳</div>
<input
type="file"
multiple="multiple"
accept="image/*"
style="
opacity: 0;
position: absolute;
border: 1px solid red;
z-index: 99999;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 30px;
width: 100px;
"
id="file"
>
</div>
<br>
<div id="content" style="width: 600px; height: 600px; border: 1px solid black; margin: 0 auto">
</div>
</body>
</html>