Vue圖片裁剪上傳組件

本組件基于vuejs框架, 使用ES6基本語法, css預編譯采用的scss, 圖片裁剪模塊基于cropperjs,拍照時的圖片信息獲取使用exif, 圖片上傳使用XMLHttpRequest

該組件已單獨部署上線, 線上地址:http://upload-img.sufaith.com/, 圖片最終是傳至我個人的七牛云, 獲取七牛云上傳憑證token的接口是我單獨做的一個nodejs服務, 可在PC或移動端打開測試下效果.

涉及到的知識點整理如下:

vuejs介紹 — Vue.js

scssSass世界上最成熟、穩定和強大的CSS擴展語言 | Sass中文網

cropperjshttps://github.com/fengyuanchen/cropperjs

exifhttps://github.com/exif-js/exif-js

XMLHttpRequest?XMLHttpRequest()

整體項目分成3個文件:

1. uploadAvator.vue (父組件,用于選擇圖片,接收crop回調,執行上傳)

2. crop.vue (裁剪組件, 用于裁剪,壓縮,回調裁剪結果給uploadAvator.vue)

3. image.js (封裝了基本的base64轉換blob、獲取圖片url、xhr上傳、圖片壓縮等方法)

整體流程如下:

input選擇圖片

調用cropperjs裁剪

修正方向, 壓縮

上傳

具體實現步驟:

一. 實現input選擇文件

1. 定義一個隱形樣式的輸入框,用于選擇圖片文件 (imgUrl初始化為默認圖片地址)

<template>

? <div class="upload-wrapper" :style="{backgroundImage: 'url(' + imgUrl + ')'}">

? ? <input @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple=""/>

? </div>

</template>

<style lang="scss" scoped>

.upload-wrapper {

? position: relative;

? width: 77px;

? height: 77px;

? background-size: cover;

? border: 0;

? border-radius: 50%;

? margin: 20px auto;

? .input {

? ? position: absolute;

? ? z-index: 1;

? ? top: 0;

? ? left: 0;

? ? width: 100%;

? ? height: 100%;

? ? opacity: 0;

? ? -webkit-tap-highlight-color: rgba(0,0,0,0);

? }

}

</style>

2. 對input選擇圖片做一些優化

(1) 每次點擊input選擇圖片時, 彈出選擇文件的彈窗很慢,有些延遲

解決方案:?明確定義input的accept屬性對應的圖片類型

<input type="file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple=""/>

(2) 在ios設備下input若含有capture屬性, 則只能調起相冊,而安卓設備下input若不含capture屬性,則只能調起相冊

解決方案:?判斷是否為ios設備, 創建對應屬性的input

const UA = navigator.userAgent

const isIpad = /(iPad).*OS\s([\d_]+)/.test(UA)

const isIpod = /(iPod)(.*OS\s([\d_]+))?/.test(UA)

const isIphone = !isIpad && /(iPhone\sOS)\s([\d_]+)/.test(UA)

const isIos = isIpad || isIpod || isIphone

<input v-if="isIos" @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple=""/>

<!-- 安卓設備保留capture屬性 -->

<input v-else @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" capture="camera" multiple=""/>?

(3) 再次點擊input選擇圖片時, 若選擇的圖片和上一次選擇的圖片相同時,則不會觸發onchange事件

解決方案:?在每次接收到onchange事件時先銷毀當前input, 再重新創建一個input, 此時可利用vue的v-if指令,輕松銷毀或重建

<input v-if="isIos && !destroyInput" @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple=""/>

<!-- 安卓設備保留capture屬性 -->

<input v-if="!isIos && !destroyInput" @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" capture="camera" multiple=""/>? ?

data() {

? ? return {

? ? ? destroyInput: false, // 是否銷毀input元素, 解決在第二次和第一次選擇的文件相同時不觸發onchange事件的問題

? ? ? isIos: isIos // 是否為ios設備

? ? }

? },

二. 調用cropperjs裁剪

1.?獲取選擇的圖片的url (用于裁剪)

2. 獲取拍照時的Orientation信息,解決拍出來的照片旋轉問題

3.顯示裁剪組件并初始化

4. 取消裁剪和開始裁剪

三. 修正方向, 壓縮并將base64回調給父組件

const image = {}

