您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
Vue是一套用于構(gòu)建用戶界面的漸進(jìn)式JavaScript框架,Vue與其它大型框架的區(qū)別是,使用Vue可以自底向上逐層應(yīng)用,其核心庫只關(guān)注視圖層,方便與第三方庫和項(xiàng)目整合,且使用Vue可以采用單文件組件和Vue生態(tài)系統(tǒng)支持的庫開發(fā)復(fù)雜的單頁應(yīng)用。
vue實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能,分享給大家,具體如下:
實(shí)現(xiàn)效果:
裁切指定區(qū)域內(nèi)的圖片
旋轉(zhuǎn)圖片
放大圖片
輸出bolb 格式數(shù)據(jù) 提供給 formData 對象
效果圖
大概原理:
利用h6 FileReader 對象, 獲取 <input type="file"/> “上傳到瀏覽器的文件” ,文件形式 為base64形式, 把 base64 賦給canvas的上下文。
然后給canvas 元素上加入對(mousedown)監(jiān)聽事件。 當(dāng)用戶鼠標(biāo)左鍵在canvas按下時(shí):
掛載對 window 對象mousemove事件 ---> 獲取 鼠標(biāo)移動(dòng)x,y距離.從而操作 canvas里的圖像的位置移動(dòng)。
掛載對 window 對象mouseup 事件, 清除 mousemove事件的綁定。(同時(shí)該事件觸發(fā)后會(huì)被刪除)
剩下的 放大、縮小 、 旋轉(zhuǎn) 是對 canvas 對象的操作/坐標(biāo)體系的操作。具體api詳見mdn canvas 文檔
代碼
dom.js
export const on = ({el, type, fn}) => { if (typeof window) { if (window.addEventListener) { el.addEventListener(type, fn, false) } else { el.attachEvent(`on${type}`, fn) } } } export const off = ({el, type, fn}) => { if (typeof window) { if (window.addEventListener) { el.removeEventListener(type, fn) } else { el.detachEvent(`on${type}`, fn) } } } export const once = ({el, type, fn}) => { const hyFn = (event) => { try { fn(event) } finally { off({el, type, fn: hyFn}) } } on({el, type, fn: hyFn}) } // 最后一個(gè) export const fbTwice = ({fn, time = 300}) => { let [cTime, k] = [null, null] // 獲取當(dāng)前時(shí)間 const getTime = () => new Date().getTime() // 混合函數(shù) const hyFn = () => { const ags = argments return () => { clearTimeout(k) k = cTime = null fn(...ags) } } return () => { if (cTime == null) { k = setTimeout(hyFn(...arguments), time) cTime = getTime() } else { if ( getTime() - cTime < 0) { // 清除之前的函數(shù)堆 ---- 重新記錄 clearTimeout(k) k = null cTime = getTime() k = setTimeout(hyFn(...arguments), time) } }} } export const contains = function(parentNode, childNode) { if (parentNode.contains) { return parentNode != childNode && parentNode.contains(childNode) } else { return !!(parentNode.compareDocumentPosition(childNode) & 16) } } export const addClass = function (el, className) { if (typeof el !== "object") { console.log('el is not elem') return null } let classList = el['className'] classList = classList === '' ? [] : classList.split(/\s+/) if (classList.indexOf(className) === -1) { classList.push(className) el.className = classList.join(' ') } else { console.warn('warn className current') } } export const removeClass = function (el, className) { let classList = el['className'] classList = classList === '' ? [] : classList.split(/\s+/) classList = classList.filter(item => { return item !== className }) el.className = classList.join(' ') } export const delay = ({fn, time}) => { let oT = null let k = null return () => { // 當(dāng)前時(shí)間 let cT = new Date().getTime() const fixFn = () => { k = oT = null fn() } if (k === null) { oT = cT k = setTimeout(fixFn, time) return } if (cT - oT < time) { oT = cT clearTimeout(k) k = setTimeout(fixFn, time) } } } export const Event = function () { // 類型 this.typeList = {} } Event.prototype.on = function ({type, fn}){ if (this.typeList.hasOwnProperty(type)) { this.typeList[type].push(fn) } else { this.typeList[type] = [] this.typeList[type].push(fn) } } Event.prototype.off = function({type, fn}) { if (this.typeList.hasOwnProperty(type)) { let list = this.typeList[type] let index = list.indexOf(fn) if (index !== -1 ) { list.splice(index, 1) } } else { console.warn('not has this type') } } Event.prototype.once = function ({type, fn}) { const fixFn = () => { fn() this.off({type, fn: fixFn}) } this.on({type, fn: fixFn}) } Event.prototype.trigger = function (type){ if (this.typeList.hasOwnProperty(type)) { this.typeList[type].forEach(fn => { fn() }) } }
組件模板
<template> <div class="jc-clip-image" :> <canvas ref="ctx" :width="clip.width" :height="clip.height" @mousedown="handleClip($event)" > </canvas> <input type="file" ref="file" @change="readFileMsg($event)"> <div class="clip-scale-btn"> <a class="add" @click="handleScale(false)">+</a> <a @click="rotate" class="right-rotate">轉(zhuǎn)</a> <a class="poor" @click="handleScale(true)">-</a> <span>{{scale}}</span> </div> <div class="upload-warp"> <a class="upload-btn" @click="dispatchUpload($event)">upload</a> <a class="upload-cancel">cancel</a> </div> <div class="create-canvas"> <a class="to-send-file" @click="outFile" title="請打開控制臺">生成文件</a> </div> </div> </template> <script> import {on, off, once} from '../../utils/dom' export default { ctx: null, file: null, x: 0, // 點(diǎn)擊canvas x 鼠標(biāo)地址 y: 0,// 點(diǎn)擊canvas y 鼠標(biāo)地址 xV: 0, // 鼠標(biāo)移動(dòng) x距離 yV: 0, // 鼠標(biāo)移動(dòng) y距離 nX: 0, // 原始坐標(biāo)點(diǎn) 圖像 x nY: 0,// 原始坐標(biāo)點(diǎn) 圖像 y img: null, props: { src: { type: String, default: null }, clip: { type: Object, default () { return {width: '200px', height: '200px'} } } }, data () { return { isShow: false, base64: null, scale: 1.5, //放大比例 deg: 0 //旋轉(zhuǎn)角度 } }, computed: { width () { const {clip} = this return parseFloat(clip.width.replace('px', '')) }, height () { const {clip} = this return parseFloat(clip.height.replace('px', '')) } }, mounted () { const {$options, $refs, width, height} = this // 初始化 canvas file nX nY Object.assign($options, { ctx: $refs.ctx.getContext('2d'), file: $refs.file, nX: -width / 2, nY: -height / 2 }) }, methods: { // 旋轉(zhuǎn)操作 rotate () { const {$options, draw} = this this.deg = (this.deg + Math.PI /2)% (Math.PI * 2) draw($options.img, $options.nX + $options.xV, $options.nY + $options.yV, this.scale, this.deg) }, // 處理放大 handleScale (flag) { const {$options, draw, deg} = this flag && this.scale > 0.1 && (this.scale = this.scale - 0.1) !flag && this.scale < 1.9 && (this.scale = this.scale + 0.1) $options.img && draw($options.img, $options.nX + $options.xV, $options.nY + $options.yV, this.scale, deg) }, // 模擬file 點(diǎn)擊事件 dispatchUpload (e) { this.clearState() const {file} = this.$options e.preventDefault() file.click() }, // 讀取 input file 信息 readFileMsg () { const {file} = this.$options const {draw, createImage, $options: {nX, nY}, scale, deg} = this const wFile = file.files[0] const reader = new FileReader() reader.onload = (e) => { const img = createImage(e.target.result, (img) => { draw(img, nX, nY, scale, deg) }) file.value = null } reader.readAsDataURL(wFile) }, // 生成 圖像 createImage (src, cb) { const img = new Image() this.$el.append(img) img.className = 'base64-hidden' img.onload = () => { cb(img) } img.src = src this.$options.img = img }, // 操作畫布畫圖 draw (img, x = 0, y = 0, scale = 0.5,deg = Math.PI ) { const {ctx} = this.$options let {width, height} = this // 圖片尺寸 let imgW = img.offsetWidth let imgH = img.offsetHeight ctx.save() ctx.clearRect( 0, 0, width, height) ctx.translate( width / 2, height / 2, img) ctx.rotate(deg) ctx.drawImage(img, x, y, imgW * scale, imgH * scale) ctx.restore() }, // ... 事件綁定 handleClip (e) { const {handleMove, $options, deg} = this if (!$options.img) { return } Object.assign(this.$options, { x: e.screenX, y: e.screenY }) on({ el: window, type: 'mousemove', fn: handleMove }) once({ el: window, type: 'mouseup', fn: (e) =>{ console.log('down') switch (deg) { case 0: { Object.assign($options, { nX: $options.nX + $options.xV, nY: $options.nY + $options.yV, xV: 0, yV: 0 }) break; } case Math.PI / 2: { Object.assign($options, { nX: $options.nY + $options.yV, nY: $options.nX - $options.xV, xV: 0, yV: 0 }) break; } case Math.PI: { Object.assign($options, { nX: $options.nX - $options.xV, nY: $options.nY - $options.yV, xV: 0, yV: 0 }) break; } default: { // $options.nY - $options.yV, $options.nX + $options.xV Object.assign($options, { nX: $options.nY - $options.yV, nY: $options.nX + $options.xV, xV: 0, yV: 0 }) } } off({ el: window, type: 'mousemove', fn: handleMove }) } }) }, // ... 處理鼠標(biāo)移動(dòng) handleMove (e){ e.preventDefault() e.stopPropagation() const {$options, draw, scale, deg} = this Object.assign($options, { xV: e.screenX - $options.x, yV: e.screenY - $options.y }) switch (deg) { case 0: { draw($options.img, $options.nX + $options.xV, $options.nY + $options.yV, scale, deg) break; } case Math.PI / 2: { draw($options.img, $options.nY + $options.yV, $options.nX - $options.xV, scale, deg) break; } case Math.PI: { draw($options.img, $options.nX - $options.xV, $options.nY - $options.yV, scale, deg) break; } default: { draw($options.img, $options.nY - $options.yV, $options.nX + $options.xV, scale, deg) break; } } }, // 清除狀態(tài) clearState () { const {$options, width, height} = this if ($options.img) { this.$el.removeChild($options.img) Object.assign($options, { x: 0, y: 0, xV: 0, yV: 0, nX: -width / 2, nY: -height / 2, img: null, }) } }, // 輸出文件 outFile () { const {$refs: {ctx}} = this console.log(ctx.toDataURL()) ctx.toBlob((blob) => {console.log(blob)}) } } } </script> <style> @component-namespace jc { @component clip-image{ position: relative; width: 100%; canvas { position: relative; width: 100%; height: 100%; cursor: pointer; box-shadow: 0 0 3px #333; } input { display: none; } .base64-hidden { position: absolute; top: 0; left: 0; display: block; width: 100%; height: auto; z-index: -999; opacity: 0; } .clip-scale-btn { position: relative; @utils-clearfix; margin-bottom: 5px; text-align: center; a { float: left; width: 20px; height: 20px; border-radius: 50%; color: #fff; background: #49a9ee; text-align: center; cursor: pointer; } &>.poor, &>.right-rotate { float: right; } &>span{ position: absolute; z-index: -9; top: 0; left: 0; display: block; position: relative; width: 100%; text-align: center; height: 20px; line-height: 20px; } } .upload-warp { @utils-clearfix; .upload-btn,.upload-cancel { float: left; display:inline-block; width: 60px; height: 25px; line-height: 25px; color: #fff; border-radius: 5px; background: #49a9ee; box-shadow: 0 0 0 #333; text-align: center; top: 0; left: 0; right: 0; bottom: 0; margin: auto; cursor: pointer; margin-top: 5px; } .upload-cancel{ background: gray; float: right; } } .to-send-file { margin-top: 5px; display: block; width: 50px; height: 25px; line-height: 25px; color: #fff; border-radius: 5px; background: #49a9ee; cursor: pointer; } }
關(guān)于“vue如何實(shí)現(xiàn)裁切圖片同時(shí)實(shí)現(xiàn)放大、縮小、旋轉(zhuǎn)功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯(cuò),請把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。