【问题标题】:Making a ngb draggable modal [closed]制作 ngb 可拖动模式 [关闭]
【发布时间】:2018-01-31 17:34:42
【问题描述】:

我有一个 Angular 应用程序,我在其中使用 ng-bootstrap 来显示 modal

我的问题是 ngb 团队不支持拖动这些模态,并且显然没有任何计划这样做soon

所以我的问题是:谁能告诉我如何使这样的模态可拖动?

提前致谢。

【问题讨论】:

    标签: angular bootstrap-modal twitter-bootstrap-4 ng-bootstrap


    【解决方案1】:

    这是我写的那个 - 有很多不必要的东西,只是为了尽可能地表现出每一点性能,同时也遵循角度最佳实践 - 我将用一个精简/简化的版本来遵循它。

    它们都只需要单独的指令,除了将指令选择器添加到您想要的元素之外,不需要 css 或 html 更改/查询。他们都使用 translate3d 而不是更改顶部和左侧位置,这可以在某些浏览器上触发 GPU 加速,但即使没有它,它通常也比更改位置更平滑。它的目的是进行翻译转换 - 相对于自身移动元素。它们都使用 HostBinding 绑定到属性,而不是直接访问 nativeElement 属性(这不必要地将指令耦合到 DOM)。第二个很好,因为它不需要 ElementRef 或 Renderer2 的依赖项,但它为文档对象添加了一个永远在线的侦听器,所以我很少使用它,即使它看起来更干净。

    我最常使用第一个,因为它仅在单击模式时添加 mousemove 侦听器,并在不再需要时将其删除。此外,它运行 angular 之外的所有移动功能,因此拖动模态不会无缘无故地触发 angular 的变化检测(我怀疑,模态框内的任何内容都不会在被拖动时发生变化,因此无需检查)。然后,由于我的大多数模式都是动态创建并在关闭时销毁的,因此在这种情况下它们也可以删除事件侦听器。我注入 elementref 以便获得对需要访问 nativeElement 的指令的父元素的引用,但我实际上并没有修改这些值,只是读取它们一次以获取引用。所以我认为它在 Angular 学说中是可以原谅的:p

    import { Directive,
             Input,
             NgZone,
             Renderer2,
             HostListener,
             HostBinding,
             OnInit, 
             ElementRef } from '@angular/core';
    
    class Position {
       x: number; y: number;
       constructor (x, y) { this.x = x; this.y = y; }
    };
    
    @Directive({
      selector: '[lt-drag]'
    })
    export class LtDragDirective {
    
       private allowDrag = true;
       private moving = false;
       private origin = null;
    
       // for adding / detaching mouse listeners dynamically so they're not *always* listening
       private moveFunc: Function;
       private clickFunc: Function;
    
       constructor(private el:ElementRef,
                   private zone: NgZone,
                   private rend: Renderer2 ) {}
    
       @Input('handle') handle: HTMLElement; 
    
       @HostBinding('style.transform') transform: string = 'translate3d(0,0,0)'; 
    
       ngOnInit() {  
    
       let host = this.el.nativeElement.offsetParent; 
    
       // applies mousemove and mouseup listeners to the parent component, typically my app componennt window, I prefer doing it like this so I'm not binding to a window or document object
    
       this.clickFunc = this.rend.listen(host, 'mouseup' , ()=>{
         this.moving = false;
       });
    
        // uses ngzone to run moving outside angular for better performance
        this.moveFunc = this.rend.listen(host, 'mousemove' ,($event)=>{
          if (this.moving && this.allowDrag) {
            this.zone.runOutsideAngular(()=>{
               event.preventDefault();
               this.moveTo($event.clientX, $event.clientY);
            }); 
         }
      });
    } 
    
     // detach listeners if host element is removed from DOM
    ngOnDestroy() { 
       if (this.clickFunc ) { this.clickFunc(); }
       if (this.moveFunc )  { this.moveFunc();  }
    }
    
     // parses css translate string for exact px position
     private getPosition(x:number, y:number) : Position {
        let transVal:string[] = this.transform.split(',');
        let newX = parseInt(transVal[0].replace('translate3d(',''));
        let newY = parseInt(transVal[1]);
        return new Position(x - newX, y - newY);
     }
    
     private moveTo(x:number, y:number) : void {
        if (this.origin) {
           this.transform = this.getTranslate( (x - this.origin.x), (y - this.origin.y) );
        }
     }
    
     private getTranslate(x:number,y:number) : string{
        return 'translate3d('+x+'px,'+y+'px,0px)';
     }
    
      @HostListener('mousedown',['$event'])
      onMouseDown(event: MouseEvent) {
        if (event.button == 2 || (this.handle !== undefined && event.target !== 
       this.handle)) {
         return;
        }
        else {
         this.moving = true;
         this.origin = this.getPosition(event.clientX, event.clientY);
       }
     } 
    }
    

    下面是更简单的版本——如果您不关心保持事件侦听器打开或绑定到文档对象

     import { Directive,
             Input, 
             HostListener,
             HostBinding,
             OnInit  } from '@angular/core';
    
     class Position {
       x: number; y: number;
       constructor (x, y) { this.x = x; this.y = y; }
     };
    
    @Directive({
       selector: '[lt-drag]'
    })
    export class LtDragDirective {
    
         private moving = false;
        private origin = null;
    
       constructor( ) {}
    
        @Input('handle') handle: HTMLElement; 
    
        @HostBinding('style.transform') transform: string = 'translate3d(0,0,0)'; 
    
        @HostListener('document:mousemove',[$event]) mousemove($event:MouseEvent) {
           event.preventDefault();
           this.moveTo($event.clientX, $event.clientY);
       }
    
        @HostListener('document:mouseup') mouseup() { 
            this.moving = false;
       }
    
        @HostListener('mousedown',['$event'])
       onMouseDown(event: MouseEvent) {
          if (event.button == 2 || (this.handle !== undefined && event.target     !== this.handle)) {
         return;   // if handle was provided and not clicked, ignore
         }
         else {
            this.moving = true;
            this.origin = this.getPosition(event.clientX, event.clientY);
       }
     }
      private getPosition(x:number, y:number) : Position {
         let transVal:string[] = this.transform.split(',');
         let newX = parseInt(transVal[0].replace('translate3d(',''));
         let newY = parseInt(transVal[1]);
         return new Position(x - newX, y - newY);
      }
    
       private moveTo(x:number, y:number) : void {
          if (this.origin) {
            this.transform = this.getTranslate( (x - this.origin.x), (y -  
            this.origin.y) );
          }
       }
    
       private getTranslate(x:number,y:number) : string{
          return 'translate3d('+x+'px,'+y+'px,0px)';
       }
     }
    

    【讨论】:

    • 谢谢,它有效 :) 现在我只需要弄清楚获得正确的主机元素 :)
    • 根组件或尽可能接近它是最理想的,因为它通常会占用整个屏幕空间。如果它远离模态的许多层次结构,您可以在应用程序组件中获取 ElementRef 并使用服务将其发送到模态:-) 或者只是说拧紧它并绑定到文档
    • 修复@HostListener('document:mousemove', [$event]) -> @HostListener('document:mousemove', ['$event'])
    【解决方案2】:

    可以使用npm包jquery和jqueryui

    npm install --save jquery jqueryui
    

    那么就这么简单了

    添加导入:

    import * as $ from 'jquery';
    import 'jqueryui';
    

    或使用

    declare var $: any;
    

    将以下内容添加到您的 ngOnInit() 方法中:

    $(document).ready(function(){
      let modalContent: any = $('.modal-content');
      let modalHeader = $('.modal-header');
      modalHeader.addClass('cursor-all-scroll');
      modalContent.draggable({
          handle: '.modal-header'
      });
    });
    

    **注意,我有一个更改光标的类,我只希望模态的标题成为拖动的句柄。

    DEMO

    【讨论】:

      【解决方案3】:

      我没有太多时间,所以我就给你我刚才做的。只看css,以及ts文件中的HostListeners。

      HTML:

      <div class="pdf-container">
        <div #pdfWrapper class="pdf-wrapper" [ngClass]="{'active': pdf}">
          <canvas #pdfCanvas [ngStyle]="{'pointer-events': pdf ? 'all' : 'none'}"></canvas>
          <span (click)="removePDF()" class="btn-pdf pdf-close" *ngIf="pdf" [ngStyle]="{'pointer-events': pdf ? 'all' : 'none'}">X</span>
          <span (click)="previousPage()" *ngIf="pdf && currentPage > 1" class="btn-pdf pdf-previous" [ngStyle]="{'pointer-events': pdf ? 'all' : 'none'}"><</span>
          <span (click)="nextPage()" *ngIf="pdf && currentPage < numPages" class="btn-pdf pdf-next" [ngStyle]="{'pointer-events': pdf ? 'all' : 'none'}">></span>
        </div>
      </div>
      

      风格:

      .pdf-container {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
      }
      .pdf-wrapper {
        position: relative;
        width: 450px;
        height: 600px;
      }
      .pdf-wrapper.active {
        box-shadow: 0 0 5px black;
        cursor: move;
      }
      canvas {
        position: absolute;
        width: inherit;
        height: inherit;
      }
      .btn-pdf {
        position: absolute;
        width: 20px;
        height: 20px;
        background-color: rgba(0, 0, 0, 0.5);
        color: white;
        text-align:center;
        cursor: pointer;
      }
      .btn-pdf.pdf-close {
        right: 0;
      }
      .btn-pdf.pdf-previous {
        top: 50%;    
      }
      .btn-pdf.pdf-next {
        right: 0;
        top: 50%
      }
      

      TS 文件:

      @ViewChild('pdfWrapper') pdfWrapper;
      private mouseDown : boolean = false;
        private lastMouseDown;
      
      @HostListener('mouseup')
        onMouseup() {
          this.mouseDown = false;
        }
      
        @HostListener('mousemove', ['$event'])
        onMousemove(event: MouseEvent) {
          if(this.mouseDown && this.pdf) {
            this.pdfWrapper.nativeElement.style.top = (event.clientY - this.lastMouseDown.offsetY) + 'px';
            this.pdfWrapper.nativeElement.style.left = (event.clientX - this.lastMouseDown.offsetX) + 'px';
          }
      
      @HostListener('mousedown', ['$event'])
        onMousedown(event) {
          if (!this.mouseDown) {
            this.lastMouseDown = event;
          }
          this.mouseDown = true;
      
        }
      

      【讨论】:

        【解决方案4】:

        您可以使用 angular2-draggable 包。适合我。

        https://github.com/xieziyu/angular2-draggable

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-08
        • 1970-01-01
        • 2015-05-31
        • 1970-01-01
        相关资源
        最近更新 更多