The events touchstart, touchend and touchmove are supported by angular. So you can imagine a directive like:
export interface TouchEventType {
element: TouchDirective;
incrX: number;
incrY: number;
}
@Directive({
selector: '[touch]',
exportAs: 'touch'
})
export class TouchDirective {
origin: any = { x: 0, y: 0 };
style: any = null;
rect: any = { x: 0, y: 0 };
incrX: number = 0;
incrY: number = 0;
@Input('touch') direction: 'horizontal' | 'vertical' | null = null;
@Output() touchMove: EventEmitter<any> = new EventEmitter<any>();
@HostListener('touchstart', ['$event']) touchStart(event) {
this.origin = {
x: event.touches[0].screenX,
y: event.touches[0].screenY
};
}
@HostListener('touchmove', ['$event']) touch(event: TouchEvent) {
this.incrX = this.rect.x + event.touches[0].screenX - this.origin.x;
this.incrY = this.rect.y + event.touches[0].screenY - this.origin.y;
this.style =
this.direction == 'horizontal'
? {
transform: 'translateX(' + this.incrX + 'px)'
}
: this.direction == 'vertical'
? {
transform: 'translateY(' + this.incrY + 'px)'
}
: {
transform: 'translateY(' + this.incrX + 'px,' + this.incrY + 'px)'
};
if (this.direction)
window.scrollBy(this.direction=='horizontal'?
event.touches[0].screenX - this.origin.x:0,
this.direction=='vertical'?
event.touches[0].screenY - this.origin.y:0)
}
@HostListener('touchend', ['$event']) touchEnd() {
this.rect = { y: this.incrY, x: this.incrX };
this.touchMove.emit({
element: this,
incrX: this.incrX,
incrY: this.incrY
});
}
@HostBinding('style') get _() {
return this.style;
}
constructor(private elementRef: ElementRef) {}
reset() {
this.style = null;
this.rect = { x: 0, y: 0 };
}
}
You can use in an .html like
<ng-container *ngFor="let item of array;let i=index">
<p touch='horizontal' (touchMove)="touchmove($event,i)">
Start editing to see some magic happen {{item}}
</p>
</ng-container>
array:any[]=[1,2,3,4,5,6,7]
touchmove(event:TouchEventType,index:number)
{
if (event.incrX<-10) //10px to the left
this.array.splice(index,1)
else
event.element.reset()
}
See the stackblitz without any warranty
Update If we want that work in touched and no touched screen we can take another approach. Instead of use @HostListener
we can use fromEvent
rxjs operator. (else we need make a hostListenr over mousedown,mouseup and mousemove)
To control at time the touch events and mouse events we use rxjs merge operator, that received the value from the two observables. To use exact the same code, we use "map" in the touch events to convert the respose (a TouchEvent) in a MouseEvent.
The last point is use the "tap" to know if is a touchscreen or not. In a touch screen if we swipe down/up, we need scroll the window dwon/up is oure directive only allow horizontal movements
@Directive({
selector: '[touch]',
exportAs: 'touch'
})
export class TouchDirective {
origin: any = { x: 0, y: 0 };
style: any = null;
rect: any = { x: 0, y: 0 };
incrX: number = 0;
incrY: number = 0;
onDrag:boolean=false;
isTouched:boolean=false;
moveSubscription: any;
downSubscription: any;
@Input('touch') direction: 'horizontal' | 'vertical' | null = null;
@Output() touchMove: EventEmitter<any> = new EventEmitter<any>();
@HostBinding('style') get _() {
return this.style;
}
@HostBinding('class.no-select') get __() {
return this.onDrag;
}
constructor(private elementRef: ElementRef) {}
ngOnInit() {
this.downSubscription=merge(
fromEvent(this.elementRef.nativeElement, 'mousedown').pipe(tap(_=>this.isTouched=false)),
fromEvent(this.elementRef.nativeElement, 'touchstart').pipe(tap(_=>this.isTouched=true),
map((event: TouchEvent) => ({
target: event.target,
screenX: event.touches[0].screenX,
screenY: event.touches[0].screenY
}))
)
).subscribe((event: MouseEvent) => {
//see that is the same code that @HostListener('touchstart', ['$event'])
this.origin = {
x: event.screenX,
y: event.screenY
};
this.onDrag=true;
merge(fromEvent(document, 'mouseup'), fromEvent(document, 'touchend'))
.pipe(take(1))
.subscribe(() => {
//see that is the same code that @HostListener('touchend', ['$event'])
if (this.moveSubscription) {
this.moveSubscription.unsubscribe();
this.moveSubscription = undefined;
}
this.rect = { y: this.incrY, x: this.incrX };
this.touchMove.emit({
element: this,
incrX: this.incrX,
incrY: this.incrY
});
this.onDrag=false;
});
if (!this.moveSubscription) {
this.moveSubscription = merge(
fromEvent(document, 'mousemove'),
fromEvent(document, 'touchmove').pipe(
map((event: TouchEvent) => ({
target: event.target,
screenX: event.touches[0].screenX,
screenY: event.touches[0].screenY
}))
))
.subscribe((event: MouseEvent) => {
//see that is the same code that @HostListener('touchmove', ['$event'])
this.incrX = this.rect.x + event.screenX - this.origin.x;
this.incrY = this.rect.y + event.screenY - this.origin.y;
this.style =
this.direction == 'horizontal'
? {
transform: 'translateX(' + this.incrX + 'px)'
}
: this.direction == 'vertical'
? {
transform: 'translateY(' + this.incrY + 'px)'
}
: {
transform: 'translateY(' + this.incrX + 'px,' + this.incrY + 'px)'
};
if (this.direction && this.isTouched)
window.scrollBy(
this.direction == 'horizontal'
? event.screenX - this.origin.x
: 0,
this.direction == 'vertical'
? event.screenY - this.origin.y
: 0
);
})
}
});
}
ngOnDestroy() {
this.downSubscription.unsubscribe();
}
reset() {
this.style = null;
this.rect = { x: 0, y: 0 };
}
}
we add the class
.no-select {
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Old versions of Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome, Edge, Opera and Firefox */
}
in styles.css and
@HostBinding('class.no-select') get __() {
return this.onDrag;
}
To not select when the element is swipping
The stackblitz for touch/no touch screen