为nzModal增加可拖拽功能 最后是RxJS实现版

实现功能很简单,就是nzModal像windows里的窗口一样,可以拖拽窗口以移动窗口位置。

思路也很简单。创建一个Directive,在Directive中先获取到modal-title的ElementRef对象和nz-modal的ElementRef。然后通过Render2为 modal-title 添加鼠标事件,再通过Render2修改nz-modal的style属性,达到跟随鼠标拖拽而移动的效果。

先创建一个新的Directive,并且引入ElementRef和Render2

import { Directive,ElementRef,Renderer2,AfterViewInit } from '@angular/core';

@Directive({
  selector: '[zmMovableModal]'
})
export class ZmMovableModalDirective {

  constructor(private elementRef:ElementRef,private render:Renderer2) {
  }
  ngAfterViewInit(){
  }
}

在你要事件拖拽的nzModal中增加这个新的Directive

  <nz-modal zmMovableModal nzTitle="Modal1" nzMask="false" (nzOnCancel)="hideModals()" [nzVisible]="modal1Visible"></nz-modal>
获取Element

首先我们在ZmMovableModalDirective中添加获取Title 和 nzModal的方法:

getModalElement(){
      return this.elementRef.nativeElement.querySelector('.ant-modal');
  }
  getModalTitleElment(){
    return this.elementRef.nativeElement.querySelector('.ant-modal-header');
  }

在ngAfterViewInit()里执行。

ngAfterViewInit(){
  let modalElement = this.getModalElement();
  let modalTitleElement = this.getModalTitleElment();
  console.log(modalElement)
  console.log(modalTitleElement);
}

如果控制台成功的打印出了对应的html代码段,就代表成功获取到了title和modal
如果是空的,可能是nzModal版本升级导致类名发生了变动,可以确定querySelector函数对应的class是否正确。

参数准备

在获取到title 和modal 后,我们先为实现拖拽功能准备一些参数:

//只有当鼠标点下之后,鼠标抬起之前,才能移动
private canMove :boolean = false;
//modal开始移动时的x,y坐标
  private modalX : number = 0;
  private modalY : number = 0;
//鼠标点下时,鼠标所在的坐标
  private mouseDownX :number = 0;
  private mouseDownY :number = 0;

有了这些准备后,我们开始为title添加鼠标事件。

鼠标事件

首先是鼠标点下的事件:

this.render.listen(modalTitleElement,'mousedown',function(event){
//记录modal和鼠标点击的起始坐标
      this.mouseDownX = event.clientX;
      this.mouseDownY = event.clientY;
//offsetLeft和offsetTop是相对窗口的坐标。
//我们的modal的移动正好也是基于整个窗口的。所以实现起来很简单。
      this.modalX = modalElement.offsetLeft;
      this.modalY = modalElement.offsetTop;
//将modal改为绝对定位。并且根据现在窗口的位置设置left和top
      this.render.setStyle(modalElement,"position","absolute");
      this.render.setStyle(modalElement,"top",`${this.modalY}px`);
      this.render.setStyle(modalElement,"left",`${this.modalX}px`);
//一切都准备好了 将状态设置 可移动。
      this.canMove = true;
}.bind(this));

然后设置mousemove的事件:

this.render.listen(modalTitleElement,'mousemove',function(event){
//只有在camMove状态下,才进行处理
      if(this.canMove){
//获得当前鼠标位置,并根据开始拖拽时的位置,计算出鼠标的偏移量
        let moveX = event.clientX - this.mouseDownX;
        let moveY = event.clientY - this.mouseDownY;
//鼠标的偏移量就是modal的偏移量。
//通过开始是modal的位置和偏移量计算Modal新的坐标
        let newModalX = this.modalX + moveX;
        let newModalY = this.modalY + moveY;
//将新坐标设置下去,这样modal就跟着鼠标移动了。
        this.render.setStyle(modalElement,"top",`${newModalY}px`);
        this.render.setStyle(modalElement,"left",`${newModalX}px`);
      }
    }.bind(this));

最后,是出挑抬起后的处理:

this.render.listen(modalTitleElement,'mouseup',function(event){
//将状态设置为不可移动
      this.canMove = false;
    }.bind(this));

这样nzModal拖拽的功能就实现了。

快速拖拽时产生BUG处理

虽然整体的功能实现了,但是还是存在一个问题。
在快速上下拖拽的时候,会有跟丢的现象。
应该是因为快速的鼠标飞出了title 的范围,那么加载在title上的mousemove事件就不会被调用,modal就不会移动了。
所以将mousemove事件绑定到更大的范围就处理了,
所以将

