vue 拍照上傳
阿新 • • 發佈:2018-11-27
vue 拍照上傳 涉及技術點 canvas裁剪 base64 blob資料裝換 forData 資料傳遞 ,還有hammer.js和exif-js具體程式碼如下
<template> <div class="pic"> <div class="bg-box"> <div class="img-box"> <img src="../../assets/img/news_bg.png" width="100%" alt=""> </div> <div class="headers"> <van-nav-bar title="個人資訊" @click-left="onClickLeft" > <img class="img" src="../..//assets/img/
[email protected]" slot="left" alt=""> </van-nav-bar> </div> </div> <form enctype="multipart/form-data" > <div class="uploadBox"> <!--主編輯器canvas--> <canvas id="canvas" :width="windowWidth" :height="windowWidth"></canvas> <!--蒙層canvas--> <canvas id="canvasMask" :width="windowWidth" :height="windowWidth"></canvas> <!--用於匯出圖片的canvas--> <canvas id="resultImg" width="160" height="160" style="display: none"></canvas> <label class="uploadBar" for="uploadInput" v-if="uploadBarShow"> <input type="file" style="display: none" id="uploadInput" accept="image/*" ref="file" @change="upLoadChange"> <span>點選上傳</span> </label> </div> <!-- <img :src="src" v-if="src"/> --> <div class="select-box" v-if="!uploadBarShow"> <!-- <input type="submit" class="sure-bt" @click="crop" value="確定裁切"> --> <div class="sure-bt" @click="crop" >確定裁切</div> <div class="cancel-bt" @click="cancel">取消</div> </div> </form> </div> </template> <script> import {Toast} from "vant"; import axios from "axios"; import { dataURItoBlob } from "../../assets/js/blob.js" import { mapMutations } from "vuex"; require("@/assets/plugin/hammer.min"); //引入hammerJS import qs from "qs" import EXIF from "exif-js"; export default { name: "home", data() { return { src: "", orientation: null, //圖片元資訊 degree: 0, //原圖片旋轉角度 windowWidth: 0, //螢幕的寬度 imgEl: null, imgWidth: 0, //圖片的寬度 imgHeight: 0, //圖片的高度 transWidth: 0, //改變後的圖片寬度 transHeight: 0, //改變後的圖片高度 initScale: 0, transformScale: 1, //初始縮放 prevX: 0, //上一次的X軸 prevY: 0, //上一次的Y軸 translateX: 0, //平移X軸 translateY: 0, //平移Y軸 uploadBarShow: true, //上傳按鈕顯示 canvas: null, //canvas canvasMask: null, //canvas-mask canvasResult: null, //裁切圖片canvas eq_types : "iOS" }; }, methods: { ...mapMutations([ 'header_pic_update' ]), onClickLeft() { this.$router.go(-1); }, cancel() { this.$router.go(-1); }, /** * 上傳圖片 * */ upLoadChange() { let files = this.$refs.file.files[0], self = this; let val = this.$refs.file.value; // console.log("檔案是:",files); // console.log("檔案是:",val); /*控制圖片上傳大小不超過1MB*/ if (files.size > 8388608) { alert("圖片不能超過1MB大小"); return false; } /*用EXIF獲取圖片元資訊*/ EXIF.getData(files, function() { self.orientation = EXIF.getTag(this, "Orientation"); }); let fr = new FileReader(); // 監聽reader物件的的onload事件,當圖片載入完成時,把base64編碼賦值給預覽圖片 fr.addEventListener( "load", () => { if (this.orientation) { /*需要對ios做一下相容*/ this.getImgData(fr.result, this.orientation, data => { this.createCanvas(data); //初始化canvas }); } else { this.createCanvas(fr.result); //初始化canvas } }, false ); fr.readAsDataURL(files); }, /** * 初始化canvas * */ createCanvas(imgBase64) { this.imgEl = new Image(); this.imgEl.src = imgBase64; this.imgEl.onload = () => { this.imgWidth = this.imgEl.width; //初始化圖片的寬 this.imgHeight = this.imgEl.height; //初始化圖片的高 // 畫蒙層 this.canvasMask.globalCompositeOperation = "source-out"; this.canvasMask.fillStyle = "rgb(255,255,255)"; this.canvasMask.arc( this.windowWidth / 2, this.windowWidth / 2, 80, 0, 2 * Math.PI ); this.canvasMask.fill(); this.canvasMask.fillStyle = "rgba(0,0,0,0.7)"; this.canvasMask.fillRect(0, 0, this.windowWidth, this.windowWidth); // 當圖片比canvas小時不做任何改變 if ( this.imgEl.width < this.windowWidth && this.imgEl.height < this.windowWidth ) { this.imgWidth = this.imgEl.width; this.imgHeight = this.imgEl.height; } else { //原圖片寬高比例 大於 圖片框寬高比例 if (1 <= this.imgEl.width / this.imgEl.height) { this.imgWidth = this.windowWidth; //以框的寬度為標準 this.imgHeight = this.windowWidth * (this.imgEl.height / this.imgEl.width); } else { //原圖片寬高比例 小於 圖片框寬高比例 this.imgWidth = this.windowWidth * (this.imgEl.width / this.imgEl.height); this.imgHeight = this.windowWidth; //以框的高度為標準 } } this.canvas.translate(this.windowWidth / 2, this.windowWidth / 2); //把canvas原點移動到中心位置 this.canvas.drawImage( this.imgEl, 0 - this.imgWidth / 2, 0 - this.imgHeight / 2, this.imgWidth, this.imgHeight ); /*初始化hammer*/ this.initHammer(); }; }, /** * @param {string} img 圖片的base64 * @param {int} dir exif獲取的方向資訊 * @param {function} next 回撥方法,返回校正方向後的base64 * */ getImgData(img, dir, next) { let image = new Image(); image.src = img; image.onload = function() { let degree = 0, drawWidth, drawHeight, width, height; drawWidth = image.naturalWidth; //暫存圖片的寬 drawHeight = image.naturalHeight; //暫存圖片的高 //以下改變一下圖片大小 let maxSide = Math.max(drawWidth, drawHeight); console.log(maxSide); if (maxSide > 4032) { let minSide = Math.min(drawWidth, drawHeight); minSide = minSide / maxSide * 4032; maxSide = 4032; if (drawWidth > drawHeight) { drawWidth = maxSide; drawHeight = minSide; } else { drawWidth = minSide; drawHeight = maxSide; } } let canvas = document.createElement("canvas"); canvas.width = width = drawWidth; canvas.height = height = drawHeight; let context = canvas.getContext("2d"); //判斷圖片方向,重置canvas大小,確定旋轉角度,iphone預設的是home鍵在右方的橫屏拍攝方式 switch (dir) { //iphone橫屏拍攝,此時home鍵在左側 case 3: degree = 180; drawWidth = -width; drawHeight = -height; break; //iphone豎屏拍攝,此時home鍵在下方(正常拿手機的方向) case 6: canvas.width = height; canvas.height = width; degree = 90; drawWidth = width; drawHeight = -height; break; //iphone豎屏拍攝,此時home鍵在上方 case 8: canvas.width = height; canvas.height = width; degree = 270; drawWidth = -width; drawHeight = height; break; } //使用canvas旋轉校正 context.rotate(degree * Math.PI / 180); context.drawImage(this, 0, 0, drawWidth, drawHeight); //返回校正圖片 next(canvas.toDataURL("image/jpeg")); }; }, /** * 初始化hammer * */ initHammer() { //隱藏上傳bar this.uploadBarShow = false; let hammer = new Hammer(document.querySelector("#canvasMask")); hammer.get("pinch").set({ enable: true }); hammer.get("rotate").set({ enable: true }); /*縮放 */ hammer.on("pinchmove pinchstart pinchin pinchout", e => { if (e.type === "pinchstart") { this.initScale = this.transformScale || 1; } this.transformScale = this.initScale * e.scale; this.canvas.clearRect( 0 - this.windowWidth / 2, 0 - this.windowWidth / 2, this.windowWidth, this.windowWidth ); this.transWidth = this.imgWidth * this.transformScale; this.transHeight = this.imgHeight * this.transformScale; this.canvas.drawImage( this.imgEl, this.translateX - this.transWidth / 2, this.translateY - this.transHeight / 2, this.transWidth, this.transHeight ); }); /*平移*/ hammer.on("panstart panmove", e => { if (e.type === "panstart") { this.prevX = this.translateX; this.prevY = this.translateY; } this.translateX = this.prevX + e.deltaX; this.translateY = this.prevY + e.deltaY; /*擦除canvas*/ this.canvas.clearRect( 0 - this.windowWidth / 2, 0 - this.windowWidth / 2, this.windowWidth, this.windowWidth ); this.canvas.drawImage( this.imgEl, this.translateX - (this.transWidth || this.imgWidth) / 2, this.translateY - (this.transHeight || this.imgHeight) / 2, this.transWidth || this.imgWidth, this.transHeight || this.imgHeight ); }); }, /** * 裁切 * */ crop() { let base64 = document.querySelector("#canvas").toDataURL("image/png"); let nImg = new Image(); nImg.src = base64; nImg.onload = () => { this.canvasResult.fillStyle = "white"; this.canvasResult.fillRect(0, 0, 160, 160); this.canvasResult.drawImage( nImg, -(this.windowWidth / 2 - 80), -(this.windowWidth / 2 - 80) ); /*最後匯出裁切好的圖片為base64碼*/ //**將base64傳給後臺 */ this.src = document.querySelector("#resultImg").toDataURL("image/jpeg"); // 將base64轉成blob let upload_imgs = dataURItoBlob(this.src) console.log(upload_imgs, "base64:-->"+this.src); let formData = new FormData(); formData.append('action', "edit_user_pic"); formData.append('files', upload_imgs, 'image.jpeg'); //有些蘋果不支援get方法 // console.log(formData); // console.log(formData.get('action')); // console.log(formData.get('files')); //新增請求頭 axios({ headers: { 'Content-Type':'multipart/form-data' }, method: 'post', url: '/api/fileApi', data: formData }) .then(res =>{ console.log(res); if(res._code !== "99999"){ Toast({ message : res._msg, duration : 2500 }); return; }; this.header_pic_update(res._result.userPic); this.$router.go(-1); }) }; } }, mounted() { let canvas = document.querySelector("#canvas"), canvasMask = document.querySelector("#canvasMask"), canvasRsut = document.querySelector("#resultImg"); this.canvas = canvas.getContext("2d"); this.canvasMask = canvasMask.getContext("2d"); this.canvasResult = canvasRsut.getContext("2d"); this.windowWidth = window.innerWidth; }, created(){ let u = navigator.userAgent; if (u.indexOf("Android") > -1 || u.indexOf("Linux") > -1) { //安卓手機 this.eq_types = "android"; } else if (u.indexOf("iPhone") > -1) { //蘋果手機 this.eq_types = "iOS"; } else if (u.indexOf("Windows Phone") > -1) { //winphone手機 } } }; </script> <style lang="less" scoped> .pic { position: relative; height: 100vh; .bg-box { position: relative; height: 139px; .img-box { position: absolute; top: 0; left: 0; z-index: -5; width: 100%; } .headers { padding-top: 57px; margin: 0 44px; /deep/ .van-hairline--bottom { background: transparent; color: #fff; height: 55px; line-height: 55px; } /deep/ .van-nav-bar__left { top: 0; } /deep/ .van-nav-bar__title { font-size: 36px; } /deep/ .van-hairline--bottom::after { border: none; } .img { width: 55px; height: 55px; } } } } * { padding: 0; margin: 0; } .uploadBox { position: relative; } #canvasMask { position: absolute; left: 0; top: 0; z-index: 5; } .uploadBar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 20vw; height: 20vw; border: 1px dashed gray; text-align: center; line-height: 20vw; font-size: 15px; color: gray; display: block; z-index: 10; } .select-box { height: 100px; margin: 20px 30px; display: flex; justify-content: space-between; align-items: center; color: #fff; font-size: 36px; .sure-bt { width: 200px; height: 80px; line-height: 80px; border-radius: 10px; background: #44bb00; } .cancel-bt { width: 200px; height: 80px; line-height: 80px; border-radius: 10px; background: #ff976a; } } </style>
blob程式碼如下
export const dataURItoBlob = function (base64Data) { var byteString; if (base64Data.split(',')[0].indexOf('base64') >= 0) byteString = atob(base64Data.split(',')[1]); else // byteString = unescape(base64Data.split(',')[1]); byteString = decodeURI(base64Data.split(',')[1]); var mimeString = base64Data.split(',')[0].split(':')[1].split(';')[0]; var ia = new Uint8Array(byteString.length); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ia], {type: mimeString}); }
這樣就可以實現拍照上傳:
注意:
在驗證資料是否append到fordata中,用formData.get(‘action’),部分ios不相容這個方法要註釋掉
如果是放在自己app種webView中安卓app需要對其做支援否則input可能無法觸發,H5瀏覽器則完全適用