1. 实现目标效果
2. 实现原理
1. 前言
在这之前可以先进入我的博客js 仿window拉选框 / 选中文件 可以先看一下这篇文章,实现原理大同小异,最后我会贴上代码方便使用。
如果遇到缩放问题,可以使用“window.devicePixelRatio”这段代码获取缩放比例,并加入到计算方向的代码块中。
2. 规则
可以看到每个方块的”上左,中上,上右,中左,中右,下左,中下,下右“,8个顶点,而”中上,中下,中左,中右“只能向一个轴方向运动,其余的点可以随意拖动
3.原理
可以看到最上方gif图,当拖动”上左”的点时,可以找到对角“下右”为二维坐标系的原点,心里可以想象一个二维坐标系,如图2.3.1,拖动点击的点,可以想象成,初始点击的位置在“下右”移动到了“上左”,到这里就和我前言里推荐大家先去看的那篇文章一样了。
拖动”中上,中下,中左,中右“这四个点时,可以在元素中心想象一个二维坐标系,如图2.3.2,“中上,中下”为Y轴,“中左,中右”为X轴,当在Y轴运动时只需要修改元素的top和height属性,X轴运动时只需要修改left和width属性
最后就可以生成一个json格式数据传给后端了
图2.3.1 图2.3.2
3. 代码
方法解释
- initEL 给大盒子初始化状态
- addEventEl 给小盒子添加对应的事件
- ElMouseDown 小盒子鼠标按下方法|顶点鼠标按下事件
- ElMouseMove 小盒子鼠标移动方法
- ElMouseUp 小盒子鼠标抬起方法
- itemMouseMove 顶点移动方法
- itemMouseUp 顶点抬起方法
- initItemEl 初始化小盒子的状态
- initItemBox 处理顶点拖动效果
- addItemBoxDom 给小盒子添加顶点
- getClientRect 获取元素坐标属性(bottom height left right top width x y)
- isOutBoundary 判断拖动小盒子是否到达边界
- $ 获取单个元素dom
- $All 获取多个元素dom
在 addItemBoxDom方法中会为所有顶点添加Attribute属性“data-index”用于区分具体哪个顶点
可以使用drag类进行二次封装,添加自己想要的方法,不懂得可以私信博主
const drag = new Drag({
el:'.container', // 盒子
item:'.drag-item', // 小盒子
});
class Drag{
constructor({
el,item
}){
this.el = this.$(el);
this.item = item;
this.itemList = this.$All(item);
this.itemBoxSize = 5;
this.InitPointer = {x:0,y:0}; // 鼠标初始位置
this.currentEl = null;
this.currentElItem = null;
this.isActive = false; // 标记是否第一次点击
this.init();
}
init(){
this.initEl();
this.initItemEl();
this.addEventEl();
}
initEl(){
this.el.style.cssText = 'position: relative;';
}
addEventEl(){
for(let i = 0; i < this.itemList.length; i++){
this.itemList[i].addEventListener('mousedown',this.ELMouseDown);
}
}
ELMouseDown = (e) => {
this.currentEl = e.target;
let rect = this.getClientRect(this.currentEl);
this.elInitPointer = {x:e.x,y:e.y,rectX:rect.left,rectY:rect.top,rectW:rect.width,rectH:rect.height};
this.currentElItem = e.target;
// 区分方块
if(e.target.className == 'drag-item-box'){
this.isActive = true
this.currentEl = e.target.offsetParent
rect = this.getClientRect(this.currentEl);
this.elInitPointer = {x:e.x,y:e.y,rectX:rect.left,rectY:rect.top,rectW:rect.width,rectH:rect.height,top:rect.top,left:rect.left};
document.addEventListener('mousemove',this.itemMouseMove);
document.addEventListener('mouseup',this.itemMouseUp);
return;
}
document.addEventListener('mousemove',this.ELMouseMove);
document.addEventListener('mouseup',this.ELMouseUp);
}
ELMouseMove = (e) => {
// this.removeLine();
const {x:initX,y:initY,rectX,rectY} = this.elInitPointer;
const {x,y} = e;
const xMove = rectX + x - initX;
const yMove = rectY + y - initY;
this.currentEl.style.top = `${yMove}px`;
this.currentEl.style.left = `${xMove}px`;
this.isOutBoundary();
// this.isShowLine();
}
ELMouseUp = (e) => {
document.removeEventListener('mousemove',this.ELMouseMove);
document.removeEventListener('mouseup',this.ELMouseUp);
// this.removeLine();
}
itemMouseMove = (e) => {
this.initItemBox(e)
}
itemMouseUp = (e) => {
document.removeEventListener('mousemove',this.itemMouseMove);
document.removeEventListener('mouseup',this.itemMouseUp);
}
initItemEl(){
const boxItme = 8;
for(let i = 0; i < this.itemList.length; i++){
const item = this.itemList[i];
item.style.position = 'absolute';
item.style.left = '50px';
item.style.top = `${i * 120}px`
for(let j = 0; j < boxItme; j++){
this.addItemBoxDom(item,j);
}
}
}
initItemBox(e){
this.isOutBoundary();
const data_index = Number(this.currentElItem.getAttribute('data-index'));
const id = '.drag-item-box';
const list = this.currentEl.querySelectorAll(id);
const arr = [0,2,5,7];
const arr1 = [1,6];
const {x:initX,y:initY,rectW,rectH,left,top} = this.elInitPointer;
let x = 0;
let y = 0;
let moveX = 0;
let moveY = 0;
if(this.isActive){
const obj = {
0:()=>{
x = left + rectW;
y = top + rectH;
moveX = left;
moveY = top;
},
2:() => {
x = left;
y = top + rectH;
moveX = left + rectW;
moveY = top;
},
5:() => {
x = left + rectW;
y = top;
moveX = left;
moveY = top + rectH;
},
7:() => {
x = left;
y = top;
moveX = left + rectW;
moveY = top + rectH;
},
1:() => {
y = top + rectH;
moveY = top;
},
6:()=>{
y = top;
moveY = top + rectH;
},
3:()=>{
x = left + rectW;
moveX = left;
},
4:()=>{
x = left;
moveX = left + rectW;
},
}
obj[data_index]();
this.elInitPointer.x = x;
this.elInitPointer.y = y;
this.isActive = false;
}else{
x = initX;
y = initY;
moveX = e.clientX;
moveY = e.clientY;
}
// 计算移动后对应元素宽高
const width = Math.abs(x - moveX);
const height = Math.abs(y - moveY);
if(arr.includes(data_index)){
if(moveX > x && moveY > y){ // 4
this.currentEl.style.left = x + 'px';
this.currentEl.style.top = y + 'px';
}else if(y > moveY && x < moveX){ // 1
this.currentEl.style.top = moveY + 'px';
this.currentEl.style.left = x + 'px';
}else if(moveX < x && moveY > y){ // 3
this.currentEl.style.left = moveX + 'px';
this.currentEl.style.top = y + 'px';
}else if(moveY < y && moveX < x){ // 2
this.currentEl.style.top = moveY + 'px';
this.currentEl.style.left = moveX + 'px';
}
this.currentEl.style.height = height + 'px';
this.currentEl.style.width = width + 'px';
}else{
if(arr1.includes(data_index)){
if(moveY > y){
this.currentEl.style.top = y + 'px';
}else{
this.currentEl.style.top = moveY + 'px';
}
this.currentEl.style.height = height + 'px';
}else{
if(moveX > x){
this.currentEl.style.left = x + 'px';
}else{
this.currentEl.style.left = moveX + 'px';
}
this.currentEl.style.width = width + 'px';
}
}
}
addItemBoxDom(dom,index){
/**
* 0 1 2
* 3 4
* 5 6 7
*/
const div = document.createElement('div');
div.classList = "drag-item-box";
div.setAttribute('data-index',index);
div.style.cssText = `z-index:1000;cursor:pointer;position: absolute;width:${this.itemBoxSize}px;height:${this.itemBoxSize}px;border:1px solid #000;`;
const rect = this.getClientRect(dom);
let x = 0;
let y = 0;
// itemBoxSize 一半
const itemBoxh = this.itemBoxSize / 2;
const style = {
0:() => {
x = `calc(-${itemBoxh}px)`
y = `calc(-${itemBoxh}px)`;
},
1:()=> {
x = `calc(50% - ${itemBoxh}px)`;
y = `calc(-${itemBoxh}px)`;
},
2:()=> {
x = `calc(100% - ${itemBoxh}px)`;
y = `calc(-${itemBoxh}px)`;
},
3:()=> {
x = `calc(-${itemBoxh}px)`;
y = `calc(50% - ${itemBoxh}px)`;
},
4:()=> {
x = `calc(100% - ${itemBoxh}px)`;
y = `calc(50% - ${itemBoxh}px)`;
},
5:()=> {
x = `calc(-${itemBoxh}px)`;
y = `calc(100% - ${itemBoxh}px)`;
},
6:()=> {
x = `calc(50% - ${itemBoxh}px)`;
y = `calc(100% - ${itemBoxh}px)`;
},
7:()=> {
x = `calc(100% - ${itemBoxh}px)`;
y = `calc(100% - ${itemBoxh}px)`;
}
}
style[index]();
div.style.left = x;
div.style.top = y;
dom.appendChild(div);
}
// 获取元素属性
getClientRect(dom){
// bottom height left right top width x y
return dom.getBoundingClientRect();
}
// 判断方块是否到边界
isOutBoundary(){
const container = this.getClientRect(this.el)
const rect = this.getClientRect(this.currentEl);
if(rect.left <= 0){this.currentEl.style.left = container.left;}
if(rect.top <= 0){this.currentEl.style.top = container.top;}
if(rect.left >= container.width - rect.width){this.currentEl.style.left = container.width - rect.width + 'px';}
if(rect.top >= container.height - rect.height){this.currentEl.style.top = container.height - rect.height + 'px';}
}
$(el){
return document.querySelector(el);
}
$All(el){
const el_list = document.querySelectorAll(el);
if(el_list.length <= 0){
return [];
}
return el_list;
}
}