1

J'ai une application Angular dans laquelle j'utilise ng-bootstrap pour afficher un modal.Faire un ngb draggable modal

Mon problème est que l'équipe de ngb ne supporte pas le déplacement de ces modaux et n'a apparemment pas l'intention de le faire à tout moment soon. Donc, ma question est: Quelqu'un peut-il me dire comment je peux faire un tel dragable modal?

Merci d'avance.

Répondre

1

Voici celui que je l'ai écrit - il y a beaucoup de choses inutiles qui est là juste pour grincer chaque morceau de performance que possible tout en suivant également les meilleures pratiques angulaires - je vais le suivre avec un trimm ed/version simplifiée.

Ils ont tous les deux seulement besoin de la directive seule, pas de modifications/requêtes CSS ou html autres que l'ajout du sélecteur de directives à l'élément que vous voulez. Ils utilisent tous les deux translate3d plutôt que de changer la position du haut et du gauche, ce qui peut déclencher l'accélération du GPU sur certains navigateurs, mais même sans cela, il fonctionne généralement plus facilement que le changement de position. C'est ce que la transformation traduire a été faite pour - déplacer un élément par rapport à soi-même. Ils utilisent tous deux HostBinding pour se lier à la propriété, plutôt que d'accéder directement à un attribut nativeElement (qui couple inutilement la directive au DOM). Le second est sympa bc il ne nécessite pas de dépendances pour ElementRef ou Renderer2, mais il ajoute un écouteur always-on à l'objet document donc je l'utilise rarement, même si son aspect est plus propre.

J'utilise le premier parce qu'il n'ajoute que l'écouteur mousemove quand on clique sur le modal, et le supprime quand il n'est plus nécessaire. De plus, il exécute toutes les fonctions de mouvement en dehors de l'angle de sorte que glisser le modal ne déclenchera pas constamment la détection de changement angulaire sans raison (rien dans la boîte modale ne changera pendant qu'il est traîné, je suppose, donc inutile de vérifier). Et puis, comme la plupart de mes modaux sont créés dynamiquement et détruits lorsqu'ils sont fermés, ils peuvent également supprimer les écouteurs d'événements dans ce cas. J'injecte elementref pour que je puisse obtenir une référence à l'élément parent de la directive qui nécessite l'accès à nativeElement, mais je ne modifie pas les valeurs, juste en les lisant une fois pour obtenir une référence. Je pense donc que sa pardonnable dans la doctrine angulaire: 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); 
    } 
} 
} 

version plus simple ci-dessous - si vous n'êtes pas intéressés à garder les écouteurs d'événements ouverts ou se lier à l'objet document

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)'; 
    } 
} 
+0

Merci, cela fonctionne:) Maintenant, j'ai juste besoin de comprendre pour obtenir le bon élément hôte :) – methgaard

+0

Le composant racine ou le plus proche que vous pouvez l'obtenir est idéal, car il occupe généralement l'espace plein écran. Si le niveau de hiérarchie est différent de plusieurs niveaux, vous pouvez obtenir le ElementRef dans le composant de l'application et l'envoyer au modal avec un service :-) Ou simplement dire le visser et le lier au document – diopside

0

Je n'ai pas beaucoup de temps, donc je vais vous donner ce que j'ai fait il y a longtemps. Il suffit de regarder le css, et les HostListeners dans le fichier ts.

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> 

STYLES:

.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 FICHIER:

@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; 

    }