本期內(nèi)容將實現(xiàn)以下操作: 整圖拖動
整圖縮放
全屏操作
橡皮筋選框
配套閱讀: github地址 性感網(wǎng)站在線模擬->點擊step8
下面放一個工作中使用的較復雜模型
十一嚣镜、 整圖拖動的實現(xiàn)
整圖拖動的實現(xiàn)
把整圖放進svg內(nèi)部的一個g元素內(nèi), 動態(tài)傳入g元素上transfrom的translate進行位置的變換,由于是組件的狀態(tài)值(state),筆者不建議放入vue-x進行管控,建議放入vue組件里的data即可, 在本項目中筆者存入了sessionStorage, 方便后面精確計算當前鼠標位置和原始比例中鼠標的所屬位置.
svgMouseDown(e) {
// svg鼠標按下觸發(fā)事件分發(fā)
this.setInitRect();
if (this.currentEvent === "sel_area") {
this.selAreaStart(e);
} else {
// 那就拖動畫布
this.currentEvent = "move_graph";
this.graphMovePre(e);
}
},
事件觸發(fā): 在svg畫布mousedown的時候進行事件分發(fā)
/**
* 畫布拖動
*/
graphMovePre(e) {
const { x, y } = e;
this.svg_trans_init = { x, y };
this.svg_trans_pre = { x: this.svg_left, y: this.svg_top };
},
graphMoveIng(e) {
const { x, y } = this.svg_trans_init;
this.svg_left = e.x - x + this.svg_trans_pre.x;
this.svg_top = e.y - y + this.svg_trans_pre.y;
sessionStorage["svg_left"] = this.svg_left;
sessionStorage["svg_top"] = this.svg_top;
},
在mousemove的過程中監(jiān)聽鼠標動態(tài)變化, 通過比較mousedown的初始位置,來更改當前畫布位置
關于坐標計算的問題放在整圖縮放里講, 回歸坐標計算需要考慮縮放倍數(shù)
十二冗澈、 整圖縮放的實現(xiàn) & 當前鼠標位置計算原始坐標
同十一, 通過svg下面g標簽的transform: scale(x), 來進行節(jié)點的整體縮放
<g :transform="` translate(${svg_left}, ${svg_top}) scale(${svgScale})`" >
在這里svgScale使用了vue-x來管控 , 是想證明, 組件的狀態(tài)管理, 沒有統(tǒng)一規(guī)范, 但是依然強烈建議state交給組件, 數(shù)據(jù)(data)交給vue-x.
↓↓
svgScale: state => state.dagStore.svgSize
這里新增一個懸浮欄組件, 方便用戶操作. 沒有用icon-font, 直接手打的字符, 后期再美化吧~~
<template>
<g>
<foreignObject width="200px" height="30px" style="position: relative">
<body xmlns="http://www.w3.org/1999/xhtml">
<div class="control_menu">
<span @click="sizeExpend">╋</span>
<span @click="sizeShrink">一</span>
<span @click="sizeInit">╬</span>
<span :class="['sel_area', 'sel_area_ing'].indexOf(currentEvent) !== -1 ? 'sel_ing' : ''" @click="sel_area($event)">口</span>
<span @click="fullScreen">{{ changeScreen }}</span>
</div>
</body>
</foreignObject>
</g>
</template>
/**
* svg畫板縮放行為
*/
sizeInit() {
this.changeSize("init"); // 回歸到默認倍數(shù)
this.svg_left = 0; // 回歸到默認位置
this.svg_top = 0;
sessionStorage['svg_left'] = 0;
sessionStorage['svg_top'] = 0;
},
sizeExpend() {
this.changeSize("expend"); // 畫板放大0.1
},
sizeShrink() {
this.changeSize("shrink"); // 畫板縮小0.1
},
由于是vue-x管控,所以在mutation里改變svgSize
CHANGE_SIZE: (state, action) => {
switch (action) {
case 'init':
state.svgSize = 1
break
case 'expend':
state.svgSize += 0.1
break
case 'shrink':
state.svgSize -= 0.1
break
default: state.svgSize = state.svgSize
}
sessionStorage['svgScale'] = state.svgSize
},
截至目前, 我們已經(jīng)完成了graph的坐標移動和縮放功能,下面有個重要的問題,就是我們在操作坐標行為的時候,拿到的只能是在組件中的坐標, 這樣會導致所有的結(jié)果都是錯位的,我們需要重新計算,拿回無縮放無位移時的真正坐標.
以節(jié)點拖動結(jié)束為例
paneDragEnd(e) {
// 節(jié)點拖動結(jié)束
this.dragFrame = { dragFrame: false, posX: 0, posY: 0 }; // 關閉模態(tài)框
const x = // x軸坐標需要減去X軸位移量, 再除以放縮比例 減去模態(tài)框?qū)挾纫话? (e.x - this.initPos.left - (sessionStorage["svg_left"] || 0)) / this.svgScale -
90;
const y = // y軸坐標需要減去y軸位移量, 再除以放縮比例 減去模態(tài)框高度一半
(e.y - this.initPos.top - (sessionStorage["svg_top"] || 0)) / this.svgScale -
15;
let params = {
model_id: sessionStorage["newGraph"],
id: this.DataAll.nodes[this.choice.index].id,
pos_x: x,
pos_y: y
};
this.moveNode(params);
},
所有用得到坐標的位置,都需要減去橫縱坐標偏移量再除以縮放的比例獲取原始比例.代碼不再贅述,可以github down完看step8的內(nèi)容.
十三湃交、全屏
目前只做了chrome瀏覽器的兼容!!!其他沒寫!!!做兼容的查對應瀏覽器API吧
fullScreen() {
if (this.changeScreen === "全") {
this.changeScreen = "關";
let root = document.getElementById("svgContent");
root.webkitRequestFullScreen();
} else {
this.changeScreen = "全";
document.webkitExitFullscreen();
}
}
document.getElementById('svgContent').webkitRequestFullScreen() 將該元素全屏
document.webkitExitFullScreen() 退出全屏.
十四、橡皮筋選框
橡皮筋選框的思路是, 拖動一個div模態(tài)框,獲取左上和右下的坐標, 改變兩坐標內(nèi)的節(jié)點的選取狀態(tài)即可.
<div :class="choice.paneNode.indexOf(item.id) !== -1 ? 'pane-node-content selected' : 'pane-node-content'">
choice: {
paneNode: [], // 選取的節(jié)點下標組
index: -1,
point: -1 // 選取的點數(shù)的下標
},
選取狀態(tài)為組件的狀態(tài),故放在組件管控,不走vuex. 框選只需要把選擇元素的id push到paneNode里即可.
selAreaStart(e) {
// 框選節(jié)點開始 在mousedown的時候調(diào)用
this.currentEvent = "sel_area_ing";
const x =
(e.x - this.initPos.left - (sessionStorage["svg_left"] || 0)) /
this.svgScale;
const y =
(e.y - this.initPos.top - (sessionStorage["svg_top"] || 0)) /
this.svgScale;
this.simulate_sel_area = {
left: x,
top: y,
width: 0,
height: 0
};
},
setSelAreaPostion(e) {
// 框選節(jié)點ing
const x =
(e.x - this.initPos.left - (sessionStorage["svg_left"] || 0)) /
this.svgScale;
const y =
(e.y - this.initPos.top - (sessionStorage["svg_top"] || 0)) /
this.svgScale;
const width = x - this.simulate_sel_area.left;
const height = y - this.simulate_sel_area.top;
this.simulate_sel_area.width = width;
this.simulate_sel_area.height = height;
},
getSelNodes(postions) {
// 選取框選的節(jié)點
const { left, top, width, height } = postions;
this.choice.paneNode.length = 0;
this.DataAll.nodes.forEach(item => {
if (
item.pos_x > left &&
item.pos_x < left + width &&
item.pos_y > top &&
item.pos_y < top + height
) {
this.choice.paneNode.push(item.id);
}
});
console.log("目前選擇的節(jié)點是", this.choice.paneNode);
},
this.simulate_sel_area 放置框選模態(tài)框的起點坐標及高寬,傳遞給組件使用即可.
十五咐汞、 事件整理
截至目前,我們項目里充斥著大量的事件,模仿js單線程,通過currentEvent來控制事件行為, 通過監(jiān)聽觸發(fā)對應事件,進行事件分發(fā).
/**
* 事件分發(fā)器
*/
dragPre(e, i, item) {
// 準備拖動節(jié)點
this.setInitRect(); // 工具類 初始化dom坐標
this.currentEvent = "dragPane"; // 修正行為
this.choice.index = i;
this.timeStamp = e.timeStamp;
this.selPaneNode(item.id);
this.setDragFramePosition(e);
e.preventDefault();
e.stopPropagation();
e.cancelBubble = true;
},
dragIng(e) {
// 事件發(fā)放器 根據(jù)currentEvent來執(zhí)行系列事件
if (
this.currentEvent === "dragPane" &&
e.timeStamp - this.timeStamp > 200 // 拖動節(jié)點延遲200毫秒響應, 來判斷點擊事件
) {
this.currentEvent = "PaneDraging"; // 確認是拖動節(jié)點
} else if (this.currentEvent === "PaneDraging") {
this.setDragFramePosition(e); // 觸發(fā)節(jié)點拖動
} else if (this.currentEvent === "dragLink") {
this.setDragLinkPostion(e); // 觸發(fā)連線拖動
} else if (this.currentEvent === "sel_area_ing") {
this.setSelAreaPostion(e); // 觸發(fā)框選
} else if (this.currentEvent === "move_graph") {
this.graphMoveIng(e);
}
},
dragEnd(e) {
// 拖動結(jié)束
if (this.currentEvent === "PaneDraging") {
this.paneDragEnd(e); // 觸發(fā)節(jié)點拖動結(jié)束
}
if (this.currentEvent === "sel_area_ing") {
this.getSelNodes(this.simulate_sel_area);
this.simulate_sel_area = {
// 觸發(fā)框選結(jié)束
left: 0,
top: 0,
width: 0,
height: 0
};
}
this.currentEvent = null;
},
回顧三期的內(nèi)容, 用了三周的時間完成模型可視化需求的抽離并更新到簡書上, 希望能給有需要的同仁以淺顯的幫助,關于本項目有什么好的想法或者建議,歡迎轉(zhuǎn)到gayhub添加微信.工作之余的時間可以交流.
謝謝每一位看到這里的同學
Thanks?(?ω?)?