Created
April 15, 2023 00:04
-
-
Save chriseugenerodriguez/5d82d0e92cb2e82ae650f01d02dc1f14 to your computer and use it in GitHub Desktop.
Slider Directive with Pagination, Mobile Swipe Support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import "../../../../assets/css/abstracts/_mixins.scss"; | |
@import "../../../../assets/css/abstracts/_variables.scss"; | |
:host { | |
position: relative; | |
display: block; | |
.slider-wrapper { | |
overflow: hidden; | |
&__inner { | |
@include transition(margin 400ms ease); | |
&__item { | |
display: inline-block; | |
padding: 0 10px; | |
} | |
} | |
&__arrows { | |
@include transform(translateY(-50%)); | |
position: absolute; | |
left: 0; | |
right: 0; | |
top: 50%; | |
margin-top: -16px; | |
height: 38px; | |
i { | |
@include font-size(20px); | |
margin: 0; | |
cursor: pointer; | |
padding: 9px 15px; | |
background: #eee; | |
border-radius: 20px; | |
height: 38px; | |
width: 38px; | |
text-align: center; | |
display: none; | |
&:hover { | |
background: #ddd; | |
} | |
&.active { | |
display: block; | |
} | |
} | |
&-left { | |
position: absolute; | |
left: 0px; | |
} | |
&-right { | |
position: absolute; | |
right: 0px; | |
} | |
} | |
&__pag { | |
padding-top: 24px; | |
padding-bottom: 16px; | |
text-align: center; | |
&-item { | |
display: inline-block; | |
margin: 0 2px; | |
vertical-align: top; | |
padding: 6px; | |
i { | |
@include border-radius(100%); | |
cursor: pointer; | |
background: #9d9fa2; | |
width: 6px; | |
height: 6px; | |
display: block; | |
&.active { | |
cursor: inherit; | |
background: #333; | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
AfterViewInit, Component, ContentChildren, Directive, ElementRef, | |
Input, QueryList, ViewChild, SimpleChanges, ViewChildren, Renderer2, ChangeDetectionStrategy, | |
ChangeDetectorRef, HostListener | |
} from '@angular/core'; | |
import { sliderItemDirective } from './slider.directive'; | |
// ENV | |
import { environment } from '../../../../environments/environment'; | |
@Directive({ | |
selector: '.slider-wrapper__inner__item' | |
}) | |
export class sliderItemElement { | |
} | |
@Component({ | |
selector: 'slider', | |
exportAs: 'slider', | |
templateUrl: 'slider.html', | |
styleUrls: ['slider.component.scss'], | |
changeDetection: ChangeDetectionStrategy.OnPush | |
}) | |
export class SliderComponent implements AfterViewInit { | |
// CONTAINER | |
@ViewChild('ul', { static: false }) UL: ElementRef; | |
// ITEMS | |
@ContentChildren(sliderItemDirective) items: QueryList<sliderItemDirective>; | |
@ViewChildren(sliderItemElement, { read: ElementRef }) private itemsElements: QueryList<ElementRef>; | |
// OPTIONS | |
@Input() showC: any = true; | |
@Input() showP: any = true; | |
@Input() count: number; | |
@Input() multiple: boolean; | |
@Input() touchEnabled = false; | |
// CLICK HANDLERS | |
@ViewChild('left', { static: false }) leftC: ElementRef; | |
@ViewChild('right', { static: false }) rightC: ElementRef; | |
// CALCULATIONS | |
length: number; | |
innerItemWidth: number; | |
totalWidth: number; | |
width: number; | |
marginLeft: number; | |
currentpag: number; | |
totalpag: number; | |
// x0: any; | |
// ARROWS | |
left: boolean; | |
right: boolean; | |
constructor(private ref: ChangeDetectorRef, private el: ElementRef, private renderer: Renderer2) { | |
this.currentpag = 0; | |
} | |
@HostListener('window:resize', ['$event']) | |
onResize(event) { | |
// this.itemsElements.changes.subscribe( | |
// r => this.calculateItems() | |
// ); | |
this.calculateItems(); | |
// RESET | |
this.marginLeft = 0; | |
this.currentpag = 0; | |
this._movePag(this.marginLeft); | |
this._calculatePag(); | |
} | |
ngAfterViewInit() { | |
if (this.showP === 'false') { | |
this.showP = false; | |
} | |
if (this.showC === 'false') { | |
this.showC = false; | |
} | |
this.calculateItems(); | |
this.itemsElements.changes.subscribe( | |
r => this.calculateItems() | |
); | |
this.integrateHammerSwipe(); | |
this.ref.detach(); | |
} | |
calculateItems() { | |
this.multiple = this.el.nativeElement.getAttribute('multiple') == null ? false : true; | |
this.count = this.el.nativeElement.getAttribute('count') == null ? 1 : this.el.nativeElement.getAttribute('count'); | |
// CONTAINER | |
this.width = this.el.nativeElement.offsetWidth; | |
// ITEMS | |
this.innerItemWidth = (this.width) / (this.count); | |
this.itemsElements['_results'].forEach(r => { | |
this.renderer.setStyle(r.nativeElement, 'width', this.innerItemWidth + 'px'); | |
}); | |
this._resetPag(); | |
} | |
_rightHandler = this.carouselRight.bind(this); | |
_leftHandler = this.carouselLeft.bind(this); | |
carouselLeft() { | |
this.marginLeft += this.width; | |
this.currentpag -= 1; | |
this._movePag(this.marginLeft); | |
this._calculatePag(); | |
} | |
carouselRight() { | |
this.marginLeft -= this.width; | |
this.currentpag += 1; | |
this._movePag(this.marginLeft); | |
this._calculatePag(); | |
} | |
clickPag(i) { | |
if (i !== this.currentpag) { | |
if (this.currentpag < i) { | |
this.carouselRight() | |
} | |
if (this.currentpag > i) { | |
this.carouselLeft() | |
} | |
} | |
this.ref.detectChanges(); | |
} | |
private _getPagination(i) { | |
if (this.multiple) { | |
let a = i.length / this.count | |
if (a <= 1) { | |
this.totalpag = 0; | |
} else { | |
this.totalpag = Math.ceil(a); | |
} | |
} else { | |
this.totalpag = Math.ceil(i.length) | |
} | |
return [...Array(this.totalpag)]; | |
} | |
private _calculatePag() { | |
if (!this.leftC || !this.rightC) { | |
return false; | |
} | |
if (Math.abs(this.marginLeft) < this.totalWidth) { | |
this.renderer.addClass(this.rightC.nativeElement, 'active'); | |
this.rightC.nativeElement.addEventListener('click', this._rightHandler); | |
if (Math.abs(this.marginLeft) !== 0) { | |
this.renderer.addClass(this.leftC.nativeElement, 'active'); | |
this.leftC.nativeElement.addEventListener('click', this._leftHandler); | |
} else { | |
this.renderer.removeClass(this.leftC.nativeElement, 'active'); | |
this.leftC.nativeElement.removeEventListener('click', this._leftHandler); | |
} | |
} else { | |
this.renderer.removeClass(this.rightC.nativeElement, 'active'); | |
this.rightC.nativeElement.removeEventListener('click', this._rightHandler); | |
if (Math.abs(this.marginLeft) === 0) { | |
this.renderer.removeClass(this.leftC.nativeElement, 'active'); | |
this.leftC.nativeElement.removeEventListener('click', this._leftHandler); | |
} else { | |
this.renderer.addClass(this.leftC.nativeElement, 'active'); | |
this.leftC.nativeElement.addEventListener('click', this._leftHandler); | |
} | |
} | |
let itemsLength: number = this.items.length; | |
let frameCount: number = this.multiple ? (itemsLength / this.count) : itemsLength; | |
if (this.currentpag >= frameCount) { | |
this.renderer.removeClass(this.rightC.nativeElement, 'active'); | |
this.rightC.nativeElement.removeEventListener('click', this._rightHandler); | |
} | |
this.ref.detectChanges(); | |
} | |
private _movePag(i) { | |
this.renderer.setStyle(this.UL.nativeElement, 'margin-left', i + 'px'); | |
this.ref.detectChanges(); | |
} | |
private _resetPag() { | |
this.length = this.itemsElements.length; | |
this.totalWidth = (this.length * this.innerItemWidth) - (this.innerItemWidth * this.count); | |
this.currentpag = 0; | |
this.marginLeft = 0; | |
if (this.length > this.count && this.showC) { | |
this.renderer.addClass(this.rightC.nativeElement, 'active'); | |
this.renderer.removeClass(this.leftC.nativeElement, 'active'); | |
this.rightC.nativeElement.addEventListener('click', this._rightHandler); | |
} | |
// WIDTH | |
this.renderer.setStyle(this.UL.nativeElement, 'width', (this.length * this.innerItemWidth) + 'px'); | |
this.ref.detectChanges(); | |
} | |
integrateHammerSwipe(): boolean | void { | |
if (!this.touchEnabled) { return false; } | |
let el = this.UL.nativeElement; | |
let hammer = new Hammer(el); | |
hammer.on('swipeleft', (ev) => { | |
let itemsLength: number = this.items.length; | |
let frameCount: number = this.multiple ? (itemsLength / this.count) : itemsLength; | |
if (this.currentpag < (frameCount - 1)) { | |
let selectedPage: number = this.currentpag + 1; | |
this.clickPag(selectedPage); | |
} | |
this._formatCurrentPage(); | |
if (!environment.production) { | |
console.log('swipeleft', this.currentpag); | |
} | |
}); | |
hammer.on('swiperight', (ev) => { | |
let itemsLength: number = this.items.length; | |
if (this.currentpag > 0) { | |
let selectedPage: number = this.currentpag - 1; | |
this._formatCurrentPage(); | |
this.clickPag(selectedPage); | |
this._formatCurrentPage(); | |
if (!environment.production) { | |
console.log('swiperight', this.currentpag); | |
} | |
} | |
}); | |
Hammer.on(el, 'mouseup', (e) => { | |
this._formatCurrentPage(); | |
}); | |
} | |
private _formatCurrentPage(): void { | |
let itemsLength: number = this.items.length; | |
let frameCount: number = this.multiple ? (itemsLength / this.count) : itemsLength; | |
if (this.currentpag < 0) { | |
this.currentpag = 0; | |
} else if (this.currentpag >= frameCount) { | |
this.currentpag = frameCount - 1; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Directive, TemplateRef } from '@angular/core'; | |
@Directive({ | |
selector: '[sliderItem]' | |
}) | |
export class sliderItemDirective { | |
constructor(public tpl: TemplateRef<any>) { | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<section class="slider-wrapper"> | |
<ul #ul class="slider-wrapper__inner"> | |
<li *ngFor="let item of items;" class="slider-wrapper__inner__item"> | |
<ng-container [ngTemplateOutlet]="item.tpl"></ng-container> | |
</li> | |
</ul> | |
<ul *ngIf="showP" class="slider-wrapper__pag"> | |
<ng-container *ngFor="let item of (multiple ? _getPagination(items) : items); let i = index" [attr.data-index]="i"> | |
<li class="slider-wrapper__pag-item"> | |
<i (click)="clickPag(i)" [class.active]="i == currentpag"></i> | |
</li> | |
</ng-container> | |
</ul> | |
<nav *ngIf="showC" class="slider-wrapper__arrows"> | |
<i #left class="fa fa-angle-left slider-wrapper__arrows-left"></i> | |
<i #right class="fa fa-angle-right slider-wrapper__arrows-right"></i> | |
</nav> | |
</section> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment