制作 ngb 可拖动模态
Making a ngb draggable modal
我有一个 Angular 应用程序,我在其中使用了 ng-bootstrap to display a modal。
我的问题是 ngb 团队不支持拖动这些模式,而且显然没有任何时候这样做的计划 soon。
所以我的问题是:谁能告诉我如何使模态成为可拖动的?
提前致谢。
我没有太多时间,所以我会告诉你我刚才做的事情。只需查看 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;
}
这是我写的 - 有很多不必要的东西,只是为了尽可能地挤出每一点性能,同时还遵循 angular 最佳实践 - 我将在后面进行修剪下/简化版。
它们都只需要单独的指令,不需要 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)';
}
}
您可以使用 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'
});
});
**注意,我有一个 class 用于更改光标,我只希望模式的 header 作为拖动的句柄。
您可以使用 angular2-draggable 包。适合我。
我有一个 Angular 应用程序,我在其中使用了 ng-bootstrap to display a modal。
我的问题是 ngb 团队不支持拖动这些模式,而且显然没有任何时候这样做的计划 soon。
所以我的问题是:谁能告诉我如何使模态成为可拖动的?
提前致谢。
我没有太多时间,所以我会告诉你我刚才做的事情。只需查看 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;
}
这是我写的 - 有很多不必要的东西,只是为了尽可能地挤出每一点性能,同时还遵循 angular 最佳实践 - 我将在后面进行修剪下/简化版。
它们都只需要单独的指令,不需要 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)';
}
}
您可以使用 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'
});
});
**注意,我有一个 class 用于更改光标,我只希望模式的 header 作为拖动的句柄。
您可以使用 angular2-draggable 包。适合我。