Open Domvel opened 5 years ago
Thanks for the issue. Ionic 3 had HammerJS internally for these kinds of things. We found that HammerJS had a tendency to capture events and not let them propagate. This caused things as simple as scrolling to no longer work. As a result, we removed it in Ionic 4 and are not likely to add it back.
Users are welcome to install it on their own, but we do not provide any kind of official support for HammerJS at this time.
In terms of having our own solution, we exposed a createGesture
function in Ionic 4.8.0. It is still being fully tested, so things might change until it's fully "released". I'd recommend giving that a try and seeing if that works for your use case. I am going to keep this issue open in case you are interested in testing/providing any feedback on the createGesture
function.
Here is the source for createGesture
: https://github.com/ionic-team/ionic/blob/master/core/src/utils/gesture/index.ts
And here is an example of it being used: https://github.com/ionic-team/ionic/blob/cd75428785fc04caa65278438c55aac5b4265db8/core/src/components/menu/menu.tsx#L174-L184
Thanks!
Thanks for the answer.
I also don't like hammerjs. Ionic should have an own solution as a mobile framework. Your solution looks promising. But I'm not sure how to create a long press event like in Ionic v3 the (press)
event for html-elements. In my case, currently I'm on the migration from v3 to v4. π It's hard for our complex app. I found no documentation / breaking changes in the migration guide about the missing press events. It was a surprise for me. Anyway... I found a (temp) solution for me by a custom Angular directive longPress
. Which detect a long press by setTimeout()
(and clearTimeout
to abort).
I also have a pressing event, which uses setInterval()
to tick every x time while the button is being pressed. ...
Are you sure what your suggestion can create a long-press event? Like the (press)
event from Ionic v3? I'm not sure.
Anyway, there should be several basic gestures in Ionic. (some events may already exist.)
https://ionicframework.com/docs/v3/components/#gestures
Ok, e.g. rotate
, pinch
, etc. are more special gestures which should only accessible with a custom property / directive on an element. (triviality)
Back to the utils/gesture
. I have no idea how to use it and I don't know if a long press is feasible.
In my opinion it should be very easy to use.
<ion-button (press)="onLongPress()" (pressing)="onHoldingEachXTime()"></ion-button>
pressing
as a cycling holding event does already exist in Ionic, I do not need to create my own Angular directive. But if the Ionic-Team say "no", I could create my directive like so: import { Gesture } from `@ionic/angular`;
// Or from another source. But maybe this should be an Angular thing? idk.
@Directive({
selector: '[longPress]'
})
export class LongPressDirective implements OnInit, OnDestroy {
ionicGesture: Gesture;
ngOnInit() {
this.ionicGesture = new Gesture(this.nativeElement);
this.ionicGesture.on('press', event => {
// Enable the pressing-event cyclical event emitter.
this.pressingTimerSubscribe();
})
// Currently I use native HTML event listener to detect `pointerup` to clear the timer.
// ...
}
}
I also uses pointercancel
, pointerout
, pointerleave
to avoid latching behavior.
It's really challenging to handle these events. In my case at the mobile device (Android 7),
the cancel events etc. are sometimes triggered without any reason. And a few cases the are never triggered. It's weird. ... sigh It's not a trivial issue. Maybe it has to do with the Ionic v3 and Hammerjs. But anyway... I would be happy if the awesome Ionic-Team build an own gesture feature with blackjack and hoo... you know? --quote bender π
For now I'd built my own long press directive by setInterval. But it will not win a beauty contest. Sorry for too many words. ^^
Thanks for the clarification. We definitely intend on adding more documentation and best practices for doing specialized gestures. This is something we are actively discussing, so we hope to have more to share soon!
Any update on this?
We still have plans for this. More to announce soon!
Exist any way to prevent issues using Hammerjs in the meantime? π
Not sure if the below follows best practices throughout, but for what it's worth, my best shot at implementing a LongPress
gesture. It works very well I must say.
import { createGesture, Gesture, GestureDetail } from '@ionic/core';
import { EventEmitter, Directive, OnInit, OnDestroy, Output, Input, ElementRef } from '@angular/core';
@Directive({
selector: '[appLongPress]'
})
export class LongPressDirective implements OnInit, OnDestroy {
ionicGesture: Gesture;
timerId: any;
@Input() delay: number;
@Output() longPressed: EventEmitter<any> = new EventEmitter();
constructor(
private elementRef: ElementRef
) { }
ngOnInit() {
this.ionicGesture = createGesture({
el: this.elementRef.nativeElement,
gestureName: 'longpress',
threshold: 0,
canStart: () => true,
onStart: (gestureEv: GestureDetail) => {
gestureEv.event.preventDefault();
this.timerId = setTimeout(() => {
this.longPressed.emit(gestureEv.event);
}, this.delay);
},
onEnd: () => {
clearTimeout(this.timerId);
}
});
this.ionicGesture.setDisabled(false);
}
ngOnDestroy() {
this.ionicGesture.destroy();
}
}
@olivermuc it looks like you need to compare the distance when the setTimeout is executed:
this.pressGesture = createGesture({
el: this.pressButton,
gestureName: 'button-press',
gesturePriority: 100,
threshold: 0,
direction: 'x',
passive: true,
onStart: (detail: GestureDetail) => {
this.pressGesture['pressed'] = false;
this.pressGesture['timerId'] = setTimeout(() => {
if (Math.abs(detail.deltaX) < 10 && Math.abs(detail.deltaX) < 10)
{
this.onPress();
this.pressGesture['pressed'] = true;
}
}, 251)
},
onEnd: () => {
clearTimeout(this.pressGesture['timerId'])
if (this.pressGesture['pressed']) {
this.onPressUp();
}
}
});
this.pressGesture.setDisabled(false);
Thanks for the feedback @jdnichollsc. Definitely an option to add, for me staying within the initial click area is not critical hence I didn't add it - but good addition in case it is needed.
@olivercodes Could you please provide a sample of how this is configured and used? Thanks for the long press directive example. π
I figured it out with some trial and error since I'm wasn't familiar with the way this directive communication worked. In case anyone else could use some help this is what was added to the HTML. Thanks @olivercodes for the help with the directive.
<ion-button (longPressed)="longPressFunction()" appLongPress delay=1000>
Also had to add this to the local .module.ts
file in Ionic 4 as the app.module.ts
didn't work for some reason.
import { LongPressDirective } from 'src/app/directives/long-press.directive';
and
declarations: [
LongPressDirective,
]
@davidquon who is @olivercodes?
Whoops. Sorry @olivercodes I meant @olivermuc. π€¦ββ Thanks @jdnichollsc and @olivermuc. π
@olivermuc I mean about this PR https://github.com/ionic-team/ionic/pull/19861
Having a maxThreshold
option to allow a little movement on the x and y axis
@olivermuc I mean about this PR #19861 Having a
maxThreshold
option to allow a little movement on the x and y axis
When I tested, no movement constraints showed. Events were fired directly, regardless of any additional vertical or horizontal motion, and the onMove
would continuously fire - if needed.
Just out of curiosity: The reason I ditched Hammer.JS and used above approach was a terrible conflict with Chrome's devtool device/touch emulator. Essentially, when assigning new 'Recognizers', it caused Chrome's scrolling to stop. Apparently due to the way it hogs touch events.
Unfortunately I'm seeing similar behaviour with the above approach - not always, and hard to reproduce, but it happens about 3 out of 10 long-presses.
Once that happens, you actually have to close the browser tab and reopen for scrolling to work again.
Anyone else seeing this too?
@olivermuc Nice try with the custom gesture directive, I tested it and unfortunately found that:
with preventDefault called there is no scrolling of the parenting ion-container when dragging the element with the directive (understandably), without it scrolling works but... disregard this because it was an effect of trying to scroll immediately after dismissing an action sheet, weird ... however ...
it disables pull to refresh component on the parenting ion-content when pulling the element with the directive and also it disables ripple effect component on the element with the directive.
Tried messing with gesture priority without any luck. There has to be a way to make gestures play nice with each other. Can't wait for an updated documentation on gestures.
I'll try this approach to see how it behaves: https://github.com/Bengejd/Useful-ionic-solutions/blob/master/src/directives/long-press.directive.ts ... nope, doesn't use latest gesture system
I settled down on this solution because it plays really nice with Gestures, and that's because it doesn't use Gestures:
import { EventEmitter, Directive, OnInit, Output, Input, ElementRef } from '@angular/core';
import { timer, Subscription } from 'rxjs';
@Directive({
selector: '[appLongPress]'
})
export class LongPressDirective implements OnInit {
timerSub: Subscription;
@Input() delay: number;
@Output() longPressed: EventEmitter<any> = new EventEmitter();
constructor(
private elementRef: ElementRef<HTMLElement>
) { }
ngOnInit() {
const isTouch = ('ontouchstart' in document.documentElement);
const element = this.elementRef.nativeElement;
element.onpointerdown = (ev) => {
this.timerSub = timer(this.delay).subscribe(() => {
this.longPressed.emit(ev);
});
};
element.onpointerup = () => { this.unsub(); };
element.onpointercancel = () => { this.unsub(); };
if (isTouch) {
element.onpointerleave = () => { this.unsub(); };
}
}
private unsub() {
if (this.timerSub && !this.timerSub.closed) { this.timerSub.unsubscribe(); }
}
}
It tries not to do unnecessary onpointerleave event handler on non touch devices, ... I'm not sure if it's even necessary for the touch devices because onpointercancel seems to fire consistently to clear the timer.
Result: pull to refresh works now and also custom ripple effects on the elements with the directive :joy:
Using configuration provided by josh morony in his youtube video https://www.youtube.com/watch?v=TdORJC-J1gg
and using the event bindings from the medium article https://medium.com/madewithply/ionic-4-long-press-gestures-96cf1e44098b
, I was able to get what I needed done, including simulating an "on-hold" event
if it is possible for you to upgrade to angular 9 you can use HammerModule in your whole application by simply binding to elements. HammerModule: https://next.angular.io/api/platform-browser/HammerModule
your module.ts
import { BrowserModule , HammerModule} from '@angular/platform-browser';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, HammerModule],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
and in any part of your application you can simply bind with all of hammerjs methods
<ion-item (press)="editMessage()">
<ion-label>message</ion-label>
</ion-item>
Any Update on this Issue?
How do I test this or import module?
https://codepen.io/liamdebeasi/pen/KKdodjd
import { createGesture } from 'https://cdn.jsdelivr.net/npm/@ionic/core/dist/esm/index.mjs';
Says: Cannot find module 'https://cdn.jsdelivr.net/npm/@ionic/core/dist/esm/index.mjs'.ts(2307)
if it is possible for you to upgrade to angular 9 you can use HammerModule in your whole application by simply binding to elements.
does Hammer now support stopPropagation() ? If you check the second comment, @liamdebeasi mentions Hammer's lack of it "We found that HammerJS had a tendency to capture events and not let them propagate" and it is totally annoying.
@liamdebeasi Hello, are there any changes about Gesture. I'm looking for swipe gesture for y and x direction. Has ionic created a simple swipe gesture? or Do I need to create my own.
@ludonoel1 I have already marked your issue as a feature request for the x and y swipe direction: https://github.com/ionic-team/ionic-framework/issues/21704. It is currently in our backlog.
Press and pressup works for me when I downgrade angular versions. My ionic info:
Ionic:
Ionic CLI : 6.11.8 (/usr/local/lib/node_modules/@ionic/cli) Ionic Framework : @ionic/angular 5.0.4 @angular-devkit/build-angular : 0.803.25 @angular-devkit/schematics : 8.3.25 @angular/cli : 8.3.25 @ionic/angular-toolkit : 2.2.0
Cordova:
Cordova CLI : 10.0.0 Cordova Platforms : 6.0.0, browser Cordova Plugins : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 4.1.3, (and 8 other plugins)
Utility:
cordova-res : not installed native-run (update available: 1.1.0) : 1.0.0
System:
NodeJS : v12.18.3 (/usr/local/bin/node) npm : 6.14.8 OS : macOS Catalina
How to use the button events e.g. (tap)
and (press)
in Ionic 5? Removed? No replacement? I don't want to create a custom directive now. Without the obsolete hammer-js. Is something what a UI framework should handle.
@infacto We have documentation on gestures that you can use for now: https://ionicframework.com/docs/utilities/gestures. We plan to add some built-in gestures in a future update.
@liamdebeasi Thanks for the info. At this moment I crafting a directive with the Ionic GesturesController
to implement the events from Ionic 3 and more e.g. period long-press:
Please consider these events. And care about cancel events. On Ionic 3 I detected some quirks about unintentional interruptions or missing release events. Especially dangerous for the periodic event. The hold event is nice to have, the others required. Why hold event? Use case: You press and hold a button to change a number until you release it. Or send values over the web or bluetooth. - Other ideas are welcome.
Additional event ideas:
(In this context "press" is short. And a long press is explicitly named.) Ok, this is maybe a bit too special. I just think aloud. To trigger ideas. π I believe in the Ionic team. You are doing a great job. I'm sure you have more / better ideas about it. Oh, this proposal here didn't handle the other gestures like pan, move, etc. At this moment it's only about short and long press.
Update: The ripple-effect does not work anymore if the gestures directive is active. sigh
Could anyone suggest please .. (press) event dose not work on iOS platform.
<ion-item (press)="tapEvent($event,)"></ion-item>
Installed platforms ios 6.1.1,
Tested on iOS version ipad 12.5.1 and iphone 14.4
ionic info
Ionic:
Ionic CLI : 5.4.16 (/usr/local/lib/node_modules/ionic)
Ionic Framework : @ionic/angular 5.5.2
@angular-devkit/build-angular : 0.1000.8
@angular-devkit/schematics : 10.0.8
@angular/cli : 10.0.8
@ionic/angular-toolkit : 2.3.3
Cordova:
Cordova CLI : 10.0.0
Cordova Platforms : none
Cordova Plugins : no whitelisted plugins (0 plugins total)
Utility:
cordova-res : not installed
native-run : not installed
System:
ios-deploy : 1.11.3
ios-sim : 8.0.2
NodeJS : v14.15.4 (/usr/local/bin/node)
npm : 6.14.10
OS : macOS Big Sur
Xcode : Xcode 12.4 Build version 12D4e
if it is possible for you to upgrade to angular 9 you can use HammerModule in your whole application by simply binding to elements. HammerModule: https://next.angular.io/api/platform-browser/HammerModule
your module.ts
import { BrowserModule , HammerModule} from '@angular/platform-browser'; @NgModule({ declarations: [AppComponent], entryComponents: [], imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, HammerModule], providers: [ StatusBar, SplashScreen, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } ], bootstrap: [AppComponent] })
and in any part of your application you can simply bind with all of hammerjs methods
<ion-item (press)="editMessage()"> <ion-label>message</ion-label> </ion-item>
helped.
Just out of curiosity: The reason I ditched Hammer.JS and used above approach was a terrible conflict with Chrome's devtool device/touch emulator. Essentially, when assigning new 'Recognizers', it caused Chrome's scrolling to stop. Apparently due to the way it hogs touch events.
Unfortunately I'm seeing similar behaviour with the above approach - not always, and hard to reproduce, but it happens about 3 out of 10 long-presses.
Once that happens, you actually have to close the browser tab and reopen for scrolling to work again.
Anyone else seeing this too?
Yes I see it all the time. Anyone know why this is happening and how to avoid it? It may be that the page changes during a gesture and Chrome devtools phone emulator can't handle it.
It's only the phone emulator; turn that off and scrolling comes back. Turn it back on and the problem is fixed, so no need to close the tab. I.e. workaround: double click the phone emulator icon in the upper left of devtools. Although sometimes you do need to close the tab...hmmm.
...Better workaround: If you get stuck in the phone emulator, turn the phone emulator off, do a longpress, and then turn it back on. What I found was the longpress brings up the context menu in the phone emulator but doesn't when the emulator is off. So it's probably that context menu that is holding everything up. This workaround clears that it seems. If so, the solution would be disabling or avoiding the longpress context menu in the phone emu.
OK I have a working fix: Set window.oncontextmenu = function () { return false; };
before any long press while in devtools Device Mode and make it return true a second after the longpress is done (using setTimeout
or however you prefer). There are a variety of hacks to see if devtools is open such as @sindresorhus/devtools-detect . It's actually screwing up the scrolling any time that context menu is showing up, not even on a specific longpress element, so you may want to leave it off for your entire devtools session (but turn it back on for web users if they need it).
I settled down on this solution because it plays really nice with Gestures, and that's because it doesn't use Gestures:
import { EventEmitter, Directive, OnInit, Output, Input, ElementRef } from '@angular/core'; import { timer, Subscription } from 'rxjs'; @Directive({ selector: '[appLongPress]' }) export class LongPressDirective implements OnInit { timerSub: Subscription; @Input() delay: number; @Output() longPressed: EventEmitter<any> = new EventEmitter(); constructor( private elementRef: ElementRef<HTMLElement> ) { } ngOnInit() { const isTouch = ('ontouchstart' in document.documentElement); const element = this.elementRef.nativeElement; element.onpointerdown = (ev) => { this.timerSub = timer(this.delay).subscribe(() => { this.longPressed.emit(ev); }); }; element.onpointerup = () => { this.unsub(); }; element.onpointercancel = () => { this.unsub(); }; if (isTouch) { element.onpointerleave = () => { this.unsub(); }; } } private unsub() { if (this.timerSub && !this.timerSub.closed) { this.timerSub.unsubscribe(); } } }
It tries not to do unnecessary onpointerleave event handler on non touch devices, ... I'm not sure if it's even necessary for the touch devices because onpointercancel seems to fire consistently to clear the timer.
Result: pull to refresh works now and also custom ripple effects on the elements with the directive π
I was already going to use hammerjs again, but it works like a charm. Thanks mate
Any news on those builtin gestures?
I settled down on this solution because it plays really nice with Gestures, and that's because it doesn't use Gestures:
import { EventEmitter, Directive, OnInit, Output, Input, ElementRef } from '@angular/core'; import { timer, Subscription } from 'rxjs'; @Directive({ selector: '[appLongPress]' }) export class LongPressDirective implements OnInit { timerSub: Subscription; @Input() delay: number; @Output() longPressed: EventEmitter<any> = new EventEmitter(); constructor( private elementRef: ElementRef<HTMLElement> ) { } ngOnInit() { const isTouch = ('ontouchstart' in document.documentElement); const element = this.elementRef.nativeElement; element.onpointerdown = (ev) => { this.timerSub = timer(this.delay).subscribe(() => { this.longPressed.emit(ev); }); }; element.onpointerup = () => { this.unsub(); }; element.onpointercancel = () => { this.unsub(); }; if (isTouch) { element.onpointerleave = () => { this.unsub(); }; } } private unsub() { if (this.timerSub && !this.timerSub.closed) { this.timerSub.unsubscribe(); } } }
It tries not to do unnecessary onpointerleave event handler on non touch devices, ... I'm not sure if it's even necessary for the touch devices because onpointercancel seems to fire consistently to clear the timer.
Result: pull to refresh works now and also custom ripple effects on the elements with the directive π
It works well except that it still triggers a click event after the long press event which causes conflicts when you need to handle both events on the same element.
Feature Request
Ionic version: ionic v4 ionic/angular: 4.7.1 ionic-cli: 5.2.5
Describe the Feature Request Since Ionic 4 there are no more touch gestures like press (long press). For a mobile framework, this is a very important part. This have to be an official supported feature in such a UI. The developer of Ionic should not hack for this feature. Like this or this.
Describe Preferred Solution Ionic should provide this feature in an official way. No hacks. No polyfills. I can not say which solution would be the best. Maybe use Angular with Hammerjs. Or build an own touch lib.
Related Code Ionic 3:
In Ionic 4 this is not possible anymore. π’ Or did I miss something? Any questions? I'm glad if I can help.