Skip to content

Instantly share code, notes, and snippets.

@chriseugenerodriguez
Created April 15, 2023 00:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chriseugenerodriguez/5d82d0e92cb2e82ae650f01d02dc1f14 to your computer and use it in GitHub Desktop.
Save chriseugenerodriguez/5d82d0e92cb2e82ae650f01d02dc1f14 to your computer and use it in GitHub Desktop.
Slider Directive with Pagination, Mobile Swipe Support
@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;
}
}
}
}
}
}
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;
}
}
}
import { Directive, TemplateRef } from '@angular/core';
@Directive({
selector: '[sliderItem]'
})
export class sliderItemDirective {
constructor(public tpl: TemplateRef<any>) {
}
}
<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