fix(activator): speed up iOS activate

Closes #364
This commit is contained in:
Adam Bradley
2015-10-30 10:17:09 -05:00
parent 7158bcd260
commit 7a63fb1038
5 changed files with 168 additions and 215 deletions

View File

@@ -6,7 +6,7 @@
<ion-content padding style="text-align:center">
<p>
<button>Default</button>
<button><span>Default</span></button>
<button class="activated">Default.activated</button>
</p>

View File

@@ -7,7 +7,7 @@ export class Activator {
this.app = app;
this.queue = [];
this.active = [];
this.clearStateTimeout = 180;
this.clearStateTimeout = 80;
this.clearAttempt = 0;
this.activatedClass = config.get('activatedClass') || 'activated';
this.x = 0;

View File

@@ -7,7 +7,7 @@ $ripple-background-color: rgba(0, 0, 0, 0.1) !default;
md-ripple {
position: absolute;
z-index: -1;
z-index: 0;
display: block;
border-radius: 50%;
@@ -18,7 +18,3 @@ md-ripple {
transform: scale(0.001) translateZ(0);
}
.item > md-ripple {
z-index: 0;
}

View File

@@ -1,244 +1,202 @@
import {Injectable} from 'angular2/angular2';
import {IonicApp} from '../app/app';
import {Config} from '../../config/config';
import {pointerCoord, hasPointerMoved, transitionEnd} from '../../util/dom';
import {Activator} from './activator';
import {RippleActivator} from './ripple';
@Injectable()
export class TapClick {
constructor(app: IonicApp, config: Config) {
const self = this;
self.app = app;
self.pointerTolerance = 4;
self.lastTouch = 0;
self.lastActivated = 0;
self.disableClick = 0;
self.disableClickLimit = 1000;
if (config.get('mdRipple')) {
self.activator = new RippleActivator(app, config);
} else {
self.activator = new Activator(app, config);
}
self.enable( config.get('tapPolyfill') !== false );
function bindDom(type, listener, useCapture) {
document.addEventListener(type, listener, useCapture);
}
bindDom('click', function(ev) {
self.click(ev);
}, true);
bindDom('touchstart', function(ev) {
self.lastTouch = Date.now();
self.pointerStart(ev);
});
bindDom('touchend', function(ev) {
self.lastTouch = Date.now();
self.touchEnd(ev);
});
bindDom('touchcancel', function(ev) {
self.lastTouch = Date.now();
self.pointerCancel(ev);
});
bindDom('mousedown', function(ev) {
self.mouseDown(ev);
}, true);
bindDom('mouseup', function(ev) {
self.mouseUp(ev);
}, true);
let startCoord = null;
let pointerTolerance = 4;
let lastTouch = 0;
let lastActivated = 0;
let disableNativeClickTime = 0;
let disableNativeClickLimit = 1000;
let activator = null;
let isEnabled = false;
let app = null;
let config = null;
let win = null;
let doc = null;
self.pointerMove = function(ev) {
let moveCoord = pointerCoord(ev);
export function initTapClick(windowInstance, documentInstance, appInstance, configInstance) {
win = windowInstance;
doc = documentInstance;
app = appInstance;
config = configInstance;
if ( hasPointerMoved(10, self.start, moveCoord) ) {
self.pointerCancel(ev);
}
};
activator = (config.get('mdRipple') ? new RippleActivator(app, config) : new Activator(app, config));
isEnabled = (config.get('tapPolyfill') !== false);
addListener('click', click, true);
addListener('touchstart', touchStart);
addListener('touchend', touchEnd);
addListener('touchcancel', touchCancel);
addListener('mousedown', mouseDown, true);
addListener('mouseup', mouseUp, true);
}
self.moveListeners = function(shouldAdd) {
document.removeEventListener('touchmove', self.pointerMove);
document.removeEventListener('mousemove', self.pointerMove);
function touchStart(ev) {
touchAction();
pointerStart(ev);
}
if (shouldAdd) {
bindDom('touchmove', self.pointerMove);
bindDom('mousemove', self.pointerMove);
}
};
function touchEnd(ev) {
touchAction();
}
if (isEnabled && startCoord && app.isEnabled()) {
let endCoord = pointerCoord(ev);
enable(shouldEnable) {
this._enabled = shouldEnable;
}
if (!hasPointerMoved(pointerTolerance, startCoord, endCoord)) {
console.debug('create click from touch');
setDisableNativeClick();;
/**
* TODO
* @param {TODO} ev TODO
*/
touchEnd(ev) {
let self = this;
if (self._enabled && self.start && self.app.isEnabled()) {
let endCoord = pointerCoord(ev);
if (!hasPointerMoved(self.pointerTolerance, self.start, endCoord)) {
console.debug('create click');
self.disableClick = Date.now();
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
clickEvent.isIonicTap = true;
ev.target.dispatchEvent(clickEvent);
}
}
self.pointerEnd(ev);
}
/**
* TODO
* @param {TODO} ev TODO
*/
mouseDown(ev) {
if (this.isDisabledClick()) {
console.debug('mouseDown prevent');
ev.preventDefault();
ev.stopPropagation();
} else if (this.lastTouch + 999 < Date.now()) {
this.pointerStart(ev);
let clickEvent = doc.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, win, 1, 0, 0, endCoord.x, endCoord.y, false, false, false, false, 0, null);
clickEvent.isIonicTap = true;
ev.target.dispatchEvent(clickEvent);
}
}
/**
* TODO
* @param {TODO} ev TODO
*/
mouseUp(ev) {
if (this.isDisabledClick()) {
console.debug('mouseUp prevent');
ev.preventDefault();
ev.stopPropagation();
}
pointerEnd(ev);
}
if (this.lastTouch + 999 < Date.now()) {
this.pointerEnd(ev);
}
function touchCancel(ev) {
touchAction();
pointerCancel(ev);
}
function mouseDown(ev) {
if (isDisabledNativeClick()) {
console.debug('mouseDown prevent');
ev.preventDefault();
ev.stopPropagation();
} else if (lastTouch + disableNativeClickLimit < Date.now()) {
pointerStart(ev);
}
}
function mouseUp(ev) {
if (isDisabledNativeClick()) {
console.debug('mouseUp prevent');
ev.preventDefault();
ev.stopPropagation();
}
/**
* TODO
* @param {TODO} ev TODO
*/
pointerStart(ev) {
let activatableEle = this.getActivatableTarget(ev.target);
if (lastTouch + disableNativeClickLimit < Date.now()) {
pointerEnd(ev);
}
}
if (activatableEle) {
this.start = pointerCoord(ev);
function pointerStart(ev) {
let activatableEle = getActivatableTarget(ev.target);
let now = Date.now();
if (this.lastActivated + 100 < now) {
this.activator.downAction(ev, activatableEle, this.start.x, this.start.y);
this.lastActivated = now;
}
if (activatableEle) {
startCoord = pointerCoord(ev);
this.moveListeners(true);
} else {
this.start = null;
let now = Date.now();
if (lastActivated + 100 < now) {
activator.downAction(ev, activatableEle, startCoord.x, startCoord.y);
lastActivated = now;
}
moveListeners(true);
} else {
startCoord = null;
}
}
function pointerEnd(ev) {
activator.upAction();
moveListeners(false);
}
function pointerMove(ev) {
let moveCoord = pointerCoord(ev);
if ( hasPointerMoved(10, startCoord, moveCoord) ) {
pointerCancel(ev);
}
}
function pointerCancel(ev) {
console.debug('pointerCancel from', ev.type);
activator.clearState();
moveListeners(false);
setDisableNativeClick();
}
function moveListeners(shouldAdd) {
removeListener('touchmove', pointerMove);
removeListener('mousemove', pointerMove);
if (shouldAdd) {
addListener('touchmove', pointerMove);
addListener('mousemove', pointerMove);
}
}
function setDisableNativeClick() {
disableNativeClickTime = Date.now() + disableNativeClickLimit;
}
function isDisabledNativeClick() {
return disableNativeClickTime > Date.now();
}
function click(ev) {
let preventReason = null;
if (!app.isEnabled()) {
preventReason = 'appDisabled';
} else if (!ev.isIonicTap && isDisabledNativeClick()) {
preventReason = 'nativeClick';
}
/**
* TODO
*/
pointerEnd(ev) {
this.activator.upAction();
this.moveListeners(false);
if (preventReason !== null) {
console.debug('click prevent', preventReason);
ev.preventDefault();
ev.stopPropagation();
}
}
/**
* TODO
*/
pointerCancel(ev) {
console.debug('pointerCancel')
this.activator.clearState();
this.moveListeners(false);
this.disableClick = Date.now();
function getActivatableTarget(ele) {
let targetEle = ele;
for (let x = 0; x < 4; x++) {
if (!targetEle) break;
if (isActivatable(targetEle)) return targetEle;
targetEle = targetEle.parentElement;
}
return null;
}
isDisabledClick() {
return this.disableClick + this.disableClickLimit > Date.now();
}
/**
* Whether the supplied click event should be allowed or not.
* @param {MouseEvent} ev The click event.
* @return {boolean} True if click event should be allowed, otherwise false.
*/
allowClick(ev) {
if (!this.app.isEnabled()) {
return false;
}
if (!ev.isIonicTap) {
if (this.isDisabledClick()) {
return false;
}
}
function isActivatable(ele) {
if (/^(A|BUTTON)$/.test(ele.tagName)) {
return true;
}
/**
* TODO
* @param {MouseEvent} ev TODO
*/
click(ev) {
if (!this.allowClick(ev)) {
console.debug('click prevent');
ev.preventDefault();
ev.stopPropagation();
}
}
getActivatableTarget(ele) {
let targetEle = ele;
for (let x = 0; x < 4; x++) {
if (!targetEle) break;
if (this.isActivatable(targetEle)) return targetEle;
targetEle = targetEle.parentElement;
}
return null;
}
isActivatable(ele) {
if (/^(A|BUTTON)$/.test(ele.tagName)) {
let attributes = ele.attributes;
for (let i = 0, l = attributes.length; i < l; i++) {
if (/click|tappable/.test(attributes[i].name)) {
return true;
}
let attributes = ele.attributes;
for (let i = 0, l = attributes.length; i < l; i++) {
if (/click|tappable/.test(attributes[i].name)) {
return true;
}
}
return false;
}
return false;
}
function touchAction() {
lastTouch = Date.now();
}
function addListener(type, listener, useCapture) {
doc.addEventListener(type, listener, useCapture);
}
function removeListener(type, listener) {
doc.removeEventListener(type, listener);
}

View File

@@ -16,7 +16,7 @@ import {NavRegistry} from '../components/nav/nav-registry';
import {Translate} from '../translation/translate';
import {ClickBlock} from '../util/click-block';
import {FeatureDetect} from '../util/feature-detect';
import {TapClick} from '../components/tap-click/tap-click';
import {initTapClick} from '../components/tap-click/tap-click';
import * as dom from '../util/dom';
@@ -35,7 +35,7 @@ export function ionicProviders(config) {
config.setPlatform(platform);
let events = new Events();
let tapClick = new TapClick(app, config, window, document);
initTapClick(window, document, app, config);
let featureDetect = new FeatureDetect();
setupDom(window, document, config, platform, featureDetect);
@@ -48,7 +48,6 @@ export function ionicProviders(config) {
provide(IonicApp, {useValue: app}),
provide(Config, {useValue: config}),
provide(Platform, {useValue: platform}),
provide(TapClick, {useValue: tapClick}),
provide(FeatureDetect, {useValue: featureDetect}),
provide(Events, {useValue: events}),
Form,