项目中需要实现在主面板中,多个模块拖动布局功能,同时模块拖出主面板后变成图标展示在面板左侧,并对布局信息进行存储功能
实现思路:首先是思考拖动后相关相交模块的移动方向,这里所用模块自上而下排列,只要上边有足够的空间,则向上排列,其次左右相交的,判断拖动模块和相交模块的中心,根据中心位置,判断拖动模块是放在相交的上边还是下边;
因为频繁获取div位置信息会大大降低页面渲染性能,所以采用坐标进行div位置的判断,可以把面板放置在一个直角坐标系,把面板的宽和高分成若干等分,比如宽度10份,高度10份,这样一个模块的坐标为Grix:0,GridY:0,w:5,h:5 相当于模块在左上角宽高占面板的一半
代码实现上,封装多个拖动类,其中核心类为DragComm,拖动和缩放后引起的模块位置移动均在这里处理,setShadowPos为计算主方法,shadowPos为当前dom的跟随dom,用于预设应该放置的位置,当移动后,根据位移设置shadow的坐标,因为移动产生的像素会频繁触发计算影响性能,所以通过判断当坐标有变动时,才触发setShadowPos计算,主要逻辑是先计算当前操作对象最小放置高度,即根据当前对象上方有交叉的元素获取最小GridY;然后根据移动方向,如果是向下移动,则说明是想要上下交换位置,获取到所有重叠元素后判断元素中心,GridY小于操作元素中心的即为需要向上移动的元素,使用getTopHeight计算出每个的最小GridY后,当前移动元素的最小GridY为向上元素中GridY + h 的最大值;接着判断对其下方元素造成的影响...
dragComm:设置x、y轴分割份数,一个模块的最小尺寸(最小占据份数)GridPos全局位置对象等
dragArea:
mouseDown:清理定时器(App.mouseUpTimer,拖动模块的保存机制为鼠标up后的5s后调用接口保存,5s内再发起的拖动属于一次拖动)获取父元素及可移动范围元素的尺寸,计算出每份的尺寸prx,pry;设置shadowPos阴影位置信息(dom的跟随阴影dom,用于预设应该放置的位置),创建阴影元素shadowEle,位置信息和阴影dom位置与当前操作元素保持一致
mouseMove:操作元素跟随鼠标移动,通过鼠标移动位置计算坐标位置(Math.round(x / this.prx);Math.round(y / this.pry)),调用setShadowPos计算阴影的真实位置(确保各模块不相交);判断鼠标是否移出移动范围,是的话说明是要放在外部展示,调用mouseUp结束当前class,父元素中删除该dom对象,GridPos中删除该模块位置信息,展示该模块对应的图标,触发图标mouseDown事件
mouseup:删除shadowEle,设置操作元素位置为阴影dom位置,设置定时器App.mouseUpTimer(5s后接口保存)
dragMenu:
mouseDown:计算prx,pry,获取菜单icon列表下右侧展示的icon节点(childrenList)及icon位置高度列表(childrenPosition),创建seat节点,移动排序时用来显示插入位置;设置shadowPos阴影坐标,GridPos中添加该icon对应的模块坐标(mouseUp时若是移入,直接更新坐标,若是排序需要删除该坐标)
mouseMove:icon拖动按钮只能在模块放置区域和右侧列表内拖动,当判断是在右侧列表内移动时,通过childrenPosition判断插入位置,插入seat,并记录seatIndex;若是在模块区域移动,则移除seat,调用showPlace计算模块要放置的位置信息,然后调用setShadowPos计算出真实要放置的位置
mouseUp:依然分两种情况,(1)排序,即移出,先从dom中删除该icon,然后插入到seatIndex对应位置,执行putOut从模块区域删除icon对应模块(drag移出触发dragMenu时,模块是在模块区域的,需要删除),GridPos中删除该模块坐标,删除阴影dom;(2)移入,隐藏icon图标,执行putIn将icon对应模块放入模块区域,initPutInEle设置模块放置位置样式,删除阴影dom
zoomArea:
模块区域内的模块只能使用左下角进行缩放,除了位置会变化,模块宽高也会变化,通过prx、pry计算出坐标信息,依然使用setShadowPos计算真实位置信息
dragComm:
是上边三个类的父类,setShadowPos为计算主方法,传入位置坐标,判断坐标是否有变动,有变动的话进入主计算逻辑(通过转换成坐标,坐标变动触发避免通过像素变更频繁触发影响性能);
getTopheight计算当前操作对象最小放置高度,即根据当前对象上方有交叉的元素获取最小GridY;getCrossList方法获取相交元素列表crossList,crossList为空表示无相交,则直接设置阴影位置坐标,若crossList不为空,则表示有相交,然后根据移动方向directionY,如果是向下移动,则说明是想要向下交换位置,在crossList中找出gridY<= y + h/2(交叉元素中gridY在当前操作元素的中心上方的元素)的元素upList,遍历upList重新计算gridY(忽略处在上方的当前操作元素上移),重新设置传入的y值为移动后的元素中下边距最靠下的位置(y = Math.max(ty + e.h, sy))
根据变更后的y再次更新相交列表crossList,获取需要向下移动的列表downList,当downList.length > 1时,当相交的元素中有上下挨着的,需要删除上下挨着的元素中位于下方的元素(位置修改时,会递归修改下方的元素,避免修改重复和进行不必要的递归);遍历downList,使用changeRelative递归修改紧邻下方的元素位置(downList中分两种情况,一种gridY在同一水平线,一种的是从中空下移,都通过sy + h - d.gridY计算出下移量,中空下移只会在当前层出现),
最后设置shadowEle显示位置,sortElePos方法重新修正各模块显示位置
import App from "@/application/app";
import { LayoutPos, ElePos } from "@/application/app.type";
import { filter } from "lodash";
import { DomUtil } from "./domUtil";
const gap = 8;
const body = document.body || document.documentElement;
export class DragComm extends DomUtil {
public static GridPos: ElePos[] = [];
// public static mouseUpTimer: NodeJS.Timeout;
tx = 12; // x轴分的份数
ty = 10; // y轴分的份数
mw = 2; // 最小宽度份数
mh = 3; // 最小高度份数
initw = 4; // 初始块的宽度份数
inith = 5; // 初始块的高度份数
ppx: number; // x轴每份占的百分比
ppy: number; // y轴每份占的百分比
prx: number; // x轴每份的长度
pry: number; // y轴每份的长度
source: HTMLElement; // 源容器
parent: HTMLElement; //父容器
px = 0;
py = 0;
pw = 0;
ph = 0;
x = 0;
y = 0;
w = 0;
h = 0;
directionX = 0;
directionY = 0;
shadowEle: HTMLElement; // 影子
dataSet: string;
originPos: ElePos; // 初始坐标
shadowPos: ElePos; // 阴影坐标
marker: HTMLElement;
time: number = null;
dragType: string;
elObj: {
[key: string]: HTMLElement;
};
constructor(source: HTMLElement, parent: HTMLElement, public app: App) {
super();
this.source = source;
this.parent = parent;
DragComm.GridPos = App.layoutPos.elePos;
this.initData();
}
initData() {
// 为空时设置初始化坐标
if (DragComm.GridPos.length === 0) {
DragComm.GridPos.push({
gridX: 0,
gridY: 0,
w: this.tx,
h: this.ty,
key: "0",
});
}
// 计算x、y每块的大小
this.ppx = 100 / this.tx;
this.ppy = 100 / this.ty;
this.dataSet = this.source.dataset.i || "";
}
// 根据坐标初始化位置
initSize() {
const gridPos = DragComm.GridPos;
const children = this.getChildren(this.parent);
children.forEach(c => {
const key = c.dataset.i;
const pos = gridPos.filter(p => p.key === key)[0];
this.setPosition(c, pos);
});
}
// 移入后初始化位置
initPutInEle(key: string) {
const children = this.getChildren(this.parent);
const c = children.filter(c => c.dataset.i === key)[0];
this.setPosition(c, this.shadowPos);
// 设置动画
this.cardShow(c);
}
initMouseDown() {
this.clearUpTime();
this.parent.style.userSelect = "none";
this.source.style.userSelect = "none";
body.style.userSelect = "none";
this.source.style.transition = "none";
this.source.style.zIndex = "10";
const { x, y, w, h } = this.geOffset(this.source);
this.x = x;
this.y = y;
this.w = w;
this.h = h;
const { x: px, y: py, w: pw, h: ph } = this.getScale(this.parent);
[this.px, this.py, this.pw, this.ph] = [px, py, pw, ph];
// 分割的每小块的尺寸
this.prx = pw / this.tx;
this.pry = ph / this.ty;
this.originPos = DragComm.GridPos.filter(d => d.key === this.dataSet)[0];
this.shadowPos = { ...this.originPos };
this.shadowEle = this.parent.querySelector(".vq-shadow-follow");
if (!this.shadowEle) {
this.shadowEle = this.create("div", "vq-shadow-follow", this.parent);
this.shadowEle.style.display = "none";
this.shadowEle.style.left = x + "px";
this.shadowEle.style.top = y + "px";
this.shadowEle.style.width = w + "px";
this.shadowEle.style.height = h + "px";
this.shadowEle.innerHTML = "<span>松手即可置入面板</span>";
}
this.setEleList();
this.time = new Date().getTime();
this.marker = document.createElement("div");
this.marker.style.position = "fixed";
this.marker.style.top = "0";
this.marker.style.left = "0";
this.marker.style.bottom = "0";
this.marker.style.width = "100%";
this.marker.style.height = "100%";
this.marker.style.right = "0";
this.marker.style.zIndex = "1000";
body.appendChild(this.marker);
}
initMouseUp() {
this.parent.style.userSelect = "";
this.source.style.userSelect = "";
this.source.style.transition = "";
this.source.style.zIndex = "";
body.style.userSelect = "";
if (this.marker) {
this.remove(this.marker);
}
if (this.shadowEle) {
this.remove(this.shadowEle);
}
this.setPosition(this.source, this.shadowPos);
this.saveLayout();
// for (let i = 0; i < DragComm.GridPos.length; i++) {
// if (DragComm.GridPos[i].key === this.dataSet) {
// DragComm.GridPos[i] = { ...this.shadowPos };
// return;
// }
// }
}
clearUpTime() {
clearTimeout(App.mouseUpTimer);
}
saveLayout() {
App.layoutPos.elePos = DragComm.GridPos;
App.setMouseUpTimer();
}
// 获取dom节点并保存,方便修改dom样式
setEleList() {
this.elObj = {};
const children = this.getChildren(this.parent);
children.forEach(c => {
const key = c.dataset.i;
if (key) {
this.elObj[key] = c;
}
});
}
// 坐标转换位置
setPosition(ele: HTMLElement, pos?: ElePos) {
if (!pos) {
const key = ele.dataset.i;
pos = DragComm.GridPos.filter(p => p.key === key)[0];
}
ele.style.left = pos.gridX * this.ppx + "%";
ele.style.top = pos.gridY * this.ppy + "%";
ele.style.width = `calc(${pos.w * this.ppx + "%"} - ${gap}px)`;
ele.style.height = `calc(${pos.h * this.ppy + "%"} - ${gap}px)`;
}
setPositionByWH(ele: HTMLElement, w: number, h: number) {
ele.style.width = `calc(${w * this.ppx + "%"} - ${gap}px)`;
ele.style.height = `calc(${h * this.ppy + "%"} - ${gap}px)`;
}
setPositionByXY(ele: HTMLElement, x: number, y: number) {
ele.style.left = x * this.ppx + "%";
ele.style.top = y * this.ppy + "%";
}
setPositionByY(ele: HTMLElement, y: number) {
ele.style.top = y * this.ppy + "%";
}
// 根据菜单图标获取要放置的位置
showPlace(x: number, y: number) {
this.shadowEle.style.display = "";
const left = x - this.px - 30;
const top = y - this.py - 30;
let sx = Math.round(left / this.prx);
let sy = Math.round(top / this.pry);
sx = sx < 0 ? 0 : sx;
sx = sx > this.tx - this.initw ? this.tx - this.initw : sx;
sy = sy < 0 ? 0 : sy;
this.setShadowPos(sx, sy);
}
removeShadowEle() {
this.remove(this.shadowEle);
}
getCrossList(x: number, y: number, w: number, h: number, ignor: string) {
const crossList: ElePos[] = [];
DragComm.GridPos.forEach(d => {
if (d.key !== ignor) {
const lx = x - (d.gridX + d.w);
const rx = d.gridX - (x + w);
const ty = y - (d.gridY + d.h);
const by = d.gridY - (y + h);
if (!(lx >= 0 || rx >= 0 || ty >= 0 || by >= 0)) {
// 相交
crossList.push(d);
}
}
});
return crossList;
}
setShadowPos(x: number, y: number, w?: number, h?: number) {
w = w ? w : this.shadowPos.w;
h = h ? h : this.shadowPos.h;
console.log("----pos---");
console.log(y);
if (x !== this.shadowPos.gridX || y !== this.shadowPos.gridY || w !== this.shadowPos.w || h !== this.shadowPos.h) {
console.log("进入判断");
this.shadowPos.gridX = x;
this.shadowPos.gridY = y;
this.shadowPos.w = w;
this.shadowPos.h = h;
let sy = (this.shadowPos.gridY = this.getTopheight(this.shadowPos));
let crossList = this.getCrossList(x, y, w, h, this.dataSet);
// 没有相交
if (crossList.length === 0) {
// console.log("无相交");
if (this.dragType === "zoom") {
// 改变了w, h, x
this.setPosition(this.shadowEle, this.shadowPos);
} else {
this.setPositionByXY(this.shadowEle, this.shadowPos.gridX, this.shadowPos.gridY);
}
} else {
console.log("相交");
if (this.directionY > 0) {
console.log("向下");
// 需要向上移动的元素(找出y 小于当前元素y+h/2的列表)
const upList = crossList.filter(d => d.gridY <= y + h / 2);
console.log("ul---", upList);
if (upList.length > 0) {
upList.forEach(e => {
const ty = this.getTopheight(e);
if (ty !== e.gridY) {
this.setPositionByY(this.elObj[e.key], ty);
e.gridY = ty;
}
y = Math.max(ty + e.h, sy);
});
this.shadowPos.gridY = y;
sy = y;
}
}
// 需要向下移动的元素(找出y大于等于当前元素的列表,视为需要向下移动的元素)
crossList = this.getCrossList(x, sy, w, h, this.dataSet);
const downList = crossList.filter(d => d.gridY >= sy);
console.log("dl---", downList);
// 判断向下移动的元素是否已经是上下级关联关系,上下挨着的话移除下边的,用上边的级联修改
if (downList.length > 1) {
// 从小到大排序
downList.sort((a, b) => {
return a.gridY - b.gridY;
});
for (let i = downList.length - 1; i > 0; i--) {
const eb = downList[i];
for (let k = i - 1; k >= 0; k--) {
const et = downList[k];
if (eb.gridY === et.gridY + et.h && (eb.gridX === et.gridX || eb.gridX + eb.w === et.gridX + et.w)) {
downList.pop();
break;
}
}
}
}
// const maxY = y;
if (downList.length > 0) {
// 从半空或者从头挤下去要移动的相对高度不同
downList.forEach(d => {
const dh = sy + h - d.gridY;
this.changeRelative(d, dh, true);
d.gridY += dh;
this.setPositionByY(this.elObj[d.key], d.gridY);
});
}
if (this.dragType === "zoom") {
// this.shadowPos.gridX = x;
// this.shadowPos.w = w;
// this.shadowPos.h = h;
this.setPosition(this.shadowEle, this.shadowPos);
} else {
// this.shadowPos.gridX = x;
// this.shadowPos.gridY = y;
this.setPositionByXY(this.shadowEle, this.shadowPos.gridX, this.shadowPos.gridY);
}
}
// 依次赋值,相当于改变了DragComm里当前元素,方便计算浮动
this.originPos.gridX = this.shadowPos.gridX;
this.originPos.gridY = this.shadowPos.gridY;
this.originPos.w = this.shadowPos.w;
this.originPos.h = this.shadowPos.h;
this.sortElePos(true);
}
}
sortElePos(all?: boolean) {
console.log("sort-pos");
console.log(DragComm.GridPos);
DragComm.GridPos.sort((a, b) => {
return a.gridY - b.gridY;
});
DragComm.GridPos.forEach(d => {
const h = this.getTopheight(d, all);
// 从菜单新拉过来还没放的时候elObj对象中还没有该元素
if (h !== d.gridY && this.elObj[d.key]) {
this.setPositionByY(this.elObj[d.key], h);
d.gridY = h;
}
});
}
/**
*
* @param e
* @param dh
* @param calcHalfCover 判断计算中空元素挤下去重叠的情况,只考虑第一层
*/
changeRelative(e: ElePos, dh: number, calcHalfCover: boolean) {
DragComm.GridPos.forEach(d => {
const rl = d.gridX + d.w;
const erl = e.gridX + e.w;
if (d.key !== this.dataSet && d.gridY === e.gridY + e.h && ((rl > e.gridX && rl <= erl) || (d.gridX >= e.gridX && d.gridX < erl) || (d.gridX <= e.gridX && rl >= erl))) {
this.changeRelative(d, dh, false);
d.gridY += dh;
this.setPositionByY(this.elObj[d.key], d.gridY);
} else if (
calcHalfCover &&
d.key !== this.dataSet &&
d.gridY < e.gridY + e.h + dh &&
d.gridY > e.gridY + e.h &&
((rl > e.gridX && rl <= erl) || (d.gridX >= e.gridX && d.gridX < erl) || (d.gridX <= e.gridX && rl >= erl))
) {
const dmh = e.gridY + e.h + dh - d.gridY;
this.changeRelative(d, dmh, false);
d.gridY += dmh;
this.setPositionByY(this.elObj[d.key], d.gridY);
}
});
}
// isTopEmpty(pos: ElePos) {
// if (pos.gridY === 0) {
// return true;
// }
// const minW = pos.gridX;
// const maxW = pos.gridX + pos.w;
// const isTopUse = DragComm.GridPos.some(g => {
// return g.gridY + g.h <= pos.gridY && !(g.gridX >= maxW || g.gridX + g.w <= minW);
// });
// return !isTopUse;
// }
getTopheight(pos: ElePos, all?: boolean) {
if (pos.gridY === 0) {
return 0;
}
const minW = pos.gridX;
const maxW = pos.gridX + pos.w;
// 获取当前操作元素的上方元素(y小于当前元素),计算应放置的最大y值
const topList = DragComm.GridPos.filter(g => {
return g.key !== pos.key && (all || g.key !== this.dataSet) && g.gridY < pos.gridY && !(g.gridX >= maxW || g.gridX + g.w <= minW);
});
if (topList.length === 0) {
return 0;
} else {
let maxH = 0;
topList.forEach(t => {
maxH = Math.max(t.gridY + t.h, maxH);
});
return maxH;
}
}
}
dragArea类为工作台中各模块的移动类,
import App from "@/application/app";
import { DragComm } from "./dragComm";
const boundary = 20;
const gap = 8;
export class DragArea extends DragComm {
moveElement: HTMLElement;
isReverse: boolean;
permitDrag: boolean; //是否允许移动标识
iconDom: HTMLElement;
ssx = 0;
ssy = 0;
left = 0;
top = 0;
currentX = 0;
currentY = 0;
latelyClientX = 0;
latelyClientY = 0;
isMove = false;
moveOrigin = 0;
/**
* @param source 要移动的dom
* @param target 可以移动的范围
* @param parent 移动元素的父元素
*/
constructor(source: HTMLElement, parent: HTMLElement, moveElement: HTMLElement, app: App) {
super(source, parent, app);
this.init(moveElement);
}
init(moveElement: HTMLElement) {
this.moveElement = moveElement;
if (this.dataSet) {
this.iconDom = this.getListIcon(this.dataSet);
}
this.moveElement.addEventListener("mousedown", this.mousedown);
}
mousedown = (e: MouseEvent) => {
console.log("--area--down");
if (this.source.parentNode !== this.parent) {
return;
}
e.stopPropagation();
this.permitDrag = true;
this.dragType = "drag";
this.isReverse = this.app.position === "left";
document.addEventListener("mousemove", this.mousemove);
document.addEventListener("mouseup", this.mouseup);
this.initMouseDown();
console.log(DragComm.GridPos);
this.ssx = this.x;
this.ssy = this.y;
this.left = e.clientX - this.x;
this.top = e.clientY - this.y;
this.latelyClientX = e.clientX;
this.latelyClientY = e.clientY;
};
mousemove = (e: MouseEvent) => {
if (!this.permitDrag) return false;
const now = new Date().getTime();
// if (now - this.time < 50) {
// return false;
// }
this.time = now;
this.isMove = true;
// console.log("--area--move");
this.shadowEle.style.display = "";
this.directionX = e.clientX - this.latelyClientX;
this.directionY = e.clientY - this.latelyClientY;
this.latelyClientX = e.clientX;
this.latelyClientY = e.clientY;
let x = e.clientX - this.left;
let y = e.clientY - this.top;
const maxL = this.pw - this.w;
// const maxT = this.ph - this.h;
x = x > maxL ? maxL : x;
x = x < 0 ? 0 : x;
// y = y > maxT ? maxT : y;
y = y < 0 ? 0 : y;
this.source.style.left = x + "px";
this.source.style.top = y + "px";
const gx = Math.round(x / this.prx);
const gy = Math.round(y / this.pry);
this.setShadowPos(gx, gy, this.originPos.w, this.originPos.h);
if (this.dataSet != "0") {
// 移出
if ((e.clientX < this.px && !this.isReverse) || (e.clientX > this.px + this.pw && this.isReverse)) {
//
this.mouseup(e);
this.remove(this.source);
DragComm.GridPos = DragComm.GridPos.filter(d => d.key != this.dataSet);
this.sortElePos();
this.iconDom.style.left = e.clientX - 20 + "px";
this.iconDom.style.top = e.clientY - 20 + "px";
this.iconDom.style.pointerEvents = "none";
this.iconDom.style.display = "";
this.iconDom.dispatchEvent(new MouseEvent("mousedown", { clientX: e.clientX, clientY: e.clientY }));
}
}
};
getListIcon(key: string) {
const iconDomLi = [...document.querySelectorAll(".vq-wrapper-content-left-list-li")] as HTMLElement[];
const list = iconDomLi.filter(li => {
return li.dataset.i === key;
});
if (list.length > 0) {
return list[0];
}
}
mouseup = (e: MouseEvent) => {
console.log("--area--up");
e.stopPropagation();
this.permitDrag = false;
this.isMove = false;
this.initMouseUp();
document.removeEventListener("mousemove", this.mousemove);
document.removeEventListener("mouseup", this.mouseup);
};
}
DragMenu为菜单拖动类,该类是移动面板左侧的菜单icon进行菜单icon排序,还有就是拖动进入模块区域后生成模块shadow对模块进行展开
import App from "@/application/app";
import { DragArea } from "./drag.area";
import { DragComm } from "./dragComm";
export class DragMenu extends DragComm {
// source: HTMLElement; // 源容器
target: HTMLElement; //允许放置的容器
isReverse: boolean;
moveElement: HTMLElement; //允许拖拽的位置
permitDrag: boolean; //是否允许移动标识
childrenList: HTMLElement[];
childrenPosition: number[];
x = 0;
y = 0;
left = 0;
top = 0;
currentX = 0;
currentY = 0;
isMove = false;
time: number = null;
seat: HTMLElement;
seatIndex = -1;
/**
* @param source 要移动的dom
* @param target 可以移动的范围
* @param menuListEle 移动元素的父元素
* @param boundaryEle 移动元素的目的地
* @param putIn 卡片移入右侧主区域
*/
timeOut: NodeJS.Timeout = null;
constructor(source: HTMLElement, target: string | HTMLElement, public menuListEle: HTMLElement, public boundaryEle: HTMLElement, public putIn: () => any, public putOut: () => any, app: App) {
super(source, boundaryEle, app);
this.init(source, target);
}
init(source: HTMLElement, target: string | HTMLElement, moveElement?: HTMLElement | string) {
this.source = typeof source === "string" ? document.querySelector(source) : source;
this.target = typeof target === "string" ? document.querySelector(target) : target;
if (moveElement) {
this.moveElement = typeof moveElement === "string" ? document.querySelector(moveElement) : moveElement;
this.moveElement.addEventListener("mousedown", this.mousedown);
} else {
this.source.addEventListener("mousedown", this.mousedown);
}
}
mousedown = (e: MouseEvent) => {
e.stopPropagation();
document.addEventListener("mousemove", this.mousemove);
document.addEventListener("mouseup", this.mouseup);
this.isReverse = this.app.position === "left";
this.seatIndex = -1;
if (e.isTrusted) {
this.timeOut = setTimeout(() => {
this.setMouseDown(e);
}, 200);
} else {
this.setMouseDown(e);
}
};
setMouseDown(e: MouseEvent) {
console.log("--menu--down");
this.clearUpTime();
this.permitDrag = true;
// 分割的每小块的尺寸 计算放置位置用
const { x: px, y: py, w: pw, h: ph } = this.getScale(this.boundaryEle);
this.px = px;
this.py = py;
this.prx = pw / this.tx;
this.pry = ph / this.ty;
const { x, y } = this.getScale(this.source);
this.x = x;
this.y = y;
this.currentX = x;
this.currentY = y;
this.left = e.clientX - this.x;
this.top = e.clientY - this.y;
this.source.style.position = "fixed";
this.source.style.left = this.x + "px";
this.source.style.top = this.y + "px";
this.source.style.padding = "10px";
this.source.style.borderRadius = "50%";
this.source.style.background = "#fff";
this.source.style.boxShadow = "0 0 5px rgba(0, 0, 0, 0.12)";
this.source.style.transform = "scale(1.3)";
this.source.style.transition = "none";
this.source.style.pointerEvents = "none";
this.source.style.zIndex = "100";
document.body.style.userSelect = "none";
this.time = new Date().getTime();
this.marker = document.createElement("div");
this.marker.classList.add("vq-drag-maker");
this.marker.style.position = "fixed";
this.marker.style.top = "0";
this.marker.style.left = "0";
this.marker.style.bottom = "0";
this.marker.style.width = "100%";
this.marker.style.height = "100%";
this.marker.style.right = "0";
this.marker.style.zIndex = "10000000";
const list = [...this.menuListEle.children] as HTMLElement[];
this.childrenList = list.filter(li => {
return li.style.position != "fixed";
});
this.childrenPosition = [];
this.childrenList.forEach(li => {
const { y, h } = this.getScale(li);
this.childrenPosition.push(y + h);
});
this.seat = document.createElement("div");
this.seat.id = "vq-seat";
this.seat.style.height = "2px";
this.seat.style.margin = "0 8px";
this.seat.style.background = "#000";
this.shadowPos = {
key: this.dataSet,
w: this.initw,
h: this.inith,
gridX: -1, // 防止初始值不变不能触发setShadowPos计算
gridY: 0,
};
// mousedown时先建立坐标
this.originPos = { ...this.shadowPos };
DragComm.GridPos.push(this.originPos);
this.shadowEle = this.boundaryEle.querySelector(".vq-shadow-follow");
if (!this.shadowEle) {
this.shadowEle = this.create("div", "vq-shadow-follow", this.boundaryEle);
this.shadowEle.innerHTML = "<span>松手即可置入面板</span>";
this.create("div", "vq-put-in", this.shadowEle);
this.shadowEle.style.display = "none";
this.setPositionByWH(this.shadowEle, this.initw, this.inith);
}
this.setEleList();
document.body.appendChild(this.marker);
}
mousemove = (e: MouseEvent) => {
if (!this.permitDrag) return false;
this.isMove = true;
console.log("--menu--move");
let x = e.clientX - this.left;
let y = e.clientY - this.top;
const { w, h } = this.getScale(this.source);
const { x: tx, y: ty, w: tw, h: th } = this.getScale(this.target);
const { x: px, y: py, w: pw, h: ph } = this.getScale(this.menuListEle);
//边界判断
x = x < tx ? tx : x;
x = x > tx + tw - w ? tx + tw - w : x;
y = y < ty ? ty : y;
y = y > ty + th - h ? ty + th - h : y;
this.currentX = x;
this.currentY = y;
this.source.style.left = x + "px";
this.source.style.top = y + "px";
// 排序
if ((!this.isReverse && x < px + pw) || (this.isReverse && x + w > px)) {
// 鼠标移出时,计算位置时忽略当前待移入dom
this.sortElePos(false);
this.shadowEle.style.display = "none";
for (let i = 0; i < this.childrenPosition.length; i++) {
if (y + h / 2 < this.childrenPosition[i] || i === this.childrenPosition.length - 1) {
this.remove(this.seat);
this.insert(this.menuListEle, this.seat, this.childrenList[i]);
this.seatIndex = i;
return;
}
}
} else {
this.shadowEle.style.display = "";
// move移出时gridX变成0,再移入时到相同位置时坐标不变不触发setShadowPos计算
this.shadowPos.gridX = -1;
this.remove(this.seat);
this.showPlace(e.clientX, e.clientY);
}
};
mouseup = (e: MouseEvent) => {
console.log("--menu--up");
e.stopPropagation();
document.removeEventListener("mousemove", this.mousemove);
document.removeEventListener("mouseup", this.mouseup);
if (!this.permitDrag) {
clearTimeout(this.timeOut);
} else {
const { x: px, y: py, w: pw, h: ph } = this.getScale(this.menuListEle);
const { w } = this.getScale(this.source);
if ((!this.isReverse && this.currentX < px + pw) || (this.isReverse && this.currentX + w > px)) {
// 移出
this.source.style = null;
if (this.isMove && this.seatIndex >= 0) {
this.remove(this.source);
this.insert(this.menuListEle, this.source, this.childrenList[this.seatIndex]);
this.putOut();
}
// 移出的话删除坐标
DragComm.GridPos = DragComm.GridPos.filter(d => d.key !== this.dataSet);
if (this.shadowEle) {
this.remove(this.shadowEle);
}
} else {
// 移入的话,坐标不做处理,mousedown已经添加坐标
const { x: sx, y: sy, w: sw, h: sh } = this.getScale(this.shadowEle);
this.source.style.boxShadow = "";
this.source.style.transition = "left .1s ease, top .1s ease, opacity .5s linear";
this.source.style.left = sx + sw / 2 - 20 + "px";
this.source.style.top = sy + sh / 2 - 20 + "px";
const toCenter = () => {
// this.source.style.transition = "opacity .5s linear";
this.source.removeEventListener("transitionend", toCenter);
this.source.style.opacity = "0";
// 图标消失渐变
const callback = () => {
console.log("渐变");
this.source.style.opacity = "1";
this.source.style.display = "none";
this.source.removeEventListener("transitionend", callback);
};
this.source.addEventListener("transitionend", callback);
console.log(DragComm.GridPos);
if (this.shadowEle) {
// const { x, sx, y: sy, w: sw, h: sh } = this.geOffset(this.shadowEle);
// 设置圆覆盖动画
const ppw = Math.ceil(Math.pow(Math.pow(sw / 2, 2) + Math.pow(sh / 2, 2), 0.5) * 2);
const pt = this.shadowEle.querySelector(".vq-put-in");
pt.style.transition = "all .2s linear";
pt.style.top = `calc(50% - ${ppw / 2}px)`;
pt.style.left = `calc(50% - ${ppw / 2}px)`;
pt.style.width = ppw + "px";
pt.style.height = ppw + "px";
const ptEnd = () => {
console.log("----shadd---trs--end");
this.putIn();
this.initPutInEle(this.dataSet);
this.remove(this.shadowEle);
pt.removeEventListener("transitionend", ptEnd);
};
pt.addEventListener("transitionend", ptEnd);
} else {
this.putIn();
this.initPutInEle(this.dataSet);
this.remove(this.shadowEle);
}
};
this.source.addEventListener("transitionend", toCenter);
}
if (this.marker) {
document.body.removeChild(this.marker);
this.marker = null;
}
const makerLi = [...document.querySelectorAll(".vq-drag-maker")] as HTMLElement[];
makerLi.forEach(m => {
this.remove(m);
});
if (this.seat) {
this.remove(this.seat);
}
// if (this.shadowEle) {
// this.remove(this.shadowEle);
// }
this.permitDrag = false;
this.isMove = false;
document.body.style.userSelect = "";
this.saveLayout();
}
};
saveLayout() {
const list = this.getChildren(this.menuListEle);
App.layoutPos.lisPos = [];
list.forEach(d => {
const key = d.dataset.i;
App.layoutPos.lisPos.push(key);
});
App.layoutPos.elePos = DragComm.GridPos;
App.setMouseUpTimer();
}
}
zoomArea:通过模块左下角调整模块的宽高,当宽高变化时同样需要进行setShadowPos判断
import App from "@/application/app";
import { LayoutPos } from "@/application/app.type";
import { DragArea } from "./drag.area";
import { DragComm } from "./dragComm";
const boundary = 30;
const gap = 15;
const body = document.body || document.documentElement;
export class ZoomArea extends DragComm {
moveElement: HTMLElement;
isReverse: boolean;
permitDrag: boolean; //是否允许移动标识
ssw = 0;
ssh = 0;
left = 0;
top = 0;
br: HTMLElement;
bf: HTMLElement;
type: string = null;
constructor(source: HTMLElement, parent: HTMLElement, moveElement: HTMLElement, app: App) {
super(source, parent, app);
this.init(moveElement, app);
}
init(moveElement: HTMLElement, app: App) {
this.moveElement = moveElement;
this.clearDom();
new DragArea(this.source, this.parent, this.moveElement, app);
// const br = (this.br = this.create("i", "vq-zoom vq-zoom-br", c));
const bf = (this.bf = this.create("i", "vq-zoom vq-zoom-bf", this.source));
// br.addEventListener("mousedown", (e: MouseEvent) => {
// this.type = "br";
// this.mousedown(e);
// });
bf.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "bf";
this.mousedown(e);
});
}
mousedown = (e: MouseEvent) => {
console.log("--zoomarea--down--");
if (this.source.parentNode !== this.parent) {
return;
}
e.stopPropagation();
this.permitDrag = true;
this.dragType = "zoom";
this.isReverse = this.app.position === "left";
document.addEventListener("mousemove", this.mousemove);
document.addEventListener("mouseup", this.mouseup);
this.initMouseDown();
this.left = e.clientX;
this.top = e.clientY;
};
mousemove = (e: MouseEvent) => {
console.log("--zoomarea--move--");
if (!this.permitDrag) return false;
// const now = new Date().getTime();
// if (now - this.time < 100) {
// return false;
// }
// this.time = now;
this.shadowEle.style.display = "";
const mouseMoveX = e.clientX;
const mouseMoveY = e.clientY;
let w = 0;
let h = 0;
if (this.type === "bf") {
w = this.w + this.left - mouseMoveX;
w = w < 0 ? 0 : w;
h = this.h - (this.top - mouseMoveY);
h = h < 0 ? 0 : h;
let sw = Math.round((w + gap) / this.prx);
let sh = Math.round((h + gap) / this.pry);
sw = sw < this.mw ? this.mw : sw;
sw = sw > this.originPos.gridX + this.originPos.w ? this.originPos.gridX + this.originPos.w : sw;
sh = sh < this.mh ? this.mh : sh;
const sx = this.shadowPos.gridX + this.shadowPos.w - sw;
const sy = this.shadowPos.gridY;
this.setShadowPos(sx, sy, sw, sh);
this.source.style.width = w + "px";
this.source.style.height = h + "px";
this.source.style.left = this.x + this.w - w + "px";
}
};
mouseup = (e: MouseEvent) => {
console.log("--zoomarea--up--");
this.permitDrag = false;
this.initMouseUp();
document.removeEventListener("mousemove", this.mousemove);
document.removeEventListener("mouseup", this.mouseup);
};
clearDom() {
const s = [...this.source.children];
s.forEach(z => {
if (z.classList.contains("vq-zoom")) {
this.remove(z);
}
});
}
}
domUtil:dom操作类
import * as Util from "./util";
export class DomUtil {
checkDomLoad(sel: string, callback: () => any) {
let timer: NodeJS.Timeout = null;
function check() {
const dom = document.querySelector(sel);
if (dom) {
// 执行dom加载完成后的操作,例如echart的初始化操作
// 清除定时器
callback();
if (!timer) {
clearTimeout(timer);
}
} else {
// 自我调用
timer = setTimeout(check, 0);
}
}
check();
}
// 获取dom
get(selector: string): HTMLElement {
return document.querySelector(selector);
}
createElement(tagName: string, className: string, html = ""): HTMLElement {
const el = document.createElement(tagName);
el.className = className || "";
el.innerHTML = html;
return el;
}
nextTick(fn: () => any) {
Promise.resolve().then(() => {
fn();
});
}
/**
*
* @param tagName
* @param className 创建dom
* @param container
*/
create(tagName: string, className: string, container?: HTMLElement): HTMLElement {
const el = document.createElement(tagName);
el.className = className || "";
if (container) {
container.appendChild(el);
}
return el;
}
createSvg(src: string, className: string, container?: HTMLElement): HTMLEmbedElement {
// HTMLOrSVGElement;
const el: HTMLEmbedElement = document.createElement("embed");
el.className = className || "";
el.src = src;
if (container) {
container.appendChild(el);
}
return el;
}
/**
*
* @param {*} el 删除元素
*/
remove(el: HTMLElement | Node) {
if (!el) return;
const parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
}
/**
*
* @param {*} el 清空子元素
*/
empty(el: HTMLElement) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
/**
*
* @param {*} el 变成最后一个儿子
*/
toFront(el: HTMLElement) {
const parent = el.parentNode;
if (parent && parent.lastChild !== el) {
parent.appendChild(el);
}
}
// 变成第一个儿子
toBack(el: HTMLElement) {
const parent = el.parentNode;
if (parent && parent.firstChild !== el) {
parent.insertBefore(el, parent.firstChild);
}
}
slideIn(el: HTMLElement, isReverse: boolean) {
const className1 = isReverse ? "vq-slideIn-reverse" : "vq-slideIn";
const className2 = isReverse ? "vq-slideOut-reverse" : "vq-slideOut";
this.addClass(el, className1);
this.removeClass(el, className2);
el.style.display = "";
}
slideOut(el: HTMLElement, isReverse: boolean) {
const className1 = isReverse ? "vq-slideIn-reverse" : "vq-slideIn";
const className2 = isReverse ? "vq-slideOut-reverse" : "vq-slideOut";
if (el.style.display === "none") {
return;
}
this.removeClass(el, className1);
this.addClass(el, className2);
const remove = () => {
el.style.display = "none";
el.removeEventListener("webkitAnimationEnd", remove);
el.removeEventListener("animationend", remove);
el.removeEventListener("oAnimationEnd", remove);
this.removeClass(el, className2);
};
el.addEventListener("webkitAnimationEnd", remove);
el.addEventListener("animationend", remove);
el.addEventListener("oAnimationEnd", remove);
// Promise.resolve().then(() => { });
}
show(el: HTMLElement, className1 = "vq-fadeIn", className2 = "vq-fadeout") {
if (!el) {
return;
}
this.addClass(el, className1);
this.removeClass(el, className2);
el.style.display = "";
}
hidden(el: HTMLElement, className1 = "vq-fadeIn", className2 = "vq-fadeout") {
if (!el) {
return;
}
if (el.style.display === "none") {
return;
}
this.removeClass(el, className1);
this.addClass(el, className2);
const remove = () => {
el.style.display = "none";
el.removeEventListener("webkitAnimationEnd", remove);
el.removeEventListener("animationend", remove);
el.removeEventListener("oAnimationEnd", remove);
};
el.addEventListener("webkitAnimationEnd", remove);
el.addEventListener("animationend", remove);
el.addEventListener("oAnimationEnd", remove);
// Promise.resolve().then(() => { });
}
cardShow(el: HTMLElement, className = "vq-card-show") {
if (!el) {
return;
}
if (el.style.display === "none") {
return;
}
this.addClass(el, className);
const remove = () => {
this.removeClass(el, className);
el.removeEventListener("webkitAnimationEnd", remove);
el.removeEventListener("animationend", remove);
el.removeEventListener("oAnimationEnd", remove);
};
el.addEventListener("webkitAnimationEnd", remove);
el.addEventListener("animationend", remove);
el.addEventListener("oAnimationEnd", remove);
}
/**
* 父节点下 特定class的第index个子元素背景闪动
* @param target 要设置动画的元素
* @param parent 父元素 通过父元素和下标获取目标元素
* @param index 设置动画的子元素下标
* @param specificClass 父元素下特定class的子元素
*/
backgroundFlash({ target, parent, index, specificClass }: { target?: HTMLElement; parent?: HTMLElement; index?: number; specificClass?: string }) {
let ele: HTMLElement = null;
if (target) {
ele = target;
} else if (parent) {
let list = [];
if (specificClass) {
list = [...parent.querySelectorAll(`.${specificClass}`)] as HTMLElement[];
} else {
list = [...parent.childNodes] as HTMLElement[];
}
ele = list[index];
}
this.addClass(ele, "vq-background-flash");
const remove = () => {
this.removeClass(ele, "vq-background-flash");
ele.removeEventListener("webkitAnimationEnd", remove);
ele.removeEventListener("animationend", remove);
ele.removeEventListener("oAnimationEnd", remove);
};
ele.addEventListener("webkitAnimationEnd", remove);
ele.addEventListener("animationend", remove);
ele.addEventListener("oAnimationEnd", remove);
}
/**
*
* @param {*} el
* @param {*} name 判断className是否存在
*/
hasClass(el: HTMLElement, name: string) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
const className = this.getClass(el);
return className.length > 0 && new RegExp("(^|\\s)" + name + "(\\s|$)").test(className);
}
/**
*
* @param {*} el
* @param {*} name 添加className
*/
addClass(el: HTMLElement, name: string) {
if (el.classList !== undefined) {
const classes = Util.splitWords(name);
for (let i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!this.hasClass(el, name)) {
const className = this.getClass(el);
this.setClass(el, (className ? className + " " : "") + name);
}
}
insert(parent: Node, c1: HTMLElement, c2?: HTMLElement) {
if (c2) {
parent.insertBefore(c1, c2);
} else {
parent.appendChild(c1);
}
}
/**
*
* @param {*} el 删除className
* @param {*} name
*/
removeClass(el: HTMLElement, name: string) {
if (!el) {
return;
}
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
this.setClass(el, Util.trim((" " + this.getClass(el) + " ").replace(" " + name + " ", " ")));
}
}
/**
*
* @param {*} el
* @param {*} name 设置className
*/
setClass(el: HTMLElement, name: string) {
el.className = name;
}
/**
*
* @param {*} el 获取className
*/
getClass(el: HTMLElement) {
return el.className || "";
}
/**
*
* @param {*} element 查找父亲大小不为空的元素
*/
getSizedParentNode(element: HTMLElement) {
do {
element = element.parentNode as HTMLElement;
} while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
return element;
}
// 获取元素位置
getScale(element: HTMLElement) {
if (!element) {
return {
x: 0,
y: 0,
w: 0,
h: 0,
};
}
const rect = element.getBoundingClientRect(); // Read-only in old browsers.
return {
x: rect.x,
y: rect.y,
w: rect.width,
h: rect.height,
bottom: rect.bottom,
};
}
geOffset(element: HTMLElement) {
return {
x: element.offsetLeft,
y: element.offsetTop,
w: element.offsetWidth,
h: element.offsetHeight,
};
}
findChild(parentNode: HTMLElement, childNode: any): HTMLElement {
if (childNode.parentNode === parentNode) {
return childNode;
}
while ((childNode = childNode.parentNode)) {
if (parentNode === childNode.parentNode) {
return childNode;
}
}
return null;
}
getEle(selector: string): HTMLElement {
const ele = document.querySelector<HTMLElement>(selector);
if (ele) {
return ele;
}
}
getChildren(parentNode: HTMLElement): HTMLElement[] {
const children: HTMLElement[] = [...parentNode.children] as HTMLElement[];
return children;
}
}
// const animations = {
// animation: "animationend",
// OAnimation: "oAnimationEnd",
// MozAnimation: "animationend",
// WebkitAnimation: "webkitAnimationEnd",
// };
// for (t in animations) {
// if (el.style[t] !== undefined) {
// return animations[t];
// }
// }
import App from "@/application/app";
import { DomUtil } from "@/utils/domUtil";
const boundary = 10;
export class DragBase extends DomUtil {
public static mouseUpTimer: NodeJS.Timeout;
source: HTMLElement; // 源容器
target: HTMLElement; //允许放置的容器
moveElement: HTMLElement; //允许拖拽的位置
permitDrag: boolean; //是否允许移动标识
x = 0;
y = 0;
left = 0;
top = 0;
currentX = 0;
currentY = 0;
isMove = false;
time: number = null;
marker: HTMLElement;
constructor(source: string | HTMLElement, target: string | HTMLElement, moveElement?: HTMLElement | string, public app?: App) {
super();
this.init(source, target, moveElement);
}
init(source: string | HTMLElement, target: string | HTMLElement, moveElement: HTMLElement | string) {
this.source = typeof source === "string" ? document.querySelector(source) : source;
this.target = typeof target === "string" ? document.querySelector(target) : target;
if (moveElement) {
this.moveElement = typeof moveElement === "string" ? document.querySelector(moveElement) : moveElement;
this.moveElement.addEventListener("mousedown", this.mousedown);
} else {
this.source.addEventListener("mousedown", this.mousedown);
}
}
mouseup = (e: MouseEvent) => {
e.stopPropagation();
this.permitDrag = false;
this.isMove = false;
this.source.style.background = "";
this.source.style.transition = "";
document.body.style.userSelect = "";
if (this.marker) {
document.body.removeChild(this.marker);
}
document.removeEventListener("mousemove", this.mousemove);
document.removeEventListener("mouseup", this.mouseup);
if (this.app) {
this.saveLayout();
}
};
mousemove = (e: MouseEvent) => {
if (!this.permitDrag) return false;
this.source.style.transition = "none";
let x = e.pageX - this.left;
let y = e.pageY - this.top;
const { w, h } = this.getScale(this.source);
const maxL = window.innerWidth - w - boundary;
const maxT = window.innerHeight - h - boundary;
//边界判断
x = x < boundary ? 0 : x;
x = x > maxL ? window.innerWidth - w : x;
y = y > maxT ? window.innerHeight - h : y;
y = y < boundary ? 0 : y;
this.currentX = x;
this.currentY = y;
this.source.style.left = x + "px";
this.source.style.top = y + "px";
this.isMove = true;
};
mousedown = (e: MouseEvent) => {
e.stopPropagation();
if (this.app) {
this.clearUpTime();
}
document.addEventListener("mousemove", this.mousemove);
document.addEventListener("mouseup", this.mouseup);
const selectEle = document.querySelector(".vq-select-area") as HTMLElement;
if (selectEle) {
selectEle.style.display = "none";
}
const { x, y } = this.getScale(this.source);
this.permitDrag = true;
this.x = x;
this.y = y;
this.left = e.pageX - this.x;
this.top = e.pageY - this.y;
this.source.style.position = "fixed";
this.source.style.left = this.x + "px";
this.source.style.top = this.y + "px";
this.time = new Date().getTime();
this.source.style.transform = "none";
this.source.style.transition = "none";
document.body.style.userSelect = "none";
this.marker = document.createElement("div");
this.marker.style.position = "fixed";
this.marker.style.top = "0";
this.marker.style.left = "0";
this.marker.style.bottom = "0";
this.marker.style.width = "100%";
this.marker.style.height = "100%";
this.marker.style.right = "0";
this.marker.style.zIndex = "1000";
document.body.appendChild(this.marker);
};
clearUpTime() {
clearTimeout(App.mouseUpTimer);
}
saveLayout() {
const { x, y, w, h } = this.getScale(this.source);
App.layoutPos.wraPos.left = x;
App.layoutPos.wraPos.top = y;
console.log("----drag--base--save");
console.log(App.layoutPos);
App.setMouseUpTimer();
}
}
import App from "@/application/app";
import { DomUtil } from "@/utils/domUtil";
const body = document.body || document.documentElement;
const min = 200;
export class Zoom extends DomUtil {
source: HTMLElement; // 源容器
boundaryEle?: HTMLElement; // 相互依赖元素
isReverse?: boolean;
isSave: boolean; // 元素位置是否需要保存,即是否为主面板
permitDrag: boolean; //是否允许移动标识
bw = 0;
bx = 0;
x = 0;
y = 0;
left = 0;
top = 0;
w = 0;
h = 0;
right = 0;
bottom = 0;
pw = 0;
ph = 0;
tf: HTMLElement;
tr: HTMLElement;
bf: HTMLElement;
br: HTMLElement;
le: HTMLElement;
re: HTMLElement;
te: HTMLElement;
be: HTMLElement;
marker: HTMLElement;
isMove = false;
type: string = null;
constructor(source: string | HTMLElement, isSave: boolean, boundaryEle?: string | HTMLElement, public app?: App) {
super();
this.init(source, isSave, boundaryEle);
}
init(source: string | HTMLElement, isSave: boolean, boundaryEle?: string | HTMLElement) {
this.source = typeof source === "string" ? document.querySelector(source) : source;
if (boundaryEle) {
this.boundaryEle = typeof boundaryEle === "string" ? document.querySelector(boundaryEle) : boundaryEle;
}
this.isSave = isSave;
this.clearDom();
const tf = (this.tf = this.create("i", "vq-zoom vq-zoom-tf", this.source));
const tr = (this.tr = this.create("i", "vq-zoom vq-zoom-tr", this.source));
const bf = (this.bf = this.create("i", "vq-zoom vq-zoom-bf", this.source));
const br = (this.br = this.create("i", "vq-zoom vq-zoom-br", this.source));
const le = (this.le = this.create("i", "vq-zoom vq-zoom-le", this.source));
const re = (this.re = this.create("i", "vq-zoom vq-zoom-re", this.source));
const te = (this.te = this.create("i", "vq-zoom vq-zoom-te", this.source));
const be = (this.be = this.create("i", "vq-zoom vq-zoom-be", this.source));
tf.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "tf";
this.mousedown(e);
});
tr.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "tr";
this.mousedown(e);
});
bf.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "bf";
this.mousedown(e);
});
br.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "br";
this.mousedown(e);
});
le.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "le";
this.mousedown(e);
});
re.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "re";
this.mousedown(e);
});
te.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "te";
this.mousedown(e);
});
be.addEventListener("mousedown", (e: MouseEvent) => {
this.type = "be";
this.mousedown(e);
});
}
mouseup = (e: MouseEvent) => {
this.permitDrag = false;
this.source.style.userSelect = "";
// this.source.style.transition = "all .3s linear";
document.removeEventListener("mousemove", this.mousemove);
document.removeEventListener("mouseup", this.mouseup);
body.style.userSelect = "";
body.removeChild(this.marker);
if (this.isSave && this.app) {
this.saveLayout();
}
};
mousemove = (e: MouseEvent) => {
if (!this.permitDrag) return false;
// const mouseMoveX = e.clientX;
// const mouseMoveY = e.clientY;
// const offsetLeft = this.source.offsetLeft;
// const offsetTop = this.source.offsetTop;
const dltX = e.clientX - this.x;
const dltY = e.clientY - this.y;
let width = 0;
let height = 0;
if (this.type === "br") {
// 右下角
width = dltX + this.w;
height = dltY + this.h;
if (height < min) {
height = min;
return;
}
if (width < min) {
width = min;
return;
}
if (dltX > this.right) {
return;
}
if (dltY > this.bottom) {
return;
}
}
if (this.type === "bf") {
// 左下角
width = this.w - dltX;
height = this.h + dltY;
const left = this.left + dltX;
if (height < min) {
height = min;
return;
}
if (width < min) {
width = min;
return;
}
if (-dltX > this.left) {
return;
}
if (dltY > this.bottom) {
return;
}
this.source.style.left = left + "px";
}
if (this.type === "tr") {
// 右上角
width = this.w + dltX;
height = this.h - dltY;
const top = this.top + dltY;
if (height < min) {
height = min;
return;
}
if (width < min) {
width = min;
return;
}
if (dltX > this.right) {
return;
}
if (-dltY > this.top) {
return;
}
this.source.style.top = top + "px";
}
if (this.type === "tf") {
// 左上角
width = this.w - dltX;
height = this.h - dltY;
const left = this.left + dltX;
const top = this.top + dltY;
if (height < min) {
height = min;
return;
}
if (width < min) {
width = min;
return;
}
if (-dltX > this.left) {
return;
}
if (-dltY > this.top) {
return;
}
this.source.style.left = left + "px";
this.source.style.top = top + "px";
}
if (this.type === "le") {
// 左边
width = this.w - dltX;
height = this.h;
const left = this.left + dltX;
if (width < min) {
width = min;
return;
}
if (-dltX > this.left) {
return;
}
this.source.style.left = left + "px";
}
if (this.type === "re") {
// 右边
width = this.w + dltX;
height = this.h;
if (width < min) {
width = min;
return;
}
if (dltX > this.right) {
return;
}
}
if (this.type === "be") {
// 下边
width = this.w;
height = this.h + dltY;
if (height < min) {
height = min;
return;
}
if (dltY > this.bottom) {
return;
}
}
if (this.type === "te") {
// 上边
width = this.w;
height = this.h - dltY;
const top = this.top + dltY;
if (height < min) {
height = min;
return;
}
if (-dltY > this.top) {
return;
}
this.source.style.top = top + "px";
}
this.source.style.width = width + "px";
this.source.style.height = height + "px";
};
mousedown = (e: MouseEvent) => {
e.stopPropagation();
if (this.isSave && this.app) {
this.clearUpTime();
}
const selectEle = document.querySelector(".vq-select-area") as HTMLElement;
if (selectEle) {
selectEle.style.display = "none";
}
this.permitDrag = true;
if (this.app) {
this.isReverse = this.app.position === "left";
}
this.source.style.userSelect = "none";
body.style.userSelect = "none";
document.addEventListener("mousemove", this.mousemove);
document.addEventListener("mouseup", this.mouseup);
const { x, y, w, h } = this.getScale(this.source);
if (this.boundaryEle) {
const { x: bx, w: bw } = this.getScale(this.boundaryEle);
this.bx = bx;
this.bw = bw;
}
this.left = x;
this.top = y;
this.w = w;
this.h = h;
this.x = e.clientX;
this.y = e.clientY;
const pw = window.innerWidth;
const ph = window.innerHeight;
this.right = pw - x - w;
this.bottom = ph - y - h;
this.marker = document.createElement("div");
this.marker.style.position = "fixed";
this.marker.style.top = "0";
this.marker.style.left = "0";
this.marker.style.bottom = "0";
this.marker.style.width = "100%";
this.marker.style.height = "100%";
this.marker.style.right = "0";
this.marker.style.zIndex = "10";
body.appendChild(this.marker);
};
clearDom() {
const zoomLl = [...this.source.children];
zoomLl.forEach(z => {
if (z.classList.contains("vq-zoom")) {
this.remove(z);
}
});
}
clearUpTime() {
clearTimeout(App.mouseUpTimer);
}
saveLayout() {
const { x, y, w, h } = this.getScale(this.source);
App.layoutPos.wraPos.left = x;
App.layoutPos.wraPos.top = y;
App.layoutPos.wraPos.width = w;
App.layoutPos.wraPos.height = h;
console.log("---zoom---save");
console.log(App.layoutPos);
App.setMouseUpTimer();
}
}

本文介绍了如何使用JavaScript实现模块的拖动自由布局功能。包括拖动后模块的移动逻辑,坐标判断,以及拖动过程中对相邻模块的影响。同时,文章提及了布局信息的存储,以及模块缩放功能的实现。通过封装拖动类,如DragComm、dragArea、dragMenu和zoomArea,来处理拖动、缩放和布局更新。拖动过程中,通过坐标计算预设模块位置,保证布局的正确性和性能。
961

被折叠的 条评论
为什么被折叠?



