Skip to content

Commit

Permalink
feat: speed up draw speed by drawing a subset of category on load (#54)
Browse files Browse the repository at this point in the history
* feat: speed up draw speed by drawing a subset of category on load
* fix: last category not activating when scrolled to bottom
* fix: clears search on anchor click
  • Loading branch information
scttcper authored Apr 13, 2018
1 parent 3545d8e commit a29514d
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 96 deletions.
28 changes: 10 additions & 18 deletions src/lib/emoji/emoji.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export interface EmojiEvent {
</span>
</span>
`,
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false,
})
Expand Down Expand Up @@ -81,9 +80,7 @@ export class EmojiComponent implements OnChanges, Emoji {
this.set
}/sheets-256/${this.sheetSize}.png`

constructor(
private emojiService: EmojiService,
) {}
constructor(private emojiService: EmojiService) {}

ngOnChanges() {
if (!this.emoji) {
Expand All @@ -93,42 +90,37 @@ export class EmojiComponent implements OnChanges, Emoji {
if (!data) {
return this.isVisible = false;
}

const { unified, custom, short_names, imageUrl, obsoleted_by, native } = data;
// const children = this.children;
this.unified = null;
if (custom) {
this.custom = custom;
if (data.custom) {
this.custom = data.custom;
}

if (!unified && !custom) {
if (!data.unified && !data.custom) {
return this.isVisible = false;
}

if (this.tooltip) {
this.title = short_names[0];
this.title = data.short_names[0];
}

if (obsoleted_by && this.hideObsolete) {
if (data.obsoleted_by && this.hideObsolete) {
return this.isVisible = false;
}

if (this.native && unified && native) {
if (this.native && data.unified && data.native) {
// hide older emoji before the split into gendered emoji
this.style = { fontSize: `${this.size}px` };
this.unified = native;
this.unified = data.native;

if (this.forceSize) {
this.style.display = 'inline-block';
this.style.width = `${this.size}px`;
this.style.height = `${this.size}px`;
}
} else if (custom) {
} else if (data.custom) {
this.style = {
width: `${this.size}px`,
height: `${this.size}px`,
display: 'inline-block',
backgroundImage: `url(${imageUrl})`,
backgroundImage: `url(${data.imageUrl})`,
backgroundSize: 'contain',
};
} else {
Expand Down
8 changes: 5 additions & 3 deletions src/lib/picker/anchors.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SVGs from './svgs';
selector: 'emoji-mart-anchors',
template: `
<div class="emoji-mart-anchors">
<ng-container *ngFor="let category of categories; let idx = index">
<ng-template ngFor let-category [ngForOf]="categories" let-idx="index" [ngForTrackBy]="trackByFn">
<span
*ngIf="category.anchor !== false"
[attr.title]="i18n.categories[category.id]"
Expand All @@ -32,10 +32,9 @@ import SVGs from './svgs';
[style.background-color]="color"
></span>
</span>
</ng-container>
</ng-template>
</div>
`,
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false,
})
Expand All @@ -47,6 +46,9 @@ export class AnchorsComponent {
@Output() anchorClick = new EventEmitter<{ category: EmojiCategory, index: number }>();
svgs: any = SVGs;

trackByFn(idx: number, cat: EmojiCategory) {
return cat.id;
}
handleClick($event: Event, index: number) {
this.anchorClick.emit({
category: this.categories[index],
Expand Down
7 changes: 2 additions & 5 deletions src/lib/picker/category.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ import { EmojiFrequentlyService } from './emoji-frequently.service';
</div>
`,
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false,
})
Expand Down Expand Up @@ -133,7 +132,6 @@ export class CategoryComponent implements OnInit, AfterViewInit {
this.parent = this.container!.nativeElement.parentNode.parentNode;
this.memoizeSize();
}

memoizeSize() {
const {
top,
Expand All @@ -150,14 +148,13 @@ export class CategoryComponent implements OnInit, AfterViewInit {
this.maxMargin = height - labelHeight;
}
}

handleScroll(scrollTop: number) {
handleScroll(scrollTop: number): boolean {
let margin = scrollTop - this.top;
margin = margin < this.minMargin ? this.minMargin : margin;
margin = margin > this.maxMargin ? this.maxMargin : margin;

if (margin === this.margin) {
return;
return false;
}

if (!this.hasStickyPosition) {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/picker/picker.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</emoji-mart-anchors>
</div>
<emoji-search
#searchRef
[i18n]="i18n"
(search)="handleSearch($event)"
(enterKey)="handleEnterKey($event)"
Expand All @@ -29,7 +30,7 @@
(scroll)="handleScroll()"
>
<div emoji-category
*ngFor="let category of categories; let idx = index; trackBy: categoryTrack"
*ngFor="let category of activeCategories; let idx = index; trackBy: categoryTrack"
#categoryRef
[id]="category.id"
[name]="category.name"
Expand Down
115 changes: 49 additions & 66 deletions src/lib/picker/picker.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Expand All @@ -22,6 +23,7 @@ import {
import { CategoryComponent } from './category.component';
import { EmojiFrequentlyService } from './emoji-frequently.service';
import { PreviewComponent } from './preview.component';
import { SearchComponent } from './search.component';
import { measureScrollbar } from './utils';


Expand Down Expand Up @@ -58,7 +60,10 @@ export class PickerComponent implements OnInit, AfterViewInit {
@Input() emoji = 'department_store';
@Input() color = '#ae65c5';
@Input() hideObsolete = true;
@Input() categories: any[] = [];
/** all categories */
@Input() categories: EmojiCategory[] = [];
/** used to temporarily draw categories */
@Input() activeCategories: EmojiCategory[] = [];
@Input() set: Emoji['set'] = 'apple';
@Input() skin: Emoji['skin'] = 1;
@Input() native: Emoji['native'] = false;
Expand All @@ -76,11 +81,13 @@ export class PickerComponent implements OnInit, AfterViewInit {
@Output() emojiSelect = new EventEmitter<any>();
@ViewChild('scrollRef') private scrollRef?: ElementRef;
@ViewChild('previewRef') private previewRef?: PreviewComponent;
@ViewChild('searchRef') private searchRef?: SearchComponent;
@ViewChildren('categoryRef')
private categoryRefs?: QueryList<CategoryComponent>;
scrollHeight = 0;
clientHeight = 0;
selected?: string;
nextScroll?: string;
scrollTop?: number;
firstRender = true;
recent?: string[];
Expand Down Expand Up @@ -114,7 +121,10 @@ export class PickerComponent implements OnInit, AfterViewInit {
this.set
}/sheets-256/${this.sheetSize}.png`

constructor(private frequently: EmojiFrequentlyService) {}
constructor(
private ref: ChangeDetectorRef,
private frequently: EmojiFrequentlyService,
) {}

ngOnInit() {
// measure scroll
Expand Down Expand Up @@ -150,12 +160,7 @@ export class PickerComponent implements OnInit, AfterViewInit {
});
}

for (
let categoryIndex = 0;
categoryIndex < allCategories.length;
categoryIndex++
) {
const category = allCategories[categoryIndex];
for (const category of allCategories) {
const isIncluded =
this.include && this.include.length
? this.include.indexOf(category.id) > -1
Expand Down Expand Up @@ -212,12 +217,17 @@ export class PickerComponent implements OnInit, AfterViewInit {

this.categories.unshift(this.SEARCH_CATEGORY);
this.selected = this.categories.filter(category => category.first)[0].name;
}

this.activeCategories = this.categories.slice(0, 3);
setTimeout(() => {
this.activeCategories = this.categories;
this.ref.markForCheck();
this.updateCategoriesSize();
});
}
ngAfterViewInit() {
this.updateCategoriesSize();
}

updateCategoriesSize() {
this.categoryRefs!.forEach(component => component.memoizeSize());

Expand All @@ -227,90 +237,63 @@ export class PickerComponent implements OnInit, AfterViewInit {
this.clientHeight = target.clientHeight;
}
}

handleAnchorClick($event: { category: EmojiCategory; index: number }) {
const component = this.categoryRefs!.find(n => n.id === $event.category.id);
let scrollToComponent = null;

scrollToComponent = () => {
if (component) {
let { top } = component;

if ($event.category.first) {
top = 0;
} else {
top += 1;
}
this.scrollRef!.nativeElement.scrollTop = top;
}
};

if (this.SEARCH_CATEGORY.emojis) {
// this.handleSearch(null);
// this.search.clear();
this.handleSearch(null);
this.searchRef!.clear();
}
if (component) {
let { top } = component;

window.requestAnimationFrame(scrollToComponent);
} else {
scrollToComponent();
if ($event.category.first) {
top = 0;
} else {
top += 1;
}
this.scrollRef!.nativeElement.scrollTop = top;
}
this.selected = $event.category.name;
this.nextScroll = $event.category.name;
}
categoryTrack(index: number, item: any) {
return item.id;
}
handleScroll() {
if (this.nextScroll) {
this.selected = this.nextScroll;
this.nextScroll = undefined;
return;
}
if (!this.scrollRef) {
return;
}

let activeCategory = null;
let scrollTop;

if (this.SEARCH_CATEGORY.emojis) {
activeCategory = this.SEARCH_CATEGORY;
} else {
const target = this.scrollRef.nativeElement;
scrollTop = target.scrollTop;
const scrollingDown = scrollTop > (this.scrollTop || 0);
let minTop = 0;

for (let i = 0, l = this.categories.length; i < l; i++) {
const ii = scrollingDown ? this.categories.length - 1 - i : i;
const category = this.categories[ii];
const component = this.categoryRefs!.find(n => n.id === category.id);

if (component) {
const active = component.handleScroll(scrollTop);

if (!minTop || component.top < minTop) {
if (component.top > 0) {
minTop = component.top;
}
}

if (active && !activeCategory) {
// check scroll is not at bottom
if (target.scrollHeight - target.scrollTop !== this.clientHeight) {
for (const category of this.categories) {
const component = this.categoryRefs!.find(n => n.id === category.id);
const active = component!.handleScroll(target.scrollTop);
if (active) {
activeCategory = category;
}
}
}

if (scrollTop < minTop) {
activeCategory = this.categories.filter(
category => !(category.anchor === false),
)[0];
} else if (scrollTop + this.clientHeight >= this.scrollHeight) {
} else {
// scrolled to bottom activate last category
activeCategory = this.categories[this.categories.length - 1];
}
}

this.scrollTop = target.scrollTop;
}
if (activeCategory) {
const { name: categoryName } = activeCategory;

if (this.selected !== categoryName) {
this.selected = categoryName;
}
this.selected = activeCategory.name;
}

this.scrollTop = scrollTop;
}
handleSearch($emojis: any) {
this.SEARCH_CATEGORY.emojis = $emojis;
Expand Down
6 changes: 3 additions & 3 deletions src/lib/picker/search.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Expand All @@ -21,8 +20,6 @@ import { EmojiSearch } from './emoji-search.service';
/>
</div>
`,
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false,
})
export class SearchComponent implements AfterViewInit {
Expand All @@ -45,6 +42,9 @@ export class SearchComponent implements AfterViewInit {
this.inputRef.nativeElement.focus();
}
}
clear() {
this.query = '';
}
handleEnterKey($event: Event) {
this.enterKey.emit($event);
$event.preventDefault();
Expand Down

0 comments on commit a29514d

Please sign in to comment.