h5移动端使用video实现拍照、上传文件对象、选择相册,做手机兼容。
html部分
<template> <div class="views"> <video style="width: 100vw; height: calc(100vh - 18vh)" object-fit="fill"></video> <!-- <img style="width: 100vw; height: calc(100vh - 18vh)" :src="str" alt="" srcset=""> --> <div class="picture" @click="pictureClick"> <i class="iconfont icon-picture"></i> </div> <div @click="handlePhotographClick" class="action"></div> <div class="folder" @click="folderClick"> <i class="iconfont icon-folder"></i> </div> <div class="bac"> <div> <div class="img_box" v-for="(img, index) in srcList" :key="img.src + index"> <i class="iconfont icon-guanbi" @click="delImg(img.name, index)"></i> <img src="../../../../public/img/files1.png" v-if="img.name === 'files'" /> <img :src="img.src" v-else /> <span>{{ index + 1 }}</span> </div> </div> <div> <button class="btn" @click="upload">确认</button> </div> </div> </div> </template>
js部分
<script> export default { data() { return { imageUrl: '', // 媒体流,用于关闭摄像头 mediaStreamTrack: null, fileName: '', // 上传文件名 fileList: [], // 上传文件列表 isCamera: true, // 是否是摄像头 imgBase64: '', // 图片base64 photoList: [], // 图片列表 localHeight: 0, // 本地视频高度 srcList: [], // 图片路径列表 } }, mounted() { this.invokingCamera() }, destroyed() { this.handlePhotographCloseClick() }, methods: { // 调用摄像头 invokingCamera() { const self = this // 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。 // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象 if (navigator.mediaDevices === undefined) { navigator.mediaDevices = {} } // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。 if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = function (constraints) { // 首先,如果有getUserMedia的话,就获得它 const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口 if (!getUserMedia) { return Promise.reject(new Error('getUserMedia is not implemented in this browser')) } // 否则,为老的navigator.getUserMedia方法包裹一个Promise return new Promise(function (resolve, reject) { getUserMedia.call(navigator, constraints, resolve, reject) }) } } const constraints = { audio: false, video: { // 前置摄像头 facingMode: { exact: 'environment' }, // 手机端相当于高 width: Math.max(window.innerWidth, window.innerHeight), // 手机端相当于宽 height: Math.min(window.innerWidth, window.innerHeight), }, } navigator.mediaDevices .getUserMedia(constraints) .then(function (stream) { self.mediaStreamTrack = stream const video = document.querySelector('video') // 旧的浏览器可能没有srcObject if ('srcObject' in video) { video.srcObject = stream } else { // 防止在新的浏览器里使用它,应为它已经不再支持了 video.src = window.URL.createObjectURL(stream) } video.onloadedmetadata = function (e) { video.play() } }) .catch(function (err) { console.log(err.name + ': ' + err.message) }) }, // 关闭摄像头 handlePhotographCloseClick() { if (this.mediaStreamTrack) { // 关闭摄像头 this.mediaStreamTrack.getTracks().forEach(function (track) { track.stop() }) this.mediaStreamTrack = null } }, // 拍照 handlePhotographClick() { const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d') const video = document.querySelector('video') canvas.width = Math.min(video.videoWidth, video.videoHeight) canvas.height = Math.max(video.videoWidth, video.videoHeight) ctx.drawImage(video, 0, 0, canvas.width, canvas.height) // 将图片转为base64 const base64Image = canvas.toDataURL('image/png') // const str = base64Image.replace('data:image/png;base64,', '') // // 文件对象数组 // this.photoList.push(this.convertBlobToFile(this.convertBase64ToBlob(str), new Date().getTime())) // 图片路径数组 this.srcList.push({ src: base64Image, // src: this.str, name: new Date().getTime() + '.png', }) }, folderClick() { // 选择文件 var input = document.createElement('input') input.type = 'file' input.accept = '.doc,.docx,.txt,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf' input.addEventListener('change', (e) => { // console.log(e.target.files, 'eeeeee') // 读取选择的文件 this.fileList.push(e.target.files) this.srcList.push({ src: null, name: 'files', }) }) // 触发点击事件,打开文件 input.click() }, pictureClick() { const self = this // 选择图片 var input = document.createElement('input') input.type = 'file' input.accept = 'image/*' input.multiple = true // 点击事件处理函数 input.addEventListener('change', function () { if (this.files && this.files[0]) { // 读取选择的图片文件 var reader = new FileReader() reader.onload = (e) => { // 图片加载完成后,将图片URL赋值给input的src属性,即可显示图片 // console.log(e.target.result, 'e.target.result') const type = this.files[0].type.split('/')[1] self.srcList.push({ src: e.target.result, name: new Date().getTime() + '.' + type, }) } reader.readAsDataURL(this.files[0]) } }) // 触发点击事件,打开图库 input.click() }, // 转换为blob格式 convertBase64ToBlob(base64Str) { // 将base64字符串转为二进制数据 const byteCharacters = atob(base64Str) // 创建Blob对象 const blob = new Blob([byteCharacters], { type: 'application/octet-stream' }) return blob }, // 将blob转为file对象 convertBlobToFile(blob, fileName) { // 创建File对象 const type = fileName.split('.')[1] const file = new File([blob], fileName, { type: `image/${type}` }) return file }, delImg(name, index) { // 删除图片 if (name === 'files') { this.fileList.splice(index, 1) } this.srcList.splice(index, 1) }, upload() { // 上传按钮 if (this.srcList.length > 0) { for (let i = 0; i < this.srcList.length; i++) { // 文件对象数组 if (this.srcList[i].src === null) { continue } const type = this.srcList[i].name.split('.')[1] this.photoList.push(this.convertBlobToFile(this.convertBase64ToBlob(this.srcList[i].src.replace(`data:image/${type};base64,`, '')), new Date().getTime() + `.${type}`)) } } const fileObj = [] for (let i = 0; i < this.fileList.length; i++) { fileObj.push(this.fileList[i][0]) } for (let i = 0; i < this.photoList.length; i++) { fileObj.push(this.photoList[i]) } console.log(fileObj, 'fileObj') }, }, } </script>
css部分
<style scoped lang="stylus"> .views{ width: 100vw; height: 100vh; background-color: #ccc; position: relative .picture{ position: absolute; left: 1rem; z-index: 9; bottom: 4rem; .icon-picture{ font-size: 1rem; font-weight: bold; } } .folder{ position: absolute; right: 1rem; z-index: 9; bottom: 4rem; .icon-folder{ font-size: 1rem; font-weight: bold; } } .action{ position: absolute; bottom: 20vh; left: 50%; margin-left: -35px; border-radius: 50px; border: 10px solid #ccc; box-shadow: 0 0 10px black; background-color: #fff; width: 70px; height: 70px; display: flex; justify-content: center; z-index :99 } .bac { overflow-x: scroll; white-space: nowrap; /* 横向内容不换行 */ align-items: flex-end; /* 图片索引在左下角 */ } .img_box { margin-top: 10px; position: relative; display: inline-block; /* img_box横向排列 */ margin-left: 8px; margin-bottom: 8px; } .icon-guanbi { position: absolute; top: -10px; right: -10px; width: 20px; height: 20px; font-size : 20px; color: #fff; color:red; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; } img { // width: 100px; height:15vh; margin-left: 8px } span { position: absolute; bottom: 0; left: 0; margin-left: 4px; margin-bottom: 4px; color: #fff; background-color: #64e8ff; padding: 4px; font-size: 20px; border-radius: 2px 2px 2px 6px; } .btn{ color: #fff; border: none; background: #029afc; height: 1rem; width: 1.5rem; position: absolute; right: 0; bottom: 1rem; z-index: 99; border-radius: 10px; line-height: 1rem; } } </style>
以上代码可以直接复制使用,留个关注吧。
一个小菜鸡