this.render.listen(modalTitleElement,'mousemove',function(event){
........

改成:

this.render.listen(this.elementRef.nativeElement,'mousemove',function(event){
............

无论怎么摇晃鼠标都不会跟丢了。

整体代码:
import { Directive,ElementRef,Renderer2,AfterViewInit } from '@angular/core';

@Directive({
  selector: '[zmMovableModal]'
})
export class ZmMovableModalDirective {
  private canMove :boolean = false;
  private modalX : number = 0;
  private modalY : number = 0;
  private mouseDownX :number = 0;
  private mouseDownY :number = 0;
  constructor(private elementRef:ElementRef,private render:Renderer2) {
  }
  ngAfterViewInit(){
    let modalElement = this.getModalElement();
    let modalTitleElement = this.getModalTitleElment();
    this.render.listen(modalTitleElement,'mousedown',function(event){
      this.mouseDownX = event.clientX;
      this.mouseDownY = event.clientY;
      this.modalX = modalElement.offsetLeft;
      this.modalY = modalElement.offsetTop;
      this.render.setStyle(modalElement,"position","absolute");
      this.render.setStyle(modalElement,"top",`${this.modalY}px`);
      this.render.setStyle(modalElement,"left",`${this.modalX}px`);
      this.canMove = true;
    }.bind(this));
    this.render.listen(modalTitleElement,'mouseup',function(event){
      this.canMove = false;
    }.bind(this));
   this.render.listen(this.elementRef.nativeElement,'mousemove',function(event){
      if(this.canMove){
        let moveX = event.clientX - this.mouseDownX;
        let moveY = event.clientY - this.mouseDownY;
        let newModalX = this.modalX + moveX;
        let newModalY = this.modalY + moveY;
        this.render.setStyle(modalElement,"top",`${newModalY}px`);
        this.render.setStyle(modalElement,"left",`${newModalX}px`);
      }
    }.bind(this));
  }
  getModalElement(){
      return this.elementRef.nativeElement.querySelector('.ant-modal');
  }
  getModalTitleElment(){
    return this.elementRef.nativeElement.querySelector('.ant-modal-header');
  }

}
新的问题

最近在使用中发现一个问题,就是如果nzTitle 是一个变量,那么虽然最后生成的html代码中有ant-modal-title这个div标签。但是在elementRef.nativeElement里却没相应的element。
这里还没找到原因。
想办先想办法绕过这个问题。

  • 1 将整个ant-modal-content作为拖拽的句柄。这个element肯定有的。
    但是有一个问题。如果modal中有文字需要复制的时候,你就无法通过鼠标选中文字进行复制。
  • 2 如果发现没有ant-modal-header,那么,向里面加入一个新的小element,作为拖拽的句柄。
    这样做的问题是操作不一致。会让用户产生疑惑。

最好还是能找到ant-modal-header element的根本原因是什么,从根本上解决问题。

将getModalTitleElement()方法代码改成如下代码,解决了这个问题:

  getModalTitleElment(){
    // let header = this.elementRef.nativeElement.querySelector('.ant-modal-header');
    let element = document.createElement("div") as any;
    // this.render.setStyle(element,"background","orange");
    this.render.setStyle(element,"width","100%");
    this.render.setStyle(element,"height","60px");
    this.render.setStyle(element,"position","absolute");
    this.render.setStyle(element,"top","0");
    this.render.setStyle(element,"left","0");
    this.render.appendChild(this.modalElement,element);
    return element;
  }

这样,只要用户拖动最上方的一段都可以拖拽整个模态框了。

使用RxJS

最近深入学习一些RxJS的用法。正好拿这个指令练练手。
获取element的方法不用改变。
在ngAfterViewInit中

let modalElement = this.getModalElement();
tihs.modalElement = modalElement;
let modalTitleElement = this.getModalTitleElement();
const mouseDown = Observable.fromEvent<MouseEvent>(modalTitleElement,"mousedown");
const mouseUp = Observable.fromEvent<MouseEvent>(this.elementRef.nativeElement,"mouseup");
const mouseMove = Observable.fromEvent<MouseEvent>(this.elementRef.nativeElement,"mousemove");
mouseDown.subscrible(event=>{
  this.mouseDownX = event.clentX;
  this.mouseDownY = event.clentY;
  this.modalX = modalElement.offsetLeft;
  this.modalY = modalElement.offsetTop;
  this.render.setStyle(modalElement,"position","absolute");
  this.render.setStyle(modalElement,"top",`${this.modalY}px`);
  this.render.setStyle(modalElelment,"left",`${this.mdoalX}px`);
});
mouseDown.map(event=>mouseMove.takeUntil(mouseUp))
.concatAll()
.subscribe(e=>{
  let moveX = e.clientX - this.mouseDownX;
  let moveY = e.clientY - this.mouseDownY;
  let newModalX = this.modalX + moveX;
  let newModalY = this.modalY + moveY;
  this.render.setStyle(modalElement,"top",`${newModalY}px`);
  this.render.setStyle(modalElement,"left",`${newModalX}px`);
});

不知道有什么方法可以用一串RxJS代码就实现整个功能。未来想到办法会更新上去。

新的解决方案

将上面代码的两个RxJS代码块合并为一个:

mouseDown.map(event=>{
  this.modalX = modalElement.offsetLeft;
  this.modalY = modalElement.offsetTop;
  this.render.setStyle(modalElment,"position","absolute");
  this.render.setStyle(modalElement,"top",`${this.modalY}px`);
  this.render.setStyle(modalElement,"left",`$(this.modalX)px`);
}).map(event=>mouseMove.takeUntil(mouseUp))
.contactAll()
.withLatestFrom(mouseDown,(move,down)=>{
  return {x:move.clientX-down.clientX,y:move.clientY-down.clientY}
})
.subscribe(pos=>{
  this.render.setStyle(modalElement,"top",`${this.modalY+pos.y}px`);
  this.render.setStyle(modalElement,"left",`$(this.modalX+pos.x)px`);
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容