Last active
November 24, 2018 05:36
-
-
Save syrxw/3f14445d29f4df8e85f7d9b766fbcd8e to your computer and use it in GitHub Desktop.
vue+svg 实现缩放
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<svg width="800px" height="800px" version="1.1" | |
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800"> | |
<g class="port_output" style="pointer-events: all;"> | |
<rect fill="#ddd" stroke-width="1.5" | |
:x="listData['001'].x" :y="listData['001'].y" | |
:width="listData['001'].width" :height="listData['001'].height" | |
:transform="listData['001'].transform" | |
style="vector-effect: non-scaling-stroke" | |
class="port" @click="elementClick($event,'001')" stroke-dasharray="none" stroke="#000"></rect> | |
<rect fill="#ddd" stroke-width="1.5" | |
:x="listData['002'].x" :y="listData['002'].y" | |
:width="listData['002'].width" :height="listData['002'].height" | |
:transform="listData['002'].transform" | |
style="vector-effect: non-scaling-stroke" | |
class="port" @click="elementClick($event,'002')" stroke-dasharray="none" stroke="#000"></rect> | |
</g> | |
<!--<path :d="item" stroke="red" stroke-width="2" fill="none" v-for="(item,index) in listData"--> | |
<!--:key="index"></path>--> | |
<g class="selected-group" :display="selectGroup.info.isShow" :transform="selectGroup.transform"> | |
<path :d="selectGroup.path" stroke="blue" stroke-width="1" fill="none"></path> | |
<image :xlink:href="require('../assets/rotate.png')" | |
:x="selectGroup.rotate.x" :y="selectGroup.rotate.y" | |
:width="selectGroup.rotate.width" :height="selectGroup.rotate.height" | |
@mousedown="shapeRotate($event)"></image> | |
<circle :cx="selectGroup.connect.cw.x" :cy="selectGroup.connect.cw.y" r="2.5" | |
stroke="blue" fill="white"></circle> | |
<circle :cx="selectGroup.connect.ce.x" :cy="selectGroup.connect.ce.y" r="2.5" | |
stroke="blue" fill="white"></circle> | |
<rect style="cursor:nw-resize" | |
:x="selectGroup.resize.nw.x - 2.5" :y="selectGroup.resize.nw.y - 2.5" | |
stroke="blue" fill="white" | |
width="5" height="5" | |
class="port" | |
@mousedown="shapeScale($event,type='nw')" | |
></rect> | |
<rect style="cursor:ne-resize" | |
:x="selectGroup.resize.ne.x - 2.5" :y="selectGroup.resize.ne.y - 2.5" | |
stroke="blue" fill="white" | |
width="5" height="5" | |
class="port" | |
@mousedown="shapeScale($event,type='ne')" | |
></rect> | |
<rect style="cursor:sw-resize" | |
:x="selectGroup.resize.sw.x - 2.5" :y="selectGroup.resize.sw.y - 2.5" | |
stroke="blue" fill="white" | |
width="5" height="5" | |
class="port" | |
@mousedown="shapeScale($event,type='sw')" | |
></rect> | |
<rect style="cursor:se-resize" | |
:x="selectGroup.resize.se.x - 2.5" :y="selectGroup.resize.se.y - 2.5" | |
stroke="blue" fill="white" | |
width="5" height="5" | |
class="port" | |
@mousedown="shapeScale($event,type='se')" | |
></rect> | |
<rect :x="selectGroup.center.x - 2.5" :y="selectGroup.center.y - 2.5" | |
stroke="blue" fill="white" | |
width="5" height="5" | |
class="port" | |
@mousedown="shapeScale" | |
></rect> | |
</g> | |
</svg> | |
</template> | |
<script> | |
import * as d3 from 'd3'; | |
export default { | |
name: 'home', | |
data() { | |
return { | |
clickPoint: { | |
x: '', | |
y: '' | |
}, | |
listData: { | |
'001': { | |
width: 100, | |
height: 100, | |
x: 200, | |
y: 100, | |
transform: '', | |
translateX: '', | |
translateY: '', | |
rotate: 0, | |
}, | |
'002': { | |
width: 150, | |
height: 150, | |
x: 450, | |
y: 100, | |
transform: '', | |
translateX: '', | |
translateY: '', | |
rotate: 0 | |
} | |
}, | |
selectGroup: { | |
info: { | |
isShow: 'none', | |
}, | |
resize: { | |
nw: { | |
name: '', | |
x: '', | |
y: '' | |
}, | |
sw: { | |
name: '', | |
x: '', | |
y: '' | |
}, | |
ne: { | |
name: '', | |
x: '', | |
y: '' | |
}, | |
se: { | |
name: '', | |
x: '', | |
y: '' | |
}, | |
}, | |
path: "", | |
rotate: { | |
x: '0', | |
y: '0', | |
width: '20', | |
height: '20', | |
angle: 0 | |
}, | |
connect: { | |
cw: { | |
name: '', | |
x: '0', | |
y: '0' | |
}, | |
ce: { | |
name: '', | |
x: '0', | |
y: '0' | |
}, | |
}, | |
center: { | |
name: '', | |
x: '', | |
y: '' | |
}, | |
transform: '' | |
}, | |
selectedItem: { | |
target: null, | |
id: "" | |
}, | |
scaleValue: { | |
x: 1, | |
y: 1 | |
}, | |
} | |
}, | |
computed: {}, | |
mounted() { | |
}, | |
methods: { | |
initSelectGroup() { | |
//初始化数据 | |
const target = this.listData[this.selectedItem.id] | |
target.translateX = target.x + target.width | |
target.translateY = target.y + target.height | |
this.scaleValue.x = 1 | |
this.scaleValue.y = 1 | |
this.selectGroup.rotate.angle = target.rotate | |
}, | |
drawSelectPoint(target) { // 画点 | |
//左上角 | |
this.selectGroup.resize.nw = { | |
name: 'nw', | |
x: target.x, | |
y: target.y | |
} | |
//左下角 | |
this.selectGroup.resize.sw = { | |
name: 'sw', | |
x: target.x, | |
y: target.y + target.height | |
} | |
//右上角 | |
this.selectGroup.resize.ne = { | |
name: 'ne', | |
x: target.x + target.width, | |
y: target.y | |
} | |
//右下角 | |
this.selectGroup.resize.se = { | |
name: 'se', | |
x: target.x + target.width, | |
y: target.y + target.height | |
} | |
//左中角 | |
this.selectGroup.connect.cw = { | |
name: 'cw', | |
x: target.x, | |
y: target.y + target.height / 2 | |
} | |
//右中角 | |
this.selectGroup.connect.ce = { | |
name: 'ce', | |
x: target.x + target.width, | |
y: target.y + (target.height / 2) | |
} | |
//中心点 | |
this.selectGroup.center = { | |
name: 'center', | |
x: target.x + (target.width / 2), | |
y: target.y + (target.height / 2) | |
} | |
}, | |
drawSelectLine() { // 画线 | |
const m1 = `M${this.selectGroup.resize.nw.x}, ${this.selectGroup.resize.nw.y}` | |
const m2 = `L${this.selectGroup.resize.ne.x}, ${this.selectGroup.resize.ne.y}` | |
const m3 = `L${this.selectGroup.resize.se.x}, ${this.selectGroup.resize.se.y}` | |
const m4 = `L${this.selectGroup.resize.sw.x}, ${this.selectGroup.resize.sw.y}` | |
const m5 = `L${this.selectGroup.resize.nw.x}, ${this.selectGroup.resize.nw.y}` | |
this.selectGroup.path = `${m1}${m2}${m3}${m4}${m5}` | |
}, | |
drawSelectRotate(target) {// 画旋转图标 | |
this.selectGroup.rotate.x = this.selectGroup.center.x - this.selectGroup.rotate.width / 2 | |
this.selectGroup.rotate.y = this.selectGroup.center.y - (target.height / 2) - this.selectGroup.rotate.height * 1.5 | |
}, | |
drawSelectAngle() { | |
const target = this.listData[this.selectedItem.id] | |
if (target.rotate !== 0) { | |
this.selectGroup.transform = `rotate(${target.rotate},${this.selectGroup.center.x},${this.selectGroup.center.y})` | |
}else{ | |
this.selectGroup.transform = '' | |
} | |
}, | |
drawSelect(target) { | |
this.drawSelectPoint(target) | |
this.drawSelectLine() | |
this.drawSelectRotate(target) | |
this.drawSelectAngle() | |
}, | |
elementClick(e, id) { | |
this.selectedItem.id = id; | |
this.selectedItem.target = e.target | |
this.initSelectGroup() | |
const p_data = this.calculatePointer('nw') | |
this.drawSelect(p_data) | |
this.selectGroup.info.isShow = 'inline' | |
}, | |
calculatePointer(type) { | |
let return_data = {} | |
const target = this.listData[this.selectedItem.id] | |
return_data.width = target.width * Math.abs(this.scaleValue.x) | |
return_data.height = target.height * Math.abs(this.scaleValue.y) | |
//计算重绘坐标点 逻辑相对复杂 | |
if (type === 'nw') { | |
return_data.x = target.translateX - return_data.width | |
return_data.y = target.translateY - return_data.height | |
if (this.scaleValue.x < 0) { | |
return_data.x = target.x + target.width | |
} | |
if (this.scaleValue.y < 0) { | |
return_data.y = target.y + target.height | |
} | |
} | |
if (type === 'ne') { | |
return_data.x = target.translateX | |
return_data.y = target.translateY - return_data.height | |
if (this.scaleValue.x < 0) { | |
return_data.x = target.x - return_data.width | |
} | |
if (this.scaleValue.y < 0) { | |
return_data.y = target.y + target.height | |
} | |
} | |
if (type === 'sw') { | |
return_data.x = target.translateX - return_data.width | |
return_data.y = target.translateY | |
if (this.scaleValue.x < 0) { | |
return_data.x = target.x + target.width | |
} | |
if (this.scaleValue.y < 0) { | |
return_data.y = target.y - return_data.height | |
} | |
} | |
if (type === 'se') { | |
return_data.x = target.x | |
return_data.y = target.y | |
if (this.scaleValue.x < 0) { | |
return_data.x = target.x - return_data.width | |
} | |
if (this.scaleValue.y < 0) { | |
return_data.y = target.y - return_data.height | |
} | |
} | |
return return_data | |
}, | |
shapeScale(e, type) { | |
const target = this.listData[this.selectedItem.id] | |
//初始计算位移距离,让scale保持原地缩放 | |
if (type === 'nw') { | |
const tx = target.x + target.width | |
const ty = target.y + target.height | |
this.scaleFunc(e, tx, ty, 'nw') | |
} | |
if (type === 'ne') { | |
const tx = target.x | |
const ty = target.y + target.height | |
this.scaleFunc(e, tx, ty, 'ne') | |
} | |
if (type === 'sw') { | |
const tx = target.x + target.width | |
const ty = target.y | |
this.scaleFunc(e, tx, ty, 'sw') | |
} | |
if (type === 'se') { | |
const tx = target.x | |
const ty = target.y | |
this.scaleFunc(e, tx, ty, 'se') | |
} | |
}, | |
scaleFunc(e, tx, ty, type) { | |
const target = this.listData[this.selectedItem.id] | |
target.translateX = tx | |
target.translateY = ty | |
const pos_x = e.clientX - target.translateX | |
const pos_y = e.clientY - target.translateY | |
let isMoving = false | |
document.onmousemove = (e) => { | |
const move_x = e.clientX - target.translateX | |
const move_y = e.clientY - target.translateY | |
this.scaleValue = { | |
x: move_x / pos_x, | |
y: move_y / pos_y | |
} | |
const p_data = this.calculatePointer(type) | |
this.drawSelect(p_data) | |
if (!isNaN(this.scaleValue.x) && !isNaN(this.scaleValue.y)) { | |
if (target.rotate !== 0) { | |
target.transform = `rotate(${target.rotate},${this.selectGroup.center.x},${this.selectGroup.center.y}) translate(${target.translateX},${target.translateY}) scale(${this.scaleValue.x},${this.scaleValue.y}) translate(-${target.translateX},-${target.translateY})` | |
} else { | |
target.transform = `translate(${target.translateX},${target.translateY}) scale(${this.scaleValue.x},${this.scaleValue.y}) translate(-${target.translateX},-${target.translateY})` | |
} | |
} | |
isMoving = true | |
}; | |
document.onmouseup = () => { | |
document.onmousemove = null; | |
document.onmouseup = null; | |
if (isMoving) { | |
const p_data = this.calculatePointer(type) | |
const target = this.listData[this.selectedItem.id] | |
target.x = p_data.x | |
target.y = p_data.y | |
target.width = p_data.width | |
target.height = p_data.height | |
if (target.rotate !== 0) { | |
target.transform = `rotate(${target.rotate},${this.selectGroup.center.x},${this.selectGroup.center.y})` | |
} else { | |
target.transform = '' | |
} | |
} | |
}; | |
}, | |
shapeRotate(e) { | |
console.log(e) | |
this.initSelectGroup() | |
const target = this.listData[this.selectedItem.id] | |
const center = this.selectGroup.center | |
const angle = Math.PI / 180 * this.selectGroup.rotate.angle | |
// const x = (target.x - center.x) * Math.cos(angle) - (target.y - center.y) * Math.sin(angle) + center.x | |
// const y = (target.x - center.x) * Math.sin(angle) + (target.y - center.y) * Math.cos(angle) + center.y | |
const p_data = this.calculatePointer('nw') | |
this.drawSelect(p_data) | |
// 旋转开始 | |
const event = window.event, | |
prevAngle = Math.atan2(event.pageY - center.y, event.pageX - center.x) - angle; | |
document.onmousemove = () => { | |
// 旋转 | |
const event = window.event, | |
angle = Math.atan2(event.pageY - center.y, event.pageX - center.x); | |
this.selectGroup.rotate.angle = Math.floor((angle - prevAngle) * 180 / Math.PI); | |
const transform = `rotate(${this.selectGroup.rotate.angle},${this.selectGroup.center.x},${this.selectGroup.center.y})` | |
target.rotate = this.selectGroup.rotate.angle | |
target.transform = transform | |
this.selectGroup.transform = transform | |
} | |
document.onmouseup = () => { | |
// 旋转结束 | |
document.onmousemove = null; | |
document.onmouseup = null; | |
} | |
}, | |
pointDown(e) { | |
this.clickPoint.x = e.offsetX | |
this.clickPoint.y = e.offsetY | |
document.onmousemove = (e) => { | |
this.drawLine(this.clickPoint.x, this.clickPoint.y, e.offsetX, e.offsetY, '002') | |
}; | |
document.onmouseup = () => { | |
document.onmousemove = null; | |
document.onmouseup = null; | |
}; | |
}, | |
drawLine(x1, y1, x2, y2, id) { | |
const path = d3.path() | |
path.moveTo(x1, y1) | |
path.bezierCurveTo(x2, y1, x1, y2, x2, y2); | |
// path.lineTo(x2,y2) | |
this.$set(this.listData, id, path.toString()) | |
console.log(this.listData) | |
}, | |
} | |
} | |
</script> | |
<style> | |
:not(svg), :not(foreignObject) > svg { | |
transform-origin: 0px 0px 0px; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment