vue版本裁切工具,包含預覽功能html
最終效果: qiuyaofan.github.io/vue-crop-de…前端
源碼地址: github.com/qiuyaofan/v…vue
// 初始化vue-cli
vue init webpack my-plugin
複製代碼
新建src/views/validSlideDemo.vue,
src/components裏新建VueCrop/index.js,VueCrop.vue,
在routes/index.js配置訪問路由(具體看github源碼)
複製代碼
最終生成的文件結構以下圖: webpack
// 導入插件入口文件
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()git
因此,vue插件註冊的過程是:github
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點有詳細講解web
首先,肯定本身的調用方式和須要暴露的參數vue-cli
<vue-crop
:crop-url="cropUrl1"
:ratio="ratio"
:height="460"
:width="460"
:previewJson="previewJson1"
class="c-crop--preview_right"
@afterCrop="afterCrop"
>
>
複製代碼
其中,@afterCrop="afterCrop"是裁切完成的回調函數,其餘是屬性配置npm
在組件src/components/VueCrop/VueCrop.vue內,能夠用this.$emit('afterCrop')觸發demo裏的afterCrop事件json
組件結構上,主要分爲:裁切主體部分(VueCrop.vue),選框組件(VueCropTool.vue),裁切框寬度、位置座標等計算(VueCropMove.js),拖拽事件註冊公共js(components/utils/draggable.js)
備註:此組件不具有真實的裁切功能,最終的裁切是傳遞給後臺去裁,你若是想擴展能夠在afterCrop函數里根據座標等信息進行處理
接下來咱們對各個組件和js進行講解
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~