image.compress = function(img, Orientation) {

? // 圖片壓縮

? // alert('圖片的朝向' + Orientation)

? let canvas = document.createElement('canvas')

? let ctx = canvas.getContext('2d')

? // 瓦片canvas

? let tCanvas = document.createElement('canvas')

? let tctx = tCanvas.getContext('2d')

? let initSize = img.src.length

? let width = img.width

? let height = img.height

? // 如果圖片大于四百萬像素,計算壓縮比并將大小壓至400萬以下

? let ratio

? if ((ratio = width * height / 4000000) > 1) {

? ? console.log('大于400萬像素')

? ? ratio = Math.sqrt(ratio)

? ? width /= ratio

? ? height /= ratio

? } else {

? ? ratio = 1

? }

? canvas.width = width

? canvas.height = height

? // 鋪底色

? ctx.fillStyle = '#fff'

? ctx.fillRect(0, 0, canvas.width, canvas.height)

? // 如果圖片像素大于100萬則使用瓦片繪制

? let count

? if ((count = width * height / 1000000) > 1) {

? ? count = ~~(Math.sqrt(count) + 1) // 計算要分成多少塊瓦片

? ? // 計算每塊瓦片的寬和高

? ? let nw = ~~(width / count)

? ? let nh = ~~(height / count)

? ? tCanvas.width = nw

? ? tCanvas.height = nh

? ? for (let i = 0; i < count; i++) {

? ? ? for (let j = 0; j < count; j++) {

? ? ? ? tctx.drawImage(img, i * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh)

? ? ? ? ctx.drawImage(tCanvas, i * nw, j * nh, nw, nh)

? ? ? }

? ? }

? } else {

? ? ctx.drawImage(img, 0, 0, width, height)

? }

? // 修復ios上傳圖片的時候 被旋轉的問題

? if (Orientation && Orientation !== '' && Orientation !== 1) {

? ? switch (Orientation) {

? ? ? case 6: // 需要順時針(向左)90度旋轉

? ? ? ? image.rotateImg(img, 'left', canvas)

? ? ? ? break

? ? ? case 8: // 需要逆時針(向右)90度旋轉

? ? ? ? image.rotateImg(img, 'right', canvas)

? ? ? ? break

? ? ? case 3: // 需要180度旋轉

? ? ? ? image.rotateImg(img, 'right', canvas) // 轉兩次

? ? ? ? image.rotateImg(img, 'right', canvas)

? ? ? ? break

? ? }

? }

? // 設置jpegs圖片的質量

? let ndata = canvas.toDataURL('image/jpeg', 1)

? console.log(`壓縮前:${initSize}`)

? console.log(`壓縮后:${ndata.length}`)

? console.log(`壓縮率:${~~(100 * (initSize - ndata.length) / initSize)}%`)

? tCanvas.width = tCanvas.height = canvas.width = canvas.height = 0

? return ndata

}

image.rotateImg = function(img, direction, canvas) {

? // 圖片旋轉

? // 最小與最大旋轉方向,圖片旋轉4次后回到原方向

? const minStep = 0

? const maxStep = 3

? if (img == null) return

? // img的高度和寬度不能在img元素隱藏后獲取,否則會出錯

? let height = img.height

? let width = img.width

? let step = 2

? if (step == null) {

? ? step = minStep

? }

? if (direction === 'right') {

? ? step++

? ? // 旋轉到原位置,即超過最大值

? ? step > maxStep && (step = minStep)

? } else {

? ? step--

? ? step < minStep && (step = maxStep)

? }

? // 旋轉角度以弧度值為參數

? let degree = step * 90 * Math.PI / 180

? let ctx = canvas.getContext('2d')

? switch (step) {

? ? case 0:

? ? ? canvas.width = width

? ? ? canvas.height = height

? ? ? ctx.drawImage(img, 0, 0)

? ? ? break

? ? case 1:

? ? ? canvas.width = height

? ? ? canvas.height = width

? ? ? ctx.rotate(degree)

? ? ? ctx.drawImage(img, 0, -height)

? ? ? break

? ? case 2:

? ? ? canvas.width = width

? ? ? canvas.height = height

? ? ? ctx.rotate(degree)

? ? ? ctx.drawImage(img, -width, -height)

? ? ? break

? ? case 3:

? ? ? canvas.width = height

? ? ? canvas.height = width

? ? ? ctx.rotate(degree)

? ? ? ctx.drawImage(img, -width, 0)

? ? ? break

? }

}

export default image

四. 上傳圖片

1. base64轉換為文件

2. XMLHttpRequest上傳

3. 定義上傳狀態的樣式,包括上傳進度和上傳失敗的標識

4.父組件接收到裁剪組件的回調的base64后,執行上傳

<template>

<div>

? <div class="upload-wrapper" :class="{'upload-status-bg': showStatusWrapper}" :style="{backgroundImage: 'url(' + imgUrl + ')'}">

? ? <input v-if="isIos && !destroyInput" @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" multiple=""/>

? ? <!-- 安卓設備保留capture屬性 -->

? ? <input v-if="!isIos && !destroyInput" @change="onChange" class="input" type="file" accept="image/jpg,image/jpeg,image/png,image/gif" capture="camera" multiple=""/>

? ? <!-- 上傳狀態 -->

? ? <div v-if="showStatusWrapper" class="upload-status-wrapper">

? ? ? <i class="fail" v-if="showStatusFail">!</i>

? ? ? <i v-else>{{procent}}%</i>

? ? </div>

? </div>

? <crop ref="cropWrapper" v-show="showCrop" @hide="showCrop=false" @finish="setUpload"></crop>

</div>

</template>

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容