perf(reorder): hit test refactored

This commit is contained in:
Manu Mtz.-Almeida
2016-06-23 16:00:58 +02:00
parent 99c50a151f
commit 6a52a4a1ec
4 changed files with 90 additions and 58 deletions

View File

@@ -13,12 +13,15 @@ const ITEM_REORDER_ACTIVE = 'reorder-active';
*/
export class ItemReorderGesture {
private selectedItem: Item = null;
private selectedItemEle: HTMLElement = null;
private selectedItemHeight: number;
private offset: Coordinates;
private lastToIndex: number;
private lastYcoord: number;
private lastScrollPosition: number;
private emptyZone: boolean;
private itemHeight: number;
private windowHeight: number;
private events: UIEventManager = new UIEventManager(false);
@@ -26,9 +29,9 @@ export class ItemReorderGesture {
constructor(public list: List) {
let element = this.list.getNativeElement();
this.events.pointerEvents(element,
(ev: any) => this.onDragStart(ev),
(ev: any) => this.onDragMove(ev),
(ev: any) => this.onDragEnd(ev));
this.onDragStart.bind(this),
this.onDragMove.bind(this),
this.onDragEnd.bind(this));
}
private onDragStart(ev: any): boolean {
@@ -37,67 +40,69 @@ export class ItemReorderGesture {
return false;
}
let item = itemEle['$ionComponent'];
let item = <Item> itemEle['$ionComponent'];
if (!item) {
console.error('item does not contain ion component');
return false;
}
ev.preventDefault();
// Preparing state
this.offset = pointerCoord(ev);
this.offset.y += this.list.scrollContent(0);
// Preparing state
this.selectedItem = item;
this.itemHeight = item.height();
this.selectedItemEle = item.getNativeElement();
this.selectedItemHeight = item.height();
this.lastToIndex = item.index;
this.lastYcoord = -100;
this.windowHeight = window.innerHeight - AUTO_SCROLL_MARGIN;
this.lastScrollPosition = this.list.scrollContent(0);
this.offset = pointerCoord(ev);
this.offset.y += this.lastScrollPosition;
item.setCssClass(ITEM_REORDER_ACTIVE, true);
this.list.reorderStart();
return true;
}
private onDragMove(ev: any) {
if (!this.selectedItem) {
let selectedItem = this.selectedItemEle;
if (!selectedItem) {
return;
}
ev.preventDefault();
// Get coordinate
var coord = pointerCoord(ev);
let coord = pointerCoord(ev);
let posY = coord.y;
// Scroll if we reach the scroll margins
let scrollPosition = this.scroll(coord);
let scrollPosition = this.scroll(posY);
// Only perform hit test if we moved at least 30px from previous position
if (Math.abs(posY - this.lastYcoord) > 30) {
let overItem = this.itemForCoord(coord);
if (overItem) {
let toIndex = overItem.index;
if (toIndex !== this.lastToIndex || this.emptyZone) {
let fromIndex = this.selectedItem.index;
this.lastToIndex = toIndex;
this.lastYcoord = posY;
this.emptyZone = false;
this.list.reorderMove(fromIndex, toIndex, this.selectedItemHeight);
}
} else {
this.emptyZone = true;
}
}
// Update selected item position
let ydiff = Math.round(coord.y - this.offset.y + scrollPosition);
this.selectedItem.setCssStyle(CSS.transform, `translateY(${ydiff}px)`);
// Only perform hit test if we moved at least 30px from previous position
if (Math.abs(coord.y - this.lastYcoord) < 30) {
return;
}
// Hit test
let overItem = this.itemForCoord(coord);
if (!overItem) {
this.emptyZone = true;
return;
}
// Move surrounding items if needed
let toIndex = overItem.index;
if (toIndex !== this.lastToIndex || this.emptyZone) {
let fromIndex = this.selectedItem.index;
this.lastToIndex = overItem.index;
this.lastYcoord = coord.y;
this.emptyZone = false;
nativeRaf(() => {
this.list.reorderMove(fromIndex, toIndex, this.itemHeight);
});
}
let ydiff = Math.round(posY - this.offset.y + scrollPosition);
selectedItem.style[CSS.transform] = `translateY(${ydiff}px)`;
}
private onDragEnd(ev: any) {
if (!this.selectedItem) {
private onDragEnd() {
if (!this.selectedItemEle) {
return;
}
@@ -106,17 +111,17 @@ export class ItemReorderGesture {
let fromIndex = this.selectedItem.index;
this.selectedItem.setCssClass(ITEM_REORDER_ACTIVE, false);
this.selectedItem = null;
this.selectedItemEle = null;
this.list.reorderEmit(fromIndex, toIndex);
});
}
private itemForCoord(coord: Coordinates): Item {
let element = <any>document.elementFromPoint(this.offset.x - 100, coord.y);
let element = <HTMLElement>document.elementFromPoint(this.offset.x - 100, coord.y);
if (!element) {
return null;
}
element = closest(element, 'ion-item', true);
if (!element) {
if (element.nodeName !== 'ION-ITEM') {
return null;
}
let item = <Item>(<any>element)['$ionComponent'];
@@ -127,20 +132,22 @@ export class ItemReorderGesture {
return item;
}
private scroll(coord: Coordinates): number {
let scrollDiff = 0;
if (coord.y < AUTO_SCROLL_MARGIN) {
scrollDiff = -SCROLL_JUMP;
} else if (coord.y > this.windowHeight) {
scrollDiff = SCROLL_JUMP;
private scroll(posY: number): number {
if (posY < AUTO_SCROLL_MARGIN) {
this.lastScrollPosition = this.list.scrollContent(-SCROLL_JUMP);
} else if (posY > this.windowHeight) {
this.lastScrollPosition = this.list.scrollContent(SCROLL_JUMP);
}
return this.list.scrollContent(scrollDiff);
return this.lastScrollPosition;
}
/**
* @private
*/
destroy() {
this.onDragEnd();
this.events.unlistenAll();
this.events = null;
this.list = null;

View File

@@ -24,7 +24,7 @@ ion-reorder {
.reorder-enabled {
ion-item {
.item {
will-change: transform;
}
@@ -33,7 +33,14 @@ ion-reorder {
}
}
ion-item.reorder-active {
.reorder-active {
.item-inner {
pointer-events: none;
}
}
.item.reorder-active {
z-index: 4;
box-shadow: 0 0 10px rgba(0, 0, 0, .5);
@@ -41,8 +48,4 @@ ion-item.reorder-active {
transition: none;
pointer-events: none;
ion-reorder {
pointer-events: none;
}
}

View File

@@ -403,6 +403,13 @@ export class Item {
});
}
/**
* @private
*/
getNativeElement() {
return this._elementRef.nativeElement;
}
/**
* @private
*/

View File

@@ -38,7 +38,7 @@ export class List extends Ion {
@Output() ionItemReorder: EventEmitter<{ from: number, to: number }> = new EventEmitter<{ from: number, to: number }>();
constructor(elementRef: ElementRef, private _zone: NgZone, @Optional() private _content: Content) {
constructor(elementRef: ElementRef, private _rendered: Renderer, private _zone: NgZone, @Optional() private _content: Content) {
super(elementRef);
}
@@ -107,6 +107,14 @@ export class List extends Ion {
this._slidingGesture && this._slidingGesture.closeOpened();
}
setCssClass(classname: string, add: boolean) {
this._rendered.setElementClass(this.getNativeElement(), classname, add);
}
reorderStart() {
this.setCssClass('reorder-active', true);
}
/**
* @private
*/
@@ -139,6 +147,7 @@ export class List extends Ion {
reorderReset() {
let children = this.elementRef.nativeElement.children;
let len = children.length;
this.setCssClass('reorder-active', false);
for (let i = 0; i < len; i++) {
children[i].style.transform = '';
}
@@ -155,7 +164,13 @@ export class List extends Ion {
let lastToIndex = this._lastToIndex;
this._lastToIndex = toIndex;
// TODO: I think both loops can be merged into a single one
// but I had no luck last time I tried
/********* DOM READ ********** */
let children = this.elementRef.nativeElement.children;
/********* DOM WRITE ********* */
if (toIndex >= lastToIndex) {
for (var i = lastToIndex; i <= toIndex; i++) {
if (i !== fromIndex) {