vue版本裁切工具,包含預覽功能
最終效果: https://qiuyaofan.github.io/vue-crop-demo/ html
源碼地址: https://github.com/qiuyaofan/vue-crop前端
// 初始化vue-cli vue init webpack my-plugin
新建src/views/validSlideDemo.vue, src/components裏新建VueCrop/index.js,VueCrop.vue, 在routes/index.js配置訪問路由(具體看github源碼)
最終生成的文件結構以下圖:vue
// 導入插件入口文件 import VueCrop from './VueCrop/index.js' const install = function (Vue, opts = {}) { /* 若是已安裝就跳過 */ if (install.installed) return // 註冊插件 Vue.component(VueCrop.name, VueCrop) } // 全局狀況下注冊插件 if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) } export { install, // 此處是爲了兼容在vue內單獨引入這個插件,若是是main.js全局引入就能夠去掉 VueCrop }
import Vue from 'vue' import App from './App' import router from './router' // 新加的:導入入口文件 import { install } from 'src/components/index.js' // 全局調用,至關於調用 `MyPlugin.install(Vue)` Vue.use(install) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
// 導入vue import VueCrop from './VueCrop.vue' // Vue.js 的插件應當有一個公開方法 install 。這個方法的第一個參數是 Vue 構造器 VueCrop.install = function (Vue) { // 註冊組件 Vue.component(VueCrop.name, VueCrop) } export default VueCrop
function MyPlugin(){ console.info('構造函數') } MyPlugin.prototype.install=function(vue,options){ console.info('構造器vue:'+vue); }
而真正註冊組件的是:Vue.component()webpack
因此,vue插件註冊的過程是:git
1.調用main.js中: import { install } from 'src/components/index.js' vue.use(install) 2.index.js添加install方法,調用Vue.component註冊組件 3.組件內的index.js同全部組件的index.js同樣
在此以前,能夠先了解下組件的命名規範等,可參考文章 掘金:Vue前端開發規範,其中第2點有詳細講解
首先,肯定本身的調用方式和須要暴露的參數github
<vue-crop :crop-url="cropUrl1" :ratio="ratio" :height="460" :width="460" :previewJson="previewJson1" class="c-crop--preview_right" @afterCrop="afterCrop" > >
其中,@afterCrop="afterCrop"是裁切完成的回調函數,其餘是屬性配置web
在組件src/components/VueCrop/VueCrop.vue內,能夠用this.$emit('afterCrop')觸發demo裏的afterCrop事件vue-cli
組件結構上,主要分爲:裁切主體部分(VueCrop.vue),選框組件(VueCropTool.vue),裁切框寬度、位置座標等計算(VueCropMove.js),拖拽事件註冊公共js(components/utils/draggable.js)npm
備註:此組件不具有真實的裁切功能,最終的裁切是傳遞給後臺去裁,你若是想擴展能夠在afterCrop函數里根據座標等信息進行處理
接下來咱們對各個組件和js進行講解json
export default function (element, options) { const moveFn = function (event) { if (options.drag) { options.drag(event) } } // mousedown fn const downFn = function (event) { if (options.start) { // 調用參數中start函數 options.start(event) } } // mouseup fn const upFn = function (event) { document.removeEventListener('mousemove', moveFn) document.removeEventListener('mouseup', upFn) document.onselectstart = null document.ondragstart = null if (options.end) { // 調用參數中end函數 options.end(event) } } // 綁定事件 element.addEventListener('mousedown', event => { if (options.stop && options.stop(event, element) === false) { return false } document.onselectstart = function () { return false } document.ondragstart = function () { return false } document.addEventListener('mousedown', downFn) document.addEventListener('mousemove', moveFn) document.addEventListener('mouseup', upFn) }) }
draggable(this.$el.querySelector('.c-crop--drap_screen'), { start: (event) => { this.startPos = [event.x, event.y] }, drag: (event) => { this.handleDragLocation(event) }, end: (event) => { this.handleDragLocation(event) } })
//script部分 <script> import VueCropTool from './VueCropTool.vue' export default { name: 'VueCrop', data () { return { // 根據裁切後的縮放和座標等生成的預覽尺寸座標數組 previewImgSize: null, // 圖片初始數據 originImgSize: null, // 裁切框寬度 elWidth: 0, // 裁切框高度 elHeight: 0, // 裁切框top cursorTop: 0, // 裁切框left cursorLeft: 0, // 根據當前的容器寬高計算出的圖片尺寸 imgH: 0, imgW: 0, // 圖片url url: this.cropUrl, // 爲適應當前的容器對原始圖片的縮放值 scale: 1, // 根據當前選區和原始圖片縮放前的尺寸,來獲得最終的裁切尺寸 coord: null, // 計算出的裁切框的初始值 cropJson: { cw: null, ch: null, w: null, h: null, r: null } } }, // 暴露出去的參數,具體解釋可看前文的表格 props: { cropUrl: String, // 比例 ratio: { type: null, default: false }, width: null, height: null, coordWidth: null, coordHeight: null, previewJson: { type: Array, default: function () { return [] } } }, components: { VueCropTool }, created () { }, watch: { // 監聽圖片路徑變化 cropUrl (val) { this.url = val // setTimeout是爲了兼容立刻獲取尺寸獲取不到的狀況 setTimeout(() => { this.setSize() }, 200) } }, methods: { // 更新拖拽尺寸,大部分由裁切框組件經過@updateSize觸發 drapSizeUpdate (w, h, t, l) { // 更新裁切框尺寸 this.elWidth = w this.elHeight = h this.cursorTop = t this.cursorLeft = l // 根據當前選區獲取原始圖片縮放前的尺寸(還原原始圖片的寬高以獲取最終裁切數據) this.coord = this.getCoord(l, t, w, h) // 更新預覽尺寸 this.setPreviewSize(this.coord) }, // 裁切完畢回調 afterCrop () { this.$emit('afterCrop', this.coord, this.url) }, // 設置preview尺寸 setPreviewSize (coord) { if (!this.previewJson.length) { return false } let result = this.previewJson.map(data => { // 計算縮放比 let scale = data.width / coord.w return { scale, l: -scale * coord.l, t: -scale * coord.t, w: scale * this.originImgSize.w, h: scale * this.originImgSize.h } }) this.previewImgSize = result }, // 設置裁切顯示的圖片尺寸,存儲scale值 async setSize () { if (!this.url) { return } let imgSize = await this.getSize(this.url) this.originImgSize = imgSize this.setCoordRange() this.scale = imgSize.w / this.imgW this.cursorTop = 0 this.cursorLeft = 0 let json = {...this.cropJson} json.w = this.imgW json.h = this.imgH // 有固定比例,則按比例截取 if (this.ratio) { json.r = this.ratio if (json.w > json.h) { let r = json.h * this.ratio / json.w if (r > 1) { json.ch = json.h / r json.cw = json.ch * this.ratio } else { json.ch = json.h json.cw = json.ch * this.ratio } } else { let r = json.w / this.ratio / json.h if (r > 1) { json.cw = json.w / r json.ch = json.cw / this.ratio } else { json.cw = json.w json.ch = json.cw / this.ratio } } } else { // 無比例 json.cw = json.w json.ch = json.h } // 裁切框的尺寸(/2是取一半的值,使裁切框居中並寬度爲一半) this.elWidth = json.cw / 2 this.elHeight = json.ch / 2 this.cursorTop = json.ch / 4 this.cursorLeft = json.cw / 4 this.cropJson = {...json} this.drapSizeUpdate(this.elWidth, this.elHeight, this.cursorTop, this.cursorLeft) }, // 根據圖片本來的尺寸比例和用戶傳入的尺寸寬高設置當前可顯示的區域圖片尺寸 setCoordRange () { var size = {...this.originImgSize} var ratio1 = this.width / this.height var ratio2 = size.r if (ratio2 > ratio1) { this.imgW = this.width this.imgH = this.width / size.r } else { this.imgH = this.height this.imgW = this.height * size.r } }, // 獲取裁切後的原始座標寬高(裁切看到的寬高不是原始圖片的寬高) getCoord (l, t, w, h) { l = this.scale * l t = this.scale * t w = this.scale * w h = this.scale * h return { p0: [l, t], p1: [l + w, t], p2: [l + w, t + h], p3: [l, t + h], w: w, h: h, l: l, t: t } }, // 獲取是src圖片的尺寸 getSize (src) { let _this = this let img = this.$el.querySelector('#c-crop--hide_img') return new Promise(resolve => { if (src && img) { img.onload = function () { const size = _this.getSizeImg(img) resolve(size) } img.src = src } else { resolve({ w: 0, h: 0, r: 0 }) } }) }, // 獲取原始圖片的真實寬高、比例 getSizeImg (img) { let w = img.width let h = img.height let r = w === 0 && h === 0 ? 0 : w / h return { w: w, h: h, r: r } } }, mounted () { this.setSize() } } </script>
//script部分 <script> // 引入拖拽js import draggable from '../utils/draggable' // 引入裁切尺寸計算js import movePos from './VueCropMove' // 和VueCropMove有關,序號對應相應的操做,這些類名對應裁切框的四條邊,四個角,四個邊上的中點,拖拽由這12個位置進行 const dragEle = ['.c-crop--drap_eline', '.c-crop--drap_sline', '.c-crop--drap_wline', '.c-crop--drap_nline', '.c-crop--drap_e', '.c-crop--drap_s', '.c-crop--drap_w', '.c-crop--drap_n', '.c-crop--drap_ne', '.c-crop--drap_se', '.c-crop--drap_sw', '.c-crop--drap_nw'] export default { data () { return { width: this.elWidth, height: this.elHeight, top: this.cursorTop, left: this.cursorLeft, // 存儲拖拽開始座標(拖拽改變位置時) startPos: [0, 0], crop: [], // 計時器 cropTimer: null, // 存儲拖拽開始座標尺寸(拖拽改變尺寸時) startSize: null } }, props: ['elWidth', 'elHeight', 'cursorTop', 'cursorLeft', 'cropJson'], created () {}, watch: { elWidth (val) { this.width = val }, elHeight (val) { this.height = val }, cursorTop (val) { this.top = val }, cursorLeft (val) { this.left = val } }, methods: { // 拖拽更新位置 handleDragLocation (event) { let x = event.clientX let y = event.clientY this.left = x - this.startPos[0] + this.left this.top = y - this.startPos[1] + this.top this.startPos = [x, y] this.handleSize() // 更新尺寸 this.$emit('updateSize', this.width, this.height, this.top, this.left) clearTimeout(this.cropTimer) // setTimeout是爲了拖拽完成才調用afterCrop this.cropTimer = setTimeout(() => { // 調用回調 this.$emit('afterCrop') }, 200) }, // 拖拽改變位置:綁定事件 dragCallLocation () { draggable(this.$el.querySelector('.c-crop--drap_screen'), { start: (event) => { this.startPos = [event.x, event.y] }, drag: (event) => { this.handleDragLocation(event) }, end: (event) => { this.handleDragLocation(event) } }) }, // 根據className獲取父元素 getParentElement (p, className) { const classNames = p.className if (classNames.indexOf(className) === -1) { p = p.parentNode return this.getParentElement(p, className) } else { return p } }, // 獲取拖拽的尺寸 getDragSize (event) { const el = this.$el const screen = this.$cropArea.getBoundingClientRect() const rect = el.getBoundingClientRect() let json = { x: event.clientX, y: event.clientY, t: rect.top, b: rect.bottom, l: rect.left, r: rect.right, w: rect.width, h: rect.height, screen: screen } json.ratio = json.w / json.h return json }, // 拖拽改變大小 handleDrag (event, i) { // 獲取座標 // console.info('move', i) const json = this.getDragSize(event) movePos[i](this, json, this.startSize) this.handleSize(true) this.$emit('updateSize', this.width, this.height, this.top, this.left) clearTimeout(this.cropTimer) this.cropTimer = setTimeout(() => { // 調用回調 this.$emit('afterCrop') }, 200) }, // 拖拽改變大小:綁定事件 dragCall (i) { let target = this.$el.querySelector(dragEle[i]) draggable(target, { start: (event) => { // 開始時拖拽框json this.startSize = this.getDragSize(event) }, drag: (event) => { this.handleDrag(event, i) }, end: (event) => { this.handleDrag(event, i) } }) }, // 改變位置大小 handleSize (isSize) { this.left = range2(this.left, this.width, this.cropJson.w) this.top = range2(this.top, this.height, this.cropJson.h) if (isSize) { let d1 = this.cropJson.w - this.left let d2 = this.cropJson.h - this.top // 按比例裁切 if (this.cropJson.r) { if (d1 < this.width) { this.width = d1 this.height = this.width / this.cropJson.r } else if (d2 < this.height) { this.height = d2 this.width = this.height * this.cropJson.r } } else { // 不按比例裁切 if (d1 < this.width) { this.width = d1 } if (d2 < this.height) { this.height = d2 } } } } }, mounted () { this.$cropArea = this.getParentElement(this.$el.parentNode, 'c-crop--area') // 初始化拖拽改變大小 for (var i = 0; i < dragEle.length; i++) { this.dragCall(i) } // 初始化拖拽改變位置 this.dragCallLocation() } } // 計算容許的範圍 function range2 (pos, val, mainW) { return pos <= 0 ? 0 : pos > mainW - val ? mainW - val : pos } </script>
// 12種形態,四條邊,邊的中點,邊的四個角。e:東,w:西,n:北,s:南,ne:東南以此類推 const movePos = { 0: e, 4: e, 1: s, 5: s, 2: w, 6: w, 3: n, 7: n, 8: ne, 9: se, 10: sw, 11: nw } let width, height, result, ratio // 獲取某種形態類型的寬或高最大值 function getMaxSize (json, startJson, dire, type) { if (type === 'w') { switch (dire) { case 'e': case 's': case 'n': case 'ne': case 'se': return json.screen.right - json.l case 'w': case 'nw': case 'sw': return startJson.r - json.screen.left } } else if (type === 'h') { switch (dire) { case 'n': case 'nw': case 'ne': return startJson.b - json.screen.top case 's': case 'w': case 'e': case 'sw': case 'se': return json.screen.bottom - startJson.t } } } // 判斷是否有ratio,返回修改後的尺寸 function setRatioSize (type, json, startJson, ratio, width, height) { if (ratio) { if (width / ratio >= height) { var maxHeight = getMaxSize(json, startJson, type, 'h') height = width / ratio if (height > maxHeight) { height = maxHeight width = height * ratio } } else { var maxWidth = getMaxSize(json, startJson, type, 'w') width = height * ratio if (width > maxWidth) { width = maxWidth height = width / ratio } } } return { width: width, height: height } } // 拖拽東邊,高度是不變的,除非有比例拖拽時 function e (_this, json, startJson) { ratio = _this.cropJson.r width = range(getWidth(json, startJson, 'e'), getMaxSize(json, startJson, 'e', 'w')) if (ratio) { // 有比例時,計算高度,並對比最大值是否超出 height = range(width / ratio, getMaxSize(json, startJson, 'e', 'h')) result = setRatioSize('e', json, startJson, ratio, width, height) setSize(_this, result) } else { _this.width = width } return _this } // 拖拽南邊,寬度是不變的,除非有比例拖拽時 function s (_this, json, startJson) { ratio = _this.cropJson.r height = range(getHeight(json, startJson, 's'), getMaxSize(json, startJson, 's', 'h')) if (ratio) { // 有比例時,計算寬度,並對比最大值是否超出 width = range(height * ratio, getMaxSize(json, startJson, 's', 'w')) result = setRatioSize('s', json, startJson, ratio, width, height) setSize(_this, result) } else { _this.height = height } return _this } // 如下同上,以此類推 function w (_this, json, startJson) { ratio = _this.cropJson.r width = range(getWidth(json, startJson, 'w'), getMaxSize(json, startJson, 'w', 'w')) if (ratio) { height = range(width / ratio, getMaxSize(json, startJson, 'w', 'h')) result = setRatioSize('w', json, startJson, ratio, width, height) setSize(_this, result) _this.left = getLeft(_this, json, startJson) } else { _this.width = width _this.left = rangeMax(json.x - json.screen.left, startJson.r) } return _this } function n (_this, json, startJson) { ratio = _this.cropJson.r height = range(getHeight(json, startJson, 'n'), getMaxSize(json, startJson, 'n', 'h')) if (ratio) { width = range(height * ratio, getMaxSize(json, startJson, 'n', 'w')) result = setRatioSize('n', json, startJson, ratio, width, height) setSize(_this, result) _this.top = getTop(_this, json, startJson) } else { _this.height = height _this.top = rangeMax(json.y - json.screen.top, startJson.b) } return _this } function ne (_this, json, startJson) { height = range(getHeight(json, startJson, 'n'), getMaxSize(json, startJson, 'ne', 'h')) width = range(getWidth(json, startJson, 'e'), getMaxSize(json, startJson, 'ne', 'w')) result = setRatioSize('ne', json, startJson, _this.cropJson.r, width, height) setSize(_this, result) _this.top = getTop(_this, json, startJson) return _this } function se (_this, json, startJson) { height = range(getHeight(json, startJson, 's'), getMaxSize(json, startJson, 'se', 'h')) width = range(getWidth(json, startJson, 'e'), getMaxSize(json, startJson, 'se', 'w')) result = setRatioSize('se', json, startJson, _this.cropJson.r, width, height) setSize(_this, result) return _this } function sw (_this, json, startJson) { width = range(getWidth(json, startJson, 'w'), getMaxSize(json, startJson, 'sw', 'w')) height = range(getHeight(json, startJson, 's'), getMaxSize(json, startJson, 'sw', 'h')) result = setRatioSize('sw', json, startJson, _this.cropJson.r, width, height) setSize(_this, result) _this.left = getLeft(_this, json, startJson) return _this } function nw (_this, json, startJson) { width = range(getWidth(json, startJson, 'w'), getMaxSize(json, startJson, 'nw', 'w')) height = range(getHeight(json, startJson, 'n'), getMaxSize(json, startJson, 'nw', 'h')) result = setRatioSize('nw', json, startJson, _this.cropJson.r, width, height) setSize(_this, result) _this.left = getLeft(_this, json, startJson) _this.top = getTop(_this, json, startJson) return _this } // 匹配範圍 function range (value, max) { value = value > max ? max : value return value < 20 ? 20 : value } // 最大值 function rangeMax (value, max) { return value > max ? max : value } // top function getTop (_this, json, startJson) { return rangeMax(startJson.b - _this.height - json.screen.top, startJson.b) } // left function getLeft (_this, json, startJson) { return rangeMax(startJson.r - _this.width - json.screen.left, startJson.r) } // height:只存在於s||n類型 function getHeight (json, startJson, type) { return type === 'n' ? startJson.b - json.y : json.y - startJson.t } // width:只存在於w||e類型 function getWidth (json, startJson, type) { return type === 'w' ? startJson.r - json.x : json.x - startJson.l } // setSize function setSize (_this, result) { _this.width = result.width _this.height = result.height } export default movePos
今天就分享到這裏啦~喜歡這個插件能夠去 github star~