著web功能越來(lái)越強(qiáng)大,我們很多時(shí)候需要在web頁(yè)面來(lái)獲取攝像頭進(jìn)行操作,原生html5提供了對(duì)攝像頭的支持,需要用戶的同意授權(quán),下面是一個(gè)基于 HTML5 的調(diào)用攝像頭拍照并上傳后臺(tái)的示例代碼:
html復(fù)制代碼<!DOCTYPE html>
<html>
<head>
<title>拍照上傳</title>
</head>
<body>
<video id="video" style="width:300px;height:200px;"></video>
<br>
<button id="btn-start">啟動(dòng)攝像頭</button>
<button id="btn-stop">停止攝像頭</button>
<button id="btn-capture">拍照上傳</button>
<br>
<canvas id="canvas"></canvas>
<form id="form-upload" method="post" enctype="multipart/form-data">
<input type="file" id="input-file" name="file"/>
</form>
<script type="text/javascript">
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 啟動(dòng)攝像頭
document.getElementById('btn-start').addEventListener('click', function() {
navigator.mediaDevices.getUserMedia({
video: true,
audio: false
}).then(function(stream) {
video.srcObject = stream;
}).catch(function(err) {
console.log("啟動(dòng)攝像頭失敗:" + err);
});
});
// 停止攝像頭
document.getElementById('btn-stop').addEventListener('click', function() {
video.pause();
video.srcObject.getTracks()[0].stop();
video.srcObject = null;
});
// 拍照,并上傳到后臺(tái)
document.getElementById('btn-capture').addEventListener('click', function() {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0);
canvas.toBlob(function(blob) {
var formData = new FormData();
formData.append('file', blob, 'photo.jpg');
postRequest('/upload', formData, function(res) {
alert(res.message);
});
}, 'image/jpeg');
});
// 發(fā)送 POST 請(qǐng)求
function postRequest(url, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var res = JSON.parse(xhr.responseText);
callback(res);
}
};
xhr.send(data);
}
</script>
</body>
</html>
上述代碼主要分為以下幾個(gè)部分:
最后,需要注意的是,在本地調(diào)試和開發(fā)時(shí),特別是在 Windows 操作系統(tǒng)下使用 Chrome 瀏覽器訪問(wèn)時(shí),可能會(huì)遇到攝像頭不能正常運(yùn)行的情況。這時(shí)可以打開地址欄,在目標(biāo)請(qǐng)求前加上 --unsafely-treat-insecure-origin-as-secure="http://localhost:8080" 參數(shù)(其中端口號(hào)需替換成實(shí)際的本地服務(wù)端口),即可獲得權(quán)限,進(jìn)行攝像頭使用。
基于Electron實(shí)現(xiàn)的pc端智能驗(yàn)機(jī)應(yīng)用,近期迭代了一個(gè)新的功能,需求是通過(guò)電腦外接攝像頭對(duì)手機(jī)屏幕進(jìn)行拍照,拍照后需將照片上傳至服務(wù)端進(jìn)行屏幕信息比對(duì),確定被檢測(cè)屏幕是否為原廠屏。
根據(jù)上面的需求,分析大概要以下幾個(gè)步驟。
確定了需要以上四個(gè)步驟后,接下來(lái)一步一步實(shí)現(xiàn)。
由于 Electron 內(nèi)置了 Chromium 瀏覽器,該瀏覽器對(duì)各項(xiàng)前端標(biāo)準(zhǔn)都支持得非常好,所以基于 Electron 開發(fā)應(yīng)用不會(huì)遇到瀏覽器兼容性問(wèn)題。幾乎可以在 Electron 中使用所有 HTML5、CSS3 、ES6 標(biāo)準(zhǔn)中定義的 API。
所以基于WebRTC提供的API即可獲取到攝像頭的視頻流。
代碼如下:
methods: {
getUserMedia() {
/* 可同時(shí)開啟video(攝像頭)和audio(麥克風(fēng)) 這里只請(qǐng)求攝像頭,所以只設(shè)置video為true */
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
/* 使用這個(gè) stream 傳遞到成功回調(diào)中 */
this.success(stream)
})
.catch(function(err) {
/* 處理 error 信息 */
this.error(error)
});
}
}
MediaDevices.getUserMedia() 會(huì)提示用戶給予使用媒體輸入的許可,媒體輸入會(huì)產(chǎn)生一個(gè)MediaStream,里面包含了請(qǐng)求的媒體類型的軌道。此流可以包含一個(gè)視頻軌道(來(lái)自硬件或者虛擬視頻源,比如相機(jī)、視頻采集設(shè)備和屏幕共享服務(wù)等等)、一個(gè)音頻軌道(同樣來(lái)自硬件或虛擬音頻源,比如麥克風(fēng)、A/D 轉(zhuǎn)換器等等),也可能是其它軌道類型。
它返回一個(gè) Promise 對(duì)象,成功后會(huì)resolve回調(diào)一個(gè) MediaStream 對(duì)象。若找不到滿足請(qǐng)求參數(shù)的媒體類型,promise會(huì)reject回調(diào)一個(gè)NotFoundError。
現(xiàn)在已經(jīng)成功獲取到視頻流,接下來(lái)就是將視頻流回顯到頁(yè)面。這里使用video標(biāo)簽完成,代碼如下:
<template>
<div class="video-page">
<div class="video-content">
<video ref="video" class="video-item"></video>
</div>
</div>
</template>
export default {
methods: {
getUserMedia() {
/* 可同時(shí)開啟video(攝像頭)和audio(麥克風(fēng)) 這里只請(qǐng)求攝像頭,所以只設(shè)置video為true */
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
/* 使用這個(gè) stream 傳遞到成功回調(diào)中 */
this.success(stream)
})
.catch(function(err) {
/* 處理 error 信息 */
this.error(error)
});
},
success(stream) {
console.log('成功', stream);
/* 將stream 分配給video標(biāo)簽 */
this.$refs.video.srcObject = stream;
this.$refs.video.play();
}
}
}
這時(shí),攝像頭中的畫面就可以顯示在頁(yè)面video標(biāo)簽內(nèi),如下圖。
為了用戶體驗(yàn),在進(jìn)入頁(yè)面之前添加了判斷攝像頭是否已經(jīng)接入并可用的邏輯,避免用戶的攝像頭未接入或者啟動(dòng),造成應(yīng)用不可用的錯(cuò)覺(jué)。
使用MediaDevices.enumerateDevices()來(lái)獲取可用媒體輸入和輸出設(shè)備的列表,例如攝像頭、麥克風(fēng)、耳機(jī)等。
navigator.mediaDevices.enumerateDevices().then(devicesList => {
console.log('------devicesList', deviceList)
})
得到的設(shè)備列表數(shù)據(jù)格式如下:
kind類型有三種,分別是audioinput、audiooutput和videoinput。分別代表音視頻的輸入和輸出??稍诹斜碇胁檎夷繕?biāo)媒體是否已經(jīng)接入且可用。
若有選擇切換設(shè)備需求,可根據(jù)kind類型進(jìn)行媒體設(shè)備分類,選擇目標(biāo)deviceId,傳入navigator.mediaDevices.getUserMedia,完成來(lái)源切換。
navigator.mediaDevices.getUserMedia({ video: { deviceId: xxxx } })
拍照其實(shí)就是截取視頻中的某一幀,這里使用canvas來(lái)實(shí)現(xiàn)截取。getContext() 方法可返回一個(gè)對(duì)象,該對(duì)象提供了用于在畫布上繪圖的方法和屬性。其中drawImage()方法用來(lái)向畫布上繪制圖像、畫布或視頻。
<template>
<div class="video-page">
<div class="video-content">
<video ref="video" class="video-item" v-if="showVideo"></video>
<canvas ref="canvas" v-else width="500" height="346"></canvas>
<div class="video-buttons">
<div @click="capture" class="button-item capture">拍照</div>
<div @click="submit" class="button-item submit"}">提交</div>
</div>
</div>
</template>
export default {
data: {
showVideo: true, // 是否展示攝像頭畫面
},
methods: {
/* 拍照按鈕點(diǎn)擊 */
capture() {
this.showVideo = false
var context = this.$refs.canvas.getContext('2d');
/* 要跟video的寬高一致 */
context.drawImage(this.$refs.video, 0, 0, 1000, 692, 0, 0, 500, 346);
}
}
}
拍照的圖片回顯至canvas標(biāo)簽。
上個(gè)步驟已經(jīng)完成了拍照,接下來(lái)就需要將圖片上傳至CDN,拿到圖片鏈接。這里有兩種方式可以實(shí)現(xiàn)獲取圖片數(shù)據(jù)。
HTMLCanvasElement.toBlob() 方法生成 Blob 對(duì)象,用以展示 canvas 上的圖片。因?yàn)橹苯涌梢阅玫綀D片文件,所以無(wú)需再使用方法2中的函數(shù)來(lái)轉(zhuǎn)化base64,直接可以獲取到圖片文件用來(lái)上傳。
toBlob(callback, type, quality)
callback:回調(diào)函數(shù),參數(shù)為Blob對(duì)象(目標(biāo)圖片文件)。
type:圖片格式,默認(rèn)為image/png 可選。
quality:0-1的數(shù)字,表示圖片質(zhì)量,可選。
點(diǎn)擊提交按鈕按鈕時(shí),先獲取圖片文件,為上傳做準(zhǔn)備。
methods: {
/* 提交按鈕點(diǎn)擊 */
submit() {
const base64Url = this.$refs.canvas.toBlob(blob => {
console.log('===blob', blob)
const data = new FormData()
data.append('file', blob)
request.post('https://XXXXX/upload', data)
}, "image/jpeg", 0.95)
}
}
console的結(jié)果如下圖:
HTMLCanvasElement.toDataURL()方法返回一個(gè)包含圖片展示的Data URL。
Data URL,即前綴為 data: 協(xié)議的 URL,其允許內(nèi)容創(chuàng)建者向文檔中嵌入小文件。
canvas.toDataURL(type, encoderOptions);
type 圖片格式,默認(rèn)為image/png。
encoderOptions 0到1之間的值,用來(lái)選定圖片質(zhì)量,默認(rèn)值是0.92,超出范圍會(huì)使用默認(rèn)值。
base64組成的圖片源數(shù)據(jù),上傳前需轉(zhuǎn)為圖片文件。這里封裝了一個(gè)convertBase64UrlToImgFile函數(shù)用來(lái)轉(zhuǎn)換。代碼如下:
<template>
<div class="video-page">
<div class="video-content">
<video ref="video" class="video-item" v-if="showVideo"></video>
<canvas ref="canvas" v-else width="500" height="346"></canvas>
<div class="video-buttons">
<div @click="capture" class="button-item capture">拍照</div>
<div @click="submit" class="button-item submit">提交</div>
</div>
</div>
</template>
export default {
data: {
/* 是否展示攝像頭畫面 */
showVideo: true,
},
methods: {
/* 將base64轉(zhuǎn)為圖片文件 */
convertBase64UrlToImgFile(urlData, fileType) {
const imgData = urlData.split('base64,').splice(-1)[0]
/* 解碼使用 base-64 編碼的字符串 轉(zhuǎn)換為byte */
const bytes = window.atob(imgData)
/* 處理異常,將ASCII碼小于0的轉(zhuǎn)換為大于0 */
const ab = new ArrayBuffer(bytes.length)
const ia = new Int8Array(ab)
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i)
}
/* 轉(zhuǎn)換成文件,可以添加文件的type,lastModifiedDate屬性 */
const blob = new Blob([ab], { type: fileType })
blob.lastModifiedDate = new Date()
return blob
},
/* 提交按鈕點(diǎn)擊 */
async submit() {
const base64Url = this.$refs.canvas.toDataURL()
const imgFile = this.convertBase64UrlToImgFile(base64Url, 'image/jpg')
console.log('====imgFile', imgFile)
const data = new FormData()
data.append('file', imgFile)
/* 上傳 */
request.post('https://XXXXX/upload', data)
},
}
}
convertBase64UrlToImgFile可用于在使用canvas外的場(chǎng)景進(jìn)行base64轉(zhuǎn)換圖片文件。和HTMLCanvasElement.toBlob()方法得到的結(jié)果一致。
以上兩種方法都可以完成圖片上傳,最終拿到CDN圖片鏈接后可傳給后端進(jìn)行處理。獲取屏幕信息。
通過(guò)以上四個(gè)步驟就完成了Electron應(yīng)用中通過(guò)外接攝像頭拍照并上傳的功能。這里基本用不到Electron的能力,和在web端的實(shí)現(xiàn)方式并無(wú)區(qū)別,Electron在這里起到的作用就是獲取攝像頭媒體流不需要獲取用戶權(quán)限。
Electron是基于Chromium和Node.js實(shí)現(xiàn)的,這就使前端開發(fā)者可以使用JavaScript、HTML和CSS輕松構(gòu)建跨平臺(tái)的桌面應(yīng)用。Electron可以使用幾乎所有的Web前端生態(tài)領(lǐng)域及Node.js生態(tài)領(lǐng)域的組件和技術(shù)方案。
后續(xù)會(huì)介紹Electron在智能驗(yàn)機(jī)應(yīng)用中的實(shí)踐方案,敬請(qǐng)期待~
作者:謝星
來(lái)源:微信公眾號(hào):大轉(zhuǎn)轉(zhuǎn)FE
出處:https://mp.weixin.qq.com/s/Yb590oc6u94feS9s7FZWrQ
天在博客中加了個(gè)新的功能,直接復(fù)制圖片然后黏貼上傳到服務(wù)器。
之前是自己弄的markdown編輯器,很多功能不完善,剛開始加了個(gè)插入圖片的按鈕,現(xiàn)在能直接復(fù)制圖片方便多了。
目前只支持chrome瀏覽器,哈哈,感覺(jué)chrome是最好用的瀏覽器。
主要原理是利用paste事件,然后拿到里面的圖片數(shù)據(jù)上傳到服務(wù)器。
下面貼代碼
<textarea id="content" />
有個(gè)編輯的文本框,然后監(jiān)聽parse事件
document.querySelector('#content').addEventListener('paste', function(event) {
var isChrome = false;
if (event.clipboardData || event.originalEvent) {
//not for ie11 某些chrome版本使用的是event.originalEvent
var clipboardData = (event.clipboardData || event.originalEvent.clipboardData);
if (clipboardData.items) {
// for chrome
var items = clipboardData.items, len = items.length, blob = null;
isChrome = true;
for (var i = 0; i < len; i++) {
if (items[i].type.indexOf("image") !== -1) {
//getAsFile() 此方法只是living standard firefox ie11 并不支持
blob = items[i].getAsFile();
}
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。