vue如何調(diào)用攝像頭實(shí)現(xiàn)拍照上傳圖片、本地上傳圖片
今天項(xiàng)目上遇到一個(gè)功能,人臉?shù)浫耄笥脩艨梢宰约赫{(diào)用攝像頭拍照上傳圖片,還可以上傳本地圖片,這個(gè)功能并沒(méi)有那么復(fù)雜,今天就封裝一下這個(gè)功能,首先咱們看一下效果。
下面是封裝好的效果,因?yàn)榉庋b的時(shí)候做了頁(yè)面銷(xiāo)毀關(guān)閉攝像頭,所以加了一個(gè)打開(kāi)攝像頭的功能,我們可以自己拍照,也有本地上傳按鈕

下面是拍照效果,左邊部分是你點(diǎn)擊拍照上傳服務(wù)器返回來(lái)的地址,然后顯示效果,右邊就是咱們上面說(shuō)的封裝好的組件

下面看一下組件實(shí)現(xiàn)的代碼
主要功能邏輯就是調(diào)用是攝像頭權(quán)限,把實(shí)時(shí)畫(huà)面顯示到video里面,點(diǎn)擊拍照的時(shí)候繪制vanvas,然后把canvas轉(zhuǎn)base64圖片,base64圖片轉(zhuǎn)文件進(jìn)行上傳,上傳圖片就不多介紹了,就是Element文件上傳,配置action,headers 就行。下面直接上代碼吧。
<template>
<div class="camera_outer">
<video id="videoCamera" :width="videoWidth" :height="videoHeight" autoplay></video>
<canvas id="canvasCamera" style="display:none;" :width="videoWidth" :height="videoHeight"></canvas>
<div style="display: flex;justify-content: space-between;align-items: center;">
<el-button type="primary" @click="setImage()">拍照</el-button>
<el-upload style="display: flex;align-items: center;" :headers="headers" :show-file-list="false"
:on-success="handleAvatarSuccess" class="avatar-uploader" action="/admin/sys-file/upload">
<el-button type="primary">上傳照片</el-button>
</el-upload>
<el-button @click="getCompetence()">打開(kāi)攝像頭</el-button>
</div>
</div>
</template>
<script>
import store from '@/store'
import { uploadImg } from '@/api/visitinfo'
export default {
data() {
return {
videoWidth: 300,
videoHeight: 300,
imgSrc: '',
thisCancas: null,
thisContext: null,
thisVideo: null,
headers: {
Authorization: 'Bearer ' + store.getters.access_token
}
}
},
mounted() {
this.getCompetence()
},
destroyed() {
this.stopNavigator()
},
methods: {
handleAvatarSuccess(res, file) {
this.$emit('refreshDataList', res.data.url)
},
// 調(diào)用權(quán)限(打開(kāi)攝像頭功能)
getCompetence() {
var _this = this
this.thisCancas = document.getElementById('canvasCamera')
this.thisContext = this.thisCancas.getContext('2d')
this.thisVideo = document.getElementById('videoCamera')
// 舊版本瀏覽器可能根本不支持mediaDevices,我們首先設(shè)置一個(gè)空對(duì)象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {}
}
// 一些瀏覽器實(shí)現(xiàn)了部分mediaDevices,我們不能只分配一個(gè)對(duì)象
// 使用getUserMedia,因?yàn)樗鼤?huì)覆蓋現(xiàn)有的屬性。
// 這里,如果缺少getUserMedia屬性,就添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先獲取現(xiàn)存的getUserMedia(如果存在)
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia
// 有些瀏覽器不支持,會(huì)返回錯(cuò)誤信息
// 保持接口一致
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'))
}
// 否則,使用Promise將調(diào)用包裝到舊的navigator.getUserMedia
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
var constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: 'scaleX(-1)' } }
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
// 舊的瀏覽器可能沒(méi)有srcObject
if ('srcObject' in _this.thisVideo) {
_this.thisVideo.srcObject = stream
} else {
// 避免在新的瀏覽器中使用它,因?yàn)樗诒粭売谩?
_this.thisVideo.src = window.URL.createObjectURL(stream)
}
_this.thisVideo.onloadedmetadata = function (e) {
_this.thisVideo.play()
}
}).catch(err => {
console.log(err)
})
},
// 繪制圖片(拍照功能)
setImage() {
var _this = this
// 點(diǎn)擊,canvas畫(huà)圖
_this.thisContext.drawImage(_this.thisVideo, 0, 0, _this.videoWidth, _this.videoHeight)
// 獲取圖片base64鏈接
var image = this.thisCancas.toDataURL('image/png')
_this.imgSrc = image
const file = image
const time = (new Date()).valueOf()
const name = time + '.png'
const conversions = this.base64ToFile(file, name)
const data = new FormData()
data.append('file', conversions)
uploadImg(data).then(res => {
if (res.data.code == 0) {
this.$emit('refreshDataList', res.data.data.url)
}
})
},
// base64圖片轉(zhuǎn)file的方法(base64圖片, 設(shè)置生成file的文件名)
base64ToFile(base64, fileName) {
// 將base64按照 , 進(jìn)行分割 將前綴 與后續(xù)內(nèi)容分隔開(kāi)
const data = base64.split(',')
// 利用正則表達(dá)式 從前綴中獲取圖片的類型信息(image/png、image/jpeg、image/webp等)
const type = data[0].match(/:(.*?);/)[1]
// 從圖片的類型信息中 獲取具體的文件格式后綴(png、jpeg、webp)
const suffix = type.split('/')[1]
// 使用atob()對(duì)base64數(shù)據(jù)進(jìn)行解碼 結(jié)果是一個(gè)文件數(shù)據(jù)流 以字符串的格式輸出
const bstr = window.atob(data[1])
// 獲取解碼結(jié)果字符串的長(zhǎng)度
let n = bstr.length
// 根據(jù)解碼結(jié)果字符串的長(zhǎng)度創(chuàng)建一個(gè)等長(zhǎng)的整形數(shù)字?jǐn)?shù)組
// 但在創(chuàng)建時(shí) 所有元素初始值都為 0
const u8arr = new Uint8Array(n)
// 將整形數(shù)組的每個(gè)元素填充為解碼結(jié)果字符串對(duì)應(yīng)位置字符的UTF-16 編碼單元
while (n--) {
// charCodeAt():獲取給定索引處字符對(duì)應(yīng)的 UTF-16 代碼單元
u8arr[n] = bstr.charCodeAt(n)
}
// 利用構(gòu)造函數(shù)創(chuàng)建File文件對(duì)象
// new File(bits, name, options)
const file = new File([u8arr], `${fileName}.${suffix}`, {
type: type
})
// 將File文件對(duì)象返回給方法的調(diào)用者
return file
},
// 關(guān)閉攝像頭
stopNavigator() {
this.thisVideo.srcObject.getTracks()[0].stop()
}
}
}
</script>
<style lang="less" scoped>
.camera_outer {
position: relative;
overflow: hidden;
// background: url("../../assets/img/user_0608_04.png") no-repeat center;
background-size: 100%;
video,
canvas,
.tx_img {
-moz-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
-o-transform: scaleX(-1);
transform: scaleX(-1);
}
.btn_camera {
position: absolute;
bottom: 4px;
left: 0;
right: 0;
height: 50px;
background-color: rgba(0, 0, 0, 0.3);
line-height: 50px;
text-align: center;
color: #ffffff;
}
.bg_r_img {
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
}
.img_bg_camera {
position: absolute;
bottom: 0;
left: 0;
right: 0;
top: 0;
img {
width: 300px;
height: 300px;
}
.img_btn_camera {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 50px;
line-height: 50px;
text-align: center;
background-color: rgba(0, 0, 0, 0.3);
color: #ffffff;
.loding_img {
width: 50px;
height: 50px;
}
}
}
}
</style>封裝好以后就是調(diào)用了
<template>
<div>
<TakePhotos ref="TakePhotos" @refreshDataList="refreshDataList" />
</div>
</template>
<script>
export default {
methods: {
refreshDataList(imgSrc) {
// 這里返回服務(wù)器圖片的地址
console.log(imgSrc)
},
}
</script>上面代碼注釋已經(jīng)都寫(xiě)了,相信大家都能看懂
總結(jié)
到此這篇關(guān)于vue如何調(diào)用攝像頭實(shí)現(xiàn)拍照上傳圖片、本地上傳圖片的文章就介紹到這了,更多相關(guān)vue調(diào)用攝像頭拍照上傳圖片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3 與 vue2 優(yōu)點(diǎn)對(duì)比匯總
隨著用vue3 的開(kāi)發(fā)者越來(lái)越多,其必定是又她一定的有帶你,接下來(lái)這篇文章小編就為大家介紹vue3 對(duì)比 vue2 有什么優(yōu)點(diǎn)?感興趣的小伙伴請(qǐng)跟小編一起閱讀下文吧2021-09-09
vue中radio根據(jù)動(dòng)態(tài)值綁定checked無(wú)效的解決
這篇文章主要介紹了vue中radio根據(jù)動(dòng)態(tài)值綁定checked無(wú)效的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Vue?中ref()和?reactive()響應(yīng)式數(shù)據(jù)的使用方法
article介紹Vue3中ref()和reactive()函數(shù)的使用方法,ref()用于創(chuàng)建基本數(shù)據(jù)類型的響應(yīng)式引用,reactive()用于創(chuàng)建響應(yīng)式對(duì)象,本文介紹Vue中ref()和reactive()響應(yīng)式數(shù)據(jù)的使用方法,感興趣的朋友一起看看吧2025-01-01
vue中提示$index is not defined錯(cuò)誤的解決方式
這篇文章主要介紹了vue中提示$index is not defined錯(cuò)誤的解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
在vue.js渲染完界面之后如何再調(diào)用函數(shù)
這篇文章主要介紹了在vue.js渲染完界面之后如何再調(diào)用函數(shù)的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
源碼揭秘為什么 Vue2 this 能夠直接獲取到 data 和 methods
本篇文章主要介紹的是Vue2 this 能夠直接獲取到 data 和 methods,閱讀本文將能學(xué)到如何學(xué)習(xí)調(diào)試 vue2 源碼、data 中的數(shù)據(jù)為什么可以用 this 直接獲取到、methods 中的方法為什么可以用 this 直接獲取到,需要的朋友可以參考一下2021-09-09
vue2 如何實(shí)現(xiàn)div contenteditable=“true”(類似于v-model)的效果
這篇文章主要給大家介紹了利用vue2如何實(shí)現(xiàn)div contenteditable="true",就是類似于v-model的效果,文中給出了兩種解決的思路,對(duì)大家具有一定的參考價(jià)值,有需要的朋友們下面來(lái)一起看看吧。2017-02-02
Vue中失去焦點(diǎn)時(shí)所觸發(fā)的事件問(wèn)題
這篇文章主要介紹了Vue中失去焦點(diǎn)時(shí)所觸發(fā)的事件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
el-select自定義指令實(shí)現(xiàn)觸底加載分頁(yè)請(qǐng)求options數(shù)據(jù)(完整代碼和接口可直接用)
某些情況下,下拉框需要做觸底加載,發(fā)請(qǐng)求,獲取option的數(shù)據(jù),下面給大家分享el-select自定義指令實(shí)現(xiàn)觸底加載分頁(yè)請(qǐng)求options數(shù)據(jù)(附上完整代碼和接口可直接用),感興趣的朋友參考下吧2024-02-02

