最近需要做一个投票活动,上传图片时需要拖拽、缩放来裁剪图片,vue的组件不少,不过自己动手才能丰衣足食,一味使用别人的组件实在难以进步,所以自己研究一番。
<div @mousedown="gMousedown" @mouseenter="gMouseenter" @mouseleave="gMouseleave" @mousemove="gMousemove" @mouseout="gMouseout" @mouseover="gMouseover" @mouseup="gMouseup"> 试一试移动 </div> methods:{ gMousedown(e){ console.log("鼠标左键按下了"); console.log(e) }, gMouseenter(e){ console.log("鼠标穿过了"); console.log(e) }, gMouseleave(e){ console.log("鼠标离开了"); console.log(e) }, gMousemove(e){ console.log("鼠标移动了"); console.log(e) }, gMouseout(e){ console.log("鼠标移开了"); console.log(e) }, gMouseover(e){ console.log("鼠标在元素上面了"); console.log(e) }, gMouseup(e){ console.log("鼠标松开了"); console.log(e) } }
<template>
<div style="margin-top: 66px;">
<div class="drag-container">
<p v-for="item in list"
draggable="true"
@dragstart="dragstart($event,item,‘list‘)"
@dragend="dragend($event,item)"
>{{item.name}}</p>
</div>
<div class="drop-container">
<p>分层</p>
<div class="drop-area" v-if="showLayer"
@drop="drop($event,‘layer‘)"
@dragenter="dragenter"
@dragover="dragover"
>
<p v-for="(item,index) in layer">
<span
draggable="true"
@dragstart="dragstart($event,item,‘layer‘,index)"
>{{item.name}}</span>
<b v-if="index!==layer.length-1">-></b>
</p>
</div>
<p>维度</p>
<div class="drop-area"
@drop="drop($event,‘dimensions‘)"
@dragover="dragover">
<span v-for="(item,index) in dimensions"
@drop="dropToItem($event,item)"
@dragover.prevent
draggable="true"
@dragstart="dragstart($event,item,‘dimensions‘,index)"
>{{item.name}}</span>
</div>
<p>对比</p>
<div class="drop-area"
@drop="drop($event,‘contrasts‘)"
@dragover="dragover">
<span v-for="(item,index) in contrasts"
@dragover.prevent
draggable="true"
@dragstart="dragstart($event,item,‘contrasts‘,index)"
>{{item.name}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
dragItem: {},//拖动的字段
dragFrom: ‘‘,//拖动从哪开始
dragIndex: ‘‘,//拖动的字段在数组中的索引
dropIndex: ‘‘,//放入地方的字段的索引
showLayer: false,//是否显示分层
isDropToItem: false,//是否是拖动到维度字段中进行分层操作
layer: [],
dimensions: [],
contrasts: [],
list: [
{
id: 1,
name: ‘姓名‘
}, {
id: 2,
name: ‘性别‘
}, {
id: 3,
name: ‘年龄‘
}, {
id: 4,
name: ‘分数‘
}]
}
},
methods: {
//拖拽开始
dragstart(event, item, frm, index) {
console.log(‘拖拽开始‘);
console.log(event);
console.log(item);
console.log(frm);
console.log(index);
const that = this;
event.dataTransfer.setData("Text", event.target.id);
that.dragItem = item;
that.dragFrom = frm;
if (!isNaN(index)) {
that.dragIndex = index;
}
},
//进入拖拽区域,进入时触发一次
dragenter(event) {
console.log(‘进入拖拽区域,进入时触发一次‘);
console.log(event);
},
//进入拖拽区域后多次触发
dragover(event) {
console.log("进入拖拽区域后多次触发");
console.log(event);
const that = this;
event.preventDefault();
let target = event.target;
that.dropIndex = that.indexFn(target);
let nodeName = target.nodeName;
if (nodeName !== ‘SPAN‘) {
that.dropIndex = -1;
}
},
//松开鼠标完成拖拽后触发
drop(event, target) {
console.log(‘松开鼠标完成拖拽后触发‘);
console.log(event);
console.log(target);
const that = this;
let dragFrom = that.dragFrom;
let dragItem = that.dragItem;
let dragIndex = that.dragIndex;
let dropIndex = that.dropIndex;
if (that.isDropToItem) {
return;
}
if (that.dragFrom === ‘layer‘) {
that.layer.splice(dragIndex, 1);
} else if (that.dragFrom === ‘dimensions‘) {
that.dimensions.splice(dragIndex, 1);
} else if (that.dragFrom === ‘contrasts‘) {
that.contrasts.splice(dragIndex, 1);
}
if (dragFrom === target && dropIndex > -1) {
let targetArr = that[target];
targetArr.splice(dropIndex, 0, dragItem);
return;
}
if (target === ‘layer‘) {
that.layer.push(dragItem);
} else if (target === ‘dimensions‘) {
that.dimensions.push(dragItem);
} else if (target === ‘contrasts‘) {
that.contrasts.push(dragItem);
}
},
//拖动到维度第一个字段时触发
dropToItem(event, item) {
console.log(‘拖动到维度第一个字段时触发‘);
console.log(event);
console.log(item);
const that = this;
if (that.dragFrom !== ‘list‘) {
that.isDropToItem = false;
return;
} else {
that.isDropToItem = true
}
if (that.showLayer) {
that.layer.push(this.dragItem);
} else {
that.showLayer = true;
that.layer.push(item);
that.layer.push(this.dragItem);
}
},
//拖拽结束后触发,不管是否拖拽成功
dragend(event, item) {
console.log(‘拖拽结束后触发,不管是否拖拽成功‘);
console.log(event);
console.log(item);
const that = this;
that.isDropToItem = false
},
//判断拖动字段的所以,用于排序
indexFn(el) {
let index = 0;
if (!el || !el.parentNode) {
return -1;
}
while (el && (el = el.previousElementSibling)) {
index++;
}
return index;
},
}
}
</script>
<style scoped lang="less">
/* css部分 */
.drag-container {
width: 66px;
height: 500px;
float: left;
background-color: #BAB5F5;
p {
line-height: 40px;
text-align: center;
cursor: pointer;
}
}
.drop-container {
margin-left: 120px;
float: left;
height: 500px;
width: 600px;
p {
color: #fff;
line-height: 40px;
}
.drop-area {
height: 80px;
width: 600px;
background-color: #4c72ff;
padding: 10px;
p {
display: inline-block;
margin: 0;
}
span {
display: inline-block;
min-width: 50px;
line-height: 40px;
margin-right: 5px;
background-color: #5a3e99;
text-align: center;
cursor: pointer;
}
}
}
</style>
touch:移动端的触摸事件,触发的条件是手指按在屏幕上并且移动(手指不能离开屏幕)
(1)、touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
(2)、touchmove:当手指在屏幕上滑动时连续的触发。在这个事件发生期间,调用preventDefault()可阻止滚动。
(3)、touchend:当手指从屏幕上移开时触发。
(4)、touchcancel:当系统停止跟踪触摸时触发。关于此事件的确切触发事件,文档中没有明确说明。
3.1、以上event对象都包含以下属性:
(1)、touches:表示当前跟踪的触摸操作的Touch对象的数组。
(2)、targetTouches:特定于事件目标的Touch对象的数组。
(3)、changeTouches:表示自上次触摸以来发生了什么改变的Touch对象的数组。
3.2、每个Touch对象包含下列属性:
(1)、clientX:触摸目标在视口中的X坐标。
(2)、clientY:触摸目标在视口中的Y坐标。
(3)、identifier:表示触摸的唯一ID。
(4)、pageX:触摸目标在页面中的x坐标。
(5)、pageY:触摸目标在页面中的y坐标。
(6)、screenX:触摸目标在屏幕中的x坐标。
(7)、screenY:触摸目标在屏幕中的y坐标。
(8)、target:触摸的DOM节点坐标
通过touchstart和touchmove实现拖拽图片:
(1)、touchstart:获取touches[0]的pageX,pageY来更新scx与scy以及更新iX与iY
(2)、touchmovw:获取touches[0]的pageX,声明变量f1x存放,移动后的x坐标等于iX + f1x - scx,y坐标同理,最后调用_drawImage来更新图片
<template>
<div class="clipper-container" ref="clipper">
<canvas ref="canvas"></canvas>
<!-- 裁剪部分 -->
<div class="clipper-part">
<div class="pCanvas-container">
<canvas ref="pCanvas"></canvas>
</div>
</div>
<!-- 底部操作栏 -->
<div class="action-bar">
<button class="btn-cancel" @click="cancel">取消</button>
<button class="btn-ok" @click="clipper">确认</button>
</div>
<!-- 背景遮罩 -->
<div class="mask" :class="{opacity: maskShow}"></div>
<!-- 手势操作层 -->
<div class="gesture-mask" ref="gesture" @touchstart="getTouchstart" @touchmove="getTouchmove" @touchend="getTouchend"></div>
</div>
</template>
<style lang="less">
.position() {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 100;
}
.clipper-container {
.position();
line-height: 0;
background-color: #000;
.clipper-part {
.position();
bottom: 61px;
z-index: 102;
.pCanvas-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 2px solid #fff;
}
}
.action-bar {
box-sizing: content-box;
.position();
top: auto;
z-index: 103;
height: 60px;
line-height: 60px;
border-top: 1px solid rgba(256, 256, 256, 0.3);
button {
display: block;
padding: 0 15px;
line-height: 60px;
font-size: 16px;
color: #fff;
background: none;
border: none;
outline: 0;
&.btn-cancel {
float: left;
}
&.btn-ok {
float: right;
}
}
}
.mask {
.position();
z-index: 101;
transition: opacity 500ms;
background-color: #000;
opacity: 0;
&.opacity {
opacity: 0.8;
}
}
.gesture-mask {
.position();
bottom: 61px;
z-index: 103;
}
}
</style>
<script>
export default {
name: ‘imageTailor‘,
props: {
img: String, //url或dataUrl
clipperImgWidth: {
type: Number,
default: 500
},
clipperImgHeight: {
type: Number,
default: 200
}
},
watch: {
img() {
const that = this;
that.loadImgQueue.push(that.img);
that.loadImg();
}
},
data() {
return {
originXDiff: 0, //裁剪canvas与原图canvas坐标原点上的差值
originYDiff: 0,
maskShow: true,
maskShowTimer: null,
ctx: null,
pCtx: null,
actionBarHeight: 61,
loadImgQueue: [], //加载图片队列
imageData: null,
imgLoaded: false,
imgLoading: false,
imgStartWidth: null,
imgStartHeight: null,
imgCurrentWidth: null,
imgCurrentHeight: null,
imgX: null, //img对于canvas的坐标
imgY: null,
imgScale: 1, //图片目前的缩放倍数 范围是1-5
imgMinScale: 1,
imgMaxScale: 5,
imgScaleStep: 60, //缩放步长,每60px加减0.1
//图片canvas宽高
cWidth: 0,
cHeight: 0,
scx:0,//对于单手操作是移动的起点坐标,对于缩放是图片距离两手指的中点最近的图标。
scy:0,
iX:0,
iY:0,
}
},
mounted() {
setTimeout(() => {
this.initClipper();
}, 10);
},
beforeDestroy() {
let getGesture = this.$refs.gesture;
getGesture.ontouchstart = null;
getGesture.ontouchmove = null;
getGesture.outouchend = null;
},
methods: {
initClipper() {
const that = this;
that.loadImgQueue.push(that.img);
that.initCanvas();
that.loadImg();
// that.initCanvas();
},
initCanvas() {
const that = this;
let getCanvas = that.$refs.canvas,
getPCanvas = that.$refs.pCanvas,
clipperClientRect = that.$refs.clipper.getBoundingClientRect(),
clipperWidth = parseInt(that.clipperImgWidth / window.devicePixelRatio),
clipperHeight = parseInt(that.clipperImgHeight / window.devicePixelRatio);
that.ctx = getCanvas.getContext(‘2d‘);
that.pCtx = getPCanvas.getContext(‘2d‘);
//判断clipperWidth与clipperHeight有没有超过容器值
if (clipperWidth < 0 || clipperWidth > clipperClientRect.width) {
clipperWidth = 250
}
if (clipperHeight < 0 || clipperHeight > clipperClientRect.height) {
clipperHeight = 100
}
//因为canvas在手机上会被放大,因此里面的内容会模糊,这里根据手机的devicePixelRatio来放大canvas,然后再通过设置css来收缩,因此关于canvas的所有值或坐标都要乘以devicePixelRatio
getCanvas.style.width = clipperClientRect.width + ‘px‘;
getCanvas.style.height = clipperClientRect.height-50 + ‘px‘;
getCanvas.width = that.ratio(clipperClientRect.width);
getCanvas.height = that.ratio(clipperClientRect.height-50);
getPCanvas.style.width = clipperWidth + ‘px‘;
getPCanvas.style.height = clipperHeight-50 + ‘px‘;
getPCanvas.width = that.ratio(clipperWidth);
getPCanvas.height = that.ratio(clipperHeight-50);
//计算两个canvas原点的x y差值
let cClientRect = getCanvas.getBoundingClientRect(),
pClientRect = getPCanvas.getBoundingClientRect();
that.originXDiff = pClientRect.left - cClientRect.left;
that.originYDiff = pClientRect.top - cClientRect.top;
that.cWidth = cClientRect.width;
that.cHeight = cClientRect.height;
},
getTouchstart(event){
const that = this;
let cClientRect = this.$refs.canvas.getBoundingClientRect(),
fingers = {}; //记录当前有多少只手指在触控屏幕
event.preventDefault();
//two finger
let figureDistance = 0;
if (!that.imgLoaded) {
return;
}
if (event.touches.length === 1) {
let finger = event.touches[0];
that.scx = finger.pageX;
that.scy = finger.pageY;
that.iX = that.imgX;
that.iY = that.imgY;
fingers[finger.identifier] = finger;
} else if (event.touches.length === 2) {
let finger1 = event.touches[0],
finger2 = event.touches[1],
f1x = finger1.pageX - cClientRect.left,
f1y = finger1.pageY - cClientRect.top,
f2x = finger2.pageX - cClientRect.left,
f2y = finger2.pageY - cClientRect.top;
that.scx = parseInt((f1x + f2x) / 2);
that.scy = parseInt((f1y + f2y) / 2);
figureDistance = that.pointDistance(f1x, f1y, f2x, f2y);
fingers[finger1.identifier] = finger1;
fingers[finger2.identifier] = finger2;
//判断变换中点是否在图片中,如果不是则去离图片最近的点
if (that.scx < that.imgX) {
that.scx = that.imgX;
}
if (that.scx > that.imgX + that.imgCurrentWidth) {
that.scx = that.imgX + that.imgCurrentHeight;
}
if (that.scy < that.imgY) {
that.scy = that.imgY;
}
if (that.scy > that.imgY + that.imgCurrentHeight) {
that.scy = that.imgY + that.imgCurrentHeight;
}
}
},
getTouchmove(event){
const that = this;
let cClientRect = that.$refs.canvas.getBoundingClientRect(),
fingers = {}; //记录当前有多少只手指在触控屏幕
//two finger
let figureDistance = 0,
pinchScale = that.imgScale;
event.preventDefault();
if (!that.imgLoaded) {
return;
}
that.maskShowTimer && clearTimeout(that.maskShowTimer);
that.maskShow = false;
if (event.touches.length === 1) {
let f1x = event.touches[0].pageX,
f1y = event.touches[0].pageY;
that.drawImage(that.iX + f1x - that.scx, that.iY + f1y - that.scy, that.imgCurrentWidth, that.imgCurrentHeight);
} else if (event.touches.length === 2) {
let finger1 = event.touches[0],
finger2 = event.touches[1],
f1x = finger1.pageX - cClientRect.left,
f1y = finger1.pageY - cClientRect.top,
f2x = finger2.pageX - cClientRect.left,
f2y = finger2.pageY - cClientRect.top,
newFigureDistance = that.pointDistance(f1x, f1y, f2x, f2y),
scale = that.imgScale + parseFloat(((newFigureDistance - figureDistance) / that.imgScaleStep).toFixed(1));
fingers[finger1.identifier] = finger1;
fingers[finger2.identifier] = finger2;
if (scale !== pinchScale) {
//目前缩放的最小比例是1,最大是5
if (scale < that.imgMinScale) {
scale = that.imgMinScale;
} else if (scale > that.imgMaxScale) {
scale = that.imgMaxScale;
}
pinchScale = scale;
that.scale(that.scx, that.scy, scale);
}
}
},
getTouchend(event){
const that = this;
let fingers = {}; //记录当前有多少只手指在触控屏幕
//two finger
let pinchScale = that.imgScale;
if (!that.imgLoaded) {
return;
}
that.imgScale = pinchScale;
//从finger删除已经离开的手指
let touches = Array.prototype.slice.call(event.changedTouches, 0);
touches.forEach(item => {
delete fingers[item.identifier];
});
//迭代fingers,如果存在finger则更新scx,scy,iX,iY,因为可能缩放后立即单指拖动
let i,
fingerArr = [];
for(i in fingers) {
if (fingers.hasOwnProperty(i)) {
fingerArr.push(fingers[i]);
}
}
if (fingerArr.length > 0) {
that.scx = fingerArr[0].pageX;
that.scy = fingerArr[0].pageY;
} else {
that.maskShowTimer = setTimeout(() => {
that.maskShow = true;
}, 300);
}
//做边界值检测
let x = that.imgX,
y = that.imgY,
pClientRect = that.$refs.pCanvas.getBoundingClientRect();
if (x > pClientRect.left + pClientRect.width) {
x = pClientRect.left
} else if (x + that.imgCurrentWidth < pClientRect.left) {
x = pClientRect.left + pClientRect.width - that.imgCurrentWidth;
}
if (y > pClientRect.top + pClientRect.height) {
y = pClientRect.top;
} else if (y + that.imgCurrentHeight < pClientRect.top) {
y = pClientRect.top + pClientRect.height - that.imgCurrentHeight;
}
if (that.imgX !== x || that.imgY !== y) {
that.drawImage(x, y, that.imgCurrentWidth, that.imgCurrentHeight);
}
},
loadImg() {
const that = this;
if (that.imgLoading || that.loadImgQueue.length === 0) {
return;
}
let img = that.loadImgQueue.shift();
if (!img) {
return;
}
let newImage = new Image(),
onLoad = e => {
newImage.removeEventListener(‘load‘, onLoad, false);
that.imageData = newImage;
that.imgLoaded = true;
that.imgLoading = false;
that.initImg(newImage.width, newImage.height);
that.loadImg();
},
onError = e => {
newImage.removeEventListener(‘error‘, onError, false);
that.imageData = newImage = null;
that.imgLoading = false;
that.loadImg();
};
that.imgLoading = true;
that.imgLoaded = false;
newImage.src = that.img;
newImage.crossOrigin = ‘Anonymous‘; //因为canvas toDataUrl不能操作未经允许的跨域图片,这需要服务器设置Access-Control-Allow-Origin头
newImage.addEventListener(‘load‘, onLoad, false);
newImage.addEventListener(‘error‘, onError, false);
},
initImg(w, h) {
const that = this;
let eW = null,
eH = null,
maxW = that.cWidth,
maxH = that.cHeight - that.actionBarHeight;
//如果图片的宽高都少于容器的宽高,则不做处理
if (w <= maxW && h <= maxH) {
eW = w;
eH = h;
} else if (w > maxW && h <= maxH) {
eW = maxW;
eH = parseInt(h / w * maxW);
} else if (w <= maxW && h > maxH) {
eW = parseInt(w / h * maxH);
eH = maxH;
} else {
//判断是横图还是竖图
if (h > w) {
eW = parseInt(w / h * maxH);
eH = maxH;
} else {
eW = maxW;
eH = parseInt(h / w * maxW);
}
}
if (eW <= maxW && eH <= maxH) {
//记录其初始化的宽高,日后的缩放功能以此值为基础
that.imgStartWidth = eW;
that.imgStartHeight = eH;
that.drawImage((maxW - eW) / 2, (maxH - eH) / 2, eW, eH);
} else {
that.initImg(eW, eH);
}
},
drawImage(x, y, w, h) {
const that = this;
that.clearCanvas();
that.imgX = parseInt(x);
that.imgY = parseInt(y);
that.imgCurrentWidth = parseInt(w);
that.imgCurrentHeight = parseInt(h);
//更新canvas
that.ctx.drawImage(that.imageData, that.ratio(x), that.ratio(y), that.ratio(w), that.ratio(h));
//更新pCanvas,只需要减去两个canvas坐标原点对应的差值即可
// this.pCtx.drawImage(this.imageData, this.ratio(x - this.originXDiff), this.ratio(y - this.originYDiff), this.ratio(w), this.ratio(h));
},
clearCanvas() {
const that = this;
let getCanvas = that.$refs.canvas,
getPCanvas = that.$refs.pCanvas;
getCanvas.width = getCanvas.width;
getCanvas.height = getCanvas.height;
getPCanvas.width = getPCanvas.width;
getPCanvas.height = getPCanvas.height;
},
ratio(size) {
return parseInt(window.devicePixelRatio * size);
},
pointDistance(x1, y1, x2, y2) {
return parseInt(Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
},
scale(x, y, scale) {
const that = this;
let newPicWidth = parseInt(that.imgStartWidth * scale),
newPicHeight = parseInt(that.imgStartHeight * scale),
newIX = parseInt(x - newPicWidth * (x - that.imgX) / that.imgCurrentWidth),
newIY = parseInt(y - newPicHeight * (y - that.imgY) / that.imgCurrentHeight);
that.drawImage(newIX, newIY, newPicWidth, newPicHeight);
},
clipper() {
const that = this;
let imgData = null;
try {
imgData = that.$refs.pCanvas.toDataURL();
} catch (e) {
console.error(‘请在response header加上Access-Control-Allow-Origin,否则canvas无法裁剪未经许可的跨域图片‘);
}
this.$emit(‘getNewImage‘, imgData);
},
cancel() {
this.$emit(‘cancel‘);
},
getBase64(dataURL) {
return dataURL.replace(/^data:image\/(png|jpg);base64,/, ‘‘);
}
}
}
</script>
最后,ε=(′ο`*)))唉路漫漫其修远兮,我这只后端的小菜鸟在前端的路上还要奋力向前,不说了接着接收运营大佬的神仙建议去了~~
拖拽一个元素如此简单,mouse、drag、touch三兄弟的用处
原文:https://www.cnblogs.com/dinghaoran/p/14212237.html