UNPKG

slider-carousel

Version:

Angular component of the carousel, using the slider as a transition. This is a simple, clean and light alternative. It also does not need dependencies.

556 lines (549 loc) 26.6 kB
import { ElementRef, Component, HostBinding, ComponentFactoryResolver, ApplicationRef, Injector, Injectable, Renderer2, ViewChild, Input, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { DomSanitizer } from '@angular/platform-browser'; import { Observable } from 'rxjs'; class SliderCarouselPreviewComponent { constructor(elRef) { this.elRef = elRef; this.class = true; this.closedClass = false; this.imageUrl = ''; this.loading = true; this.scaleRate = 1; this.modalRef = {}; } get width() { return (this.loading ? 0 : this.img.width * this.scaleRate) + 'px'; } get height() { return (this.loading ? 0 : this.img.height * this.scaleRate) + 'px'; } get windowWidth() { return window.innerWidth - 40; } get windowHeight() { return window.innerHeight - 40; } ngOnInit() { this.imageUrl = this.modalRef.image.safeUrl; this.loadImage(this.modalRef.image.pureUrl); } loadImage(imageUrl) { this.loading = true; this.img = new Image(); this.img.onload = () => { this.loading = false; this.onWindowResize(); }; this.img.src = imageUrl; } onImageChange(image, fileName) { if (!this.loading && this.imageUrl !== image.safeUrl) { this.imageUrl = image.safeUrl; this.loadImage(image.pureUrl); } } onKeydown(event) { event.preventDefault(); event.stopImmediatePropagation(); if (event.keyCode === 27) this.close(); } onWindowResize() { if (!this.loading) { let widthDiff = this.windowWidth - this.img.width; let heightDiff = this.windowHeight - this.img.height; if (widthDiff < 0 || heightDiff < 0) { if (widthDiff < heightDiff) { if (this.img.width > this.windowWidth) { this.scaleRate = this.windowWidth / this.img.width; return; } } else { if (this.img.height > this.windowHeight) { this.scaleRate = this.windowHeight / this.img.height; return; } } } } this.scaleRate = 1; } close() { this.closedClass = true; setTimeout(() => this.modalRef.close(), 500); } } SliderCarouselPreviewComponent.ctorParameters = () => [ { type: ElementRef } ]; SliderCarouselPreviewComponent.decorators = [ { type: Component, args: [{ selector: 'slider-carousel-preview', template: "<section>\n <img [src]=\"imageUrl\" [style.width]=\"width\" [style.height]=\"height\" [style.opacity]=\"loading ? 0 : 1\" />\n <div *ngIf=\"!loading\" class=\"slider-carousel-preview-actions\">\n <button matRipple matTooltipPosition=\"above\" (click)=\"close()\">\n <svg x=\"0px\" y=\"0px\" viewBox=\"0 0 51.976 51.976\">\n <path d=\"M44.373,7.603c-10.137-10.137-26.632-10.138-36.77,0c-10.138,10.138-10.137,26.632,0,36.77s26.632,10.138,36.77,0\n C54.51,34.235,54.51,17.74,44.373,7.603z M36.241,36.241c-0.781,0.781-2.047,0.781-2.828,0l-7.425-7.425l-7.778,7.778\n c-0.781,0.781-2.047,0.781-2.828,0c-0.781-0.781-0.781-2.047,0-2.828l7.778-7.778l-7.425-7.425c-0.781-0.781-0.781-2.048,0-2.828\n c0.781-0.781,2.047-0.781,2.828,0l7.425,7.425l7.071-7.071c0.781-0.781,2.047-0.781,2.828,0c0.781,0.781,0.781,2.047,0,2.828\n l-7.071,7.071l7.425,7.425C37.022,34.194,37.022,35.46,36.241,36.241z\"/>\n </svg>\n </button>\n </div>\n</section>\n<div *ngIf=\"loading\" class=\"slider-carousel-loading\"></div>", host: { '(document:keydown)': 'onKeydown($event)', '(window:resize)': 'onWindowResize()' } },] } ]; SliderCarouselPreviewComponent.ctorParameters = () => [ { type: ElementRef } ]; SliderCarouselPreviewComponent.propDecorators = { class: [{ type: HostBinding, args: ['class.slider-carousel-preview',] }], closedClass: [{ type: HostBinding, args: ['class.closed',] }] }; class Helper { constructor(componentFactoryResolver, appRef, injector) { this.componentFactoryResolver = componentFactoryResolver; this.appRef = appRef; this.injector = injector; } openPreview(data = {}) { let onCloseSubscribe; let object; object = { onClose: new Observable((subscribe) => onCloseSubscribe = subscribe), instance: null }; let componenRef = this.createComponent(SliderCarouselPreviewComponent, data, (data) => { onCloseSubscribe.next(data); }); object.instance = componenRef.instance; return object; } createComponent(component, data, onClose) { const componentRef = this.componentFactoryResolver .resolveComponentFactory(component) .create(this.injector); if (!data) data = {}; data.close = (data) => { this.appRef.detachView(componentRef.hostView); componentRef.destroy(); if (onClose) onClose(data); }; Object.assign(componentRef.instance, { modalRef: data }); this.appRef.attachView(componentRef.hostView); const domElem = componentRef.hostView.rootNodes[0]; document.body.appendChild(domElem); return componentRef; } smoothScroll(element, scroll, duration = 400, direction = 'top') { let start = direction === 'top' ? element.scrollTop : element.scrollLeft; if (scroll < 0) scroll = 0; let distance = (scroll - start) - 77; let startTime = new Date().getTime(); if (!duration) duration = 400; let easeInOutQuart = (time, from, distance, duration) => { if ((time /= duration / 2) < 1) return distance / 2 * time * time * time * time + from; return -distance / 2 * ((time -= 2) * time * time * time - 2) + from; }; let timer = setInterval(() => { const time = new Date().getTime() - startTime, newScroll = easeInOutQuart(time, start, distance, duration); if (time >= duration) { clearInterval(timer); timer = null; } if (element.scrollTo) { if (direction === 'top') element.scrollTo(element.scrollLeft, newScroll); else element.scrollTo(newScroll, element.scrollTop); } else { if (direction === 'top') element.scrollTop = newScroll; else element.scrollLeft = newScroll; } }, 1000 / 60); return timer; } elementIsChild(element, parentElment) { try { while (element && element.tagName.toUpperCase() !== 'BODY') { if (element === parentElment) { return true; } element = element.parentNode; } return false; } catch (_a) { return false; } } isMobileDevice() { return (typeof window.orientation !== "undefined") || (navigator.userAgent.indexOf('IEMobile') !== -1); } } Helper.ctorParameters = () => [ { type: ComponentFactoryResolver }, { type: ApplicationRef }, { type: Injector } ]; Helper.decorators = [ { type: Injectable } ]; Helper.ctorParameters = () => [ { type: ComponentFactoryResolver }, { type: ApplicationRef }, { type: Injector } ]; class SliderCarouselComponent { constructor(sanitizer, renderer, helper) { this.sanitizer = sanitizer; this.renderer = renderer; this.helper = helper; this.class = true; this.images = []; this.preview = true; this.height = '500px'; this.width = '100%'; this.maxWidth = '100%'; this.autoSize = false; this.safeImages = []; this.initialized = false; this.currentImageIndex = 0; this.lastDirectionIsRight = false; this.containerWidth = 0; this.destroyed = false; this.windowResizing = false; this.listeners = []; this.drag = { state: 'none', startOffset: 0, currentOffset: 0, startLeft: 0, currentLeft: 0, }; } get currentImage() { return this.safeImages[this.currentImageIndex]; } get currentIsFisrt() { return this.currentImageIndex === 0; } get currentIsLast() { return this.currentImageIndex === (this.images.length - 1); } get isDragging() { return this.drag.state !== 'none'; } ngOnInit() { } ngOnChanges() { this.prepare(); } prepare() { if (typeof this.height !== 'string') this.height = '500px'; else if (!this.height.endsWith('%') && !this.height.endsWith('px')) this.height += 'px'; if (typeof this.width !== 'string') this.width = '100%'; else if (!this.width.endsWith('%') && !this.width.endsWith('px')) this.width += 'px'; if (typeof this.maxWidth !== 'string') this.maxWidth = '100%'; else if (!this.maxWidth.endsWith('%') && !this.maxWidth.endsWith('px')) this.maxWidth += 'px'; this.initialized = true; this.checkImages(); this.initSlideDragWatching(); this.ngAfterViewChecked(); } ngAfterViewChecked() { if (!this.windowResizing) { let width = 0; if (this.sectionEl && this.sectionEl.nativeElement) width = this.sectionEl.nativeElement.clientWidth; if (width !== this.containerWidth) this.containerWidth = width; } } checkImages() { if (this.images && this.images.length) { if (typeof this.images[0] !== 'string') { this.images.forEach((image) => { if (!image.md) image.md = image.lg; if (!image.sm) image.sm = image.md; }); } else { this.images = this.images.map((image) => { return { lg: image, md: image, sm: image }; }); } this.safeImages = this.images.map((image) => { var _a, _b, _c, _d, _e, _f; let sizes = { lg: { url: ((_a = image.lg) === null || _a === void 0 ? void 0 : _a.startsWith('http')) ? this.sanitizer.bypassSecurityTrustUrl(image.lg) : image.lg, style: ((_b = image.lg) === null || _b === void 0 ? void 0 : _b.startsWith('http')) ? this.sanitizer.bypassSecurityTrustStyle(`url('${image.lg}')`) : `url('${image.lg}')`, pure: image.lg }, md: { url: ((_c = image.md) === null || _c === void 0 ? void 0 : _c.startsWith('http')) ? this.sanitizer.bypassSecurityTrustUrl(image.md) : image.md, style: ((_d = image.md) === null || _d === void 0 ? void 0 : _d.startsWith('http')) ? this.sanitizer.bypassSecurityTrustStyle(`url('${image.md}')`) : `url('${image.md}')`, pure: image.md }, sm: { url: ((_e = image.sm) === null || _e === void 0 ? void 0 : _e.startsWith('http')) ? this.sanitizer.bypassSecurityTrustUrl(image.sm) : image.sm, style: ((_f = image.sm) === null || _f === void 0 ? void 0 : _f.startsWith('http')) ? this.sanitizer.bypassSecurityTrustStyle(`url('${image.sm}')`) : `url('${image.sm}')`, pure: image.sm } }; return sizes; }); } else { this.safeImages = []; } } initSlideDragWatching() { if (this.innerImagesEl && this.innerImagesEl.nativeElement) { this.stopSlideDragWatching(); if (this.helper.isMobileDevice()) this.listeners = [ this.renderer.listen(document.body, 'touchstart', this.onStartDrag.bind(this)), this.renderer.listen(document.body, 'touchmove', this.onDragging.bind(this)), this.renderer.listen(document.body, 'touchend', this.onEndDrag.bind(this)) ]; else this.listeners = [ this.renderer.listen(document.body, 'mousedown', this.onStartDrag.bind(this)), this.renderer.listen(document.body, 'mousemove', this.onDragging.bind(this)), this.renderer.listen(document.body, 'mouseup', this.onEndDrag.bind(this)), ]; } else setTimeout(() => this.initSlideDragWatching(), 200); } stopSlideDragWatching() { if (this.listeners && this.listeners.length) this.listeners.forEach((unListen) => unListen()); } previewImage(image) { if (!this.previewRef) { this.previewRef = this.helper.openPreview({ image: { pureUrl: image.lg.pure, safeUrl: image.lg.url, } }); this.previewRef.onClose.subscribe(() => this.previewRef = null); } } selectImage(imageIndex, event) { if (imageIndex !== this.currentImageIndex) { this.lastDirectionIsRight = imageIndex > this.currentImageIndex; this.currentImageIndex = imageIndex; if (event.target && this.imageListEl && this.imageListEl.nativeElement) this.scrollingToElement(event.target, this.lastDirectionIsRight); } } goPrevImage(salt = 0) { if (this.currentImageIndex > 0) { if (salt) { if ((this.currentImageIndex - salt) >= 0) this.currentImageIndex -= salt + 1; else this.currentImageIndex = 0; } else this.currentImageIndex--; this.lastDirectionIsRight = false; let target = this.imageListEl.nativeElement.children[0].children[this.currentImageIndex]; if (this.imageListEl && this.imageListEl.nativeElement) this.scrollingToElement(target, this.lastDirectionIsRight); } } goNextImage(salt = 0) { if ((this.currentImageIndex + 1) < this.images.length) { if (salt) { if ((this.currentImageIndex + salt + 1) <= (this.images.length - 1)) this.currentImageIndex = salt + 1; else this.currentImageIndex = this.images.length; } else this.currentImageIndex++; this.lastDirectionIsRight = true; let target = this.imageListEl.nativeElement.children[0].children[this.currentImageIndex]; if (this.imageListEl && this.imageListEl.nativeElement) this.scrollingToElement(target, this.lastDirectionIsRight); } } scrollingToElement(element, directionIsRight) { let scrollElement = this.imageListEl.nativeElement; if (scrollElement && scrollElement.scrollWidth > 0) { let blockWidth = element.clientWidth + 14; let scrollLeft = element.offsetLeft - 14; let scrollRight = element.offsetLeft + element.clientWidth + 14; let currentScrollLeft = scrollElement.scrollLeft; let currentScrollRight = scrollElement.scrollLeft + scrollElement.clientWidth; let scroll = .1; if (directionIsRight && (currentScrollRight - scrollRight) < Math.floor(blockWidth / 2)) scroll = currentScrollLeft + blockWidth + 100; else if (!directionIsRight && (scrollLeft - currentScrollLeft) < Math.floor(blockWidth / 2)) scroll = currentScrollLeft - blockWidth; if (scroll !== .1) { if (this.timerSroll) clearInterval(this.timerSroll); this.timerSroll = this.helper.smoothScroll(scrollElement, scroll, 400, 'left'); } } } onKeydown(event) { if (!this.destroyed && !this.isDragging && [37, 39].indexOf(event.keyCode) >= 0) { if (event.keyCode === 37) this.goPrevImage(); else this.goNextImage(); if (this.previewRef) this.previewRef.instance.onImageChange({ pureUrl: this.currentImage.lg.pure, safeUrl: this.currentImage.lg.url, }); } } onWindowResize() { if (!this.destroyed && !this.windowResizing) { this.windowResizing = true; setTimeout(() => { this.containerWidth = this.sectionEl.nativeElement.clientWidth; setTimeout(() => { this.windowResizing = false; setTimeout(() => { if (this.imageListEl && this.imageListEl.nativeElement) { let target = this.imageListEl.nativeElement.children[0].children[this.currentImageIndex]; if (this.imageListEl && this.imageListEl.nativeElement) this.scrollingToElement(target, this.lastDirectionIsRight); } }, 100); }); }); } } onStartDrag(event) { let isTouching = false; if (event.touches) { isTouching = true; event = event.touches[0]; } if (isTouching || event.button === 0) { if (this.helper.elementIsChild(event.target, this.sectionEl.nativeElement)) { this.drag.startLeft = this.innerImagesEl.nativeElement.offsetLeft; this.drag.currentLeft = this.innerImagesEl.nativeElement.offsetLeft; this.drag.startOffset = event.clientX; this.drag.currentOffset = event.clientX; this.drag.state = 'start'; } } } onDragging(event) { if (event.touches) event = event.touches[0]; if (this.drag.state === 'start' || this.drag.state === 'dragging') { this.drag.state = 'dragging'; this.drag.currentOffset = event.clientX; if (this.drag.startOffset !== this.drag.currentOffset) { let draggingRight = this.drag.currentOffset > this.drag.startOffset; let delta = Math.abs(this.drag.currentOffset - this.drag.startOffset) * .8; if (((draggingRight && this.currentIsFisrt) || (!draggingRight && this.currentIsLast)) && delta > Math.floor(this.containerWidth / 3)) delta = Math.floor(this.containerWidth / 3); this.drag.currentLeft = this.drag.startLeft + ((draggingRight ? 1 : -1) * delta); } else this.drag.currentLeft = this.drag.startLeft; } } onEndDrag(event) { let isTouching = false; if (event.touches) { isTouching = true; event = event.touches[0]; } if (isTouching || event.button === 0) { let amountDragged = Math.abs(this.drag.startOffset - this.drag.currentOffset); if (this.drag.state === 'dragging' && amountDragged > 0) setTimeout(() => { this.drag.state = 'none'; let minDreg = 70; if (amountDragged >= minDreg) { let draggingRight = this.drag.currentOffset > this.drag.startOffset; let salts = (amountDragged / this.sectionEl.nativeElement.clientWidth) - 1; if (salts >= 1) salts = Math.floor(salts); else salts = 0; if (draggingRight && !this.currentIsFisrt) this.goPrevImage(salts); else if (!draggingRight && !this.currentIsLast) this.goNextImage(salts); } }); else { this.drag.state = 'none'; } } } ngOnDestroy() { this.destroyed = true; this.stopSlideDragWatching(); } } SliderCarouselComponent.ctorParameters = () => [ { type: DomSanitizer }, { type: Renderer2 }, { type: Helper } ]; SliderCarouselComponent.decorators = [ { type: Component, args: [{ selector: 'slider-carousel', template: "<section #section *ngIf=\"safeImages && safeImages.length\" [style.width]=\"width\" [style.maxWidth]=\"maxWidth\">\n <div *ngIf=\"!windowResizing && containerWidth > 0\" class=\"image-controller animated fadeIn\" [style.maxWidth.px]=\"containerWidth\">\n\n <!-- INNER CONTAINER -->\n <ul #innerImages *ngIf=\"!autoSize\"\n [ngClass]=\"{'dragging-effect': isDragging}\"\n\t\t\t[style.left.px]=\"isDragging ? drag.currentLeft : (-currentImageIndex * containerWidth)\"\n\t\t\t[style.width.px]=\"safeImages.length * containerWidth\"\n [style.height]=\"height\">\n <li *ngFor=\"let image of safeImages; let i = index;\"\n [style.backgroundImage]=\"image.md.style\"\n [style.width.px]=\"containerWidth\"\n [ngClass]=\"{'cursor-pointer': preview, 'is-current': i === currentImageIndex}\"\n (click)=\"!isDragging && previewImage(image)\">\n </li>\n </ul>\n <ul #innerImages *ngIf=\"autoSize\" [ngClass]=\"{'dragging-effect': isDragging}\" [style.left.px]=\"isDragging ? drag.currentLeft : (-currentImageIndex * containerWidth)\">\n <div *ngFor=\"let image of safeImages; let i = index;\" [style.width.px]=\"containerWidth\">\n\t\t\t\t<img [src]=\"image.md.url\" draggable=\"false\" [ngClass]=\"{'cursor-pointer': preview, 'is-current': i === currentImageIndex}\" (click)=\"!isDragging && previewImage(image)\"/>\n </div>\n </ul>\n\n <!-- NAVIGATION BUTTONS -->\n <div (click)=\"goPrevImage()\" class=\"image-controller-prev\" [ngClass]=\"{'disabled': currentImageIndex <= 0}\" role=\"button\">\n <svg viewBox=\"0 0 456 456\">\n <path d=\"M227.996,0C102.081,0,0,102.081,0,227.996c0,125.945,102.081,227.996,227.996,227.996\n\t\t\t\tc125.945,0,227.996-102.051,227.996-227.996C455.992,102.081,353.941,0,227.996,0z M299.435,238.788l-98.585,98.585\n\t\t\t\tc-5.928,5.897-15.565,5.897-21.492,0c-5.928-5.928-5.928-15.595,0-21.492l87.885-87.885l-87.885-87.885\n\t\t\t\tc-5.928-5.928-5.928-15.565,0-21.492s15.565-5.928,21.492,0l98.585,98.585c3.04,2.979,4.469,6.901,4.438,10.792\n\t\t\t\tC303.873,231.918,302.414,235.809,299.435,238.788z\"/>\n </svg> \n </div>\n <div (click)=\"goNextImage()\" class=\"image-controller-next\" [ngClass]=\"{'disabled': (currentImageIndex + 1) >= safeImages.length}\" role=\"button\">\n <svg viewBox=\"0 0 456 456\">\n <path d=\"M227.996,0C102.081,0,0,102.081,0,227.996c0,125.945,102.081,227.996,227.996,227.996\n\t\t\t\tc125.945,0,227.996-102.051,227.996-227.996C455.992,102.081,353.941,0,227.996,0z M299.435,238.788l-98.585,98.585\n\t\t\t\tc-5.928,5.897-15.565,5.897-21.492,0c-5.928-5.928-5.928-15.595,0-21.492l87.885-87.885l-87.885-87.885\n\t\t\t\tc-5.928-5.928-5.928-15.565,0-21.492s15.565-5.928,21.492,0l98.585,98.585c3.04,2.979,4.469,6.901,4.438,10.792\n\t\t\t\tC303.873,231.918,302.414,235.809,299.435,238.788z\"/>\n </svg> \n </div>\n\n </div>\n\n <!-- GALLERY NAVIGATION -->\n <div *ngIf=\"!windowResizing && containerWidth > 0\" #imageList class=\"footer-images animated fadeIn\" [style.maxWidth.px]=\"containerWidth\">\n <ul>\n <li matRipple (click)=\"selectImage(i, $event)\" *ngFor=\"let image of safeImages; let i = index;\"\n [ngClass]=\"{'is-current': i === currentImageIndex}\"\n [style.backgroundImage]=\"image.sm.style\">\n </li>\n </ul>\n </div>\n</section>", host: { '(document:keydown)': 'onKeydown($event)', '(window:resize)': 'onWindowResize()' } },] } ]; SliderCarouselComponent.ctorParameters = () => [ { type: DomSanitizer }, { type: Renderer2 }, { type: Helper } ]; SliderCarouselComponent.propDecorators = { class: [{ type: HostBinding, args: ['class.slider-carousel',] }], sectionEl: [{ type: ViewChild, args: ['section',] }], imageListEl: [{ type: ViewChild, args: ['imageList',] }], innerImagesEl: [{ type: ViewChild, args: ['innerImages',] }], images: [{ type: Input }], preview: [{ type: Input }], height: [{ type: Input }], width: [{ type: Input }], maxWidth: [{ type: Input, args: ['max-width',] }], autoSize: [{ type: Input, args: ['auto-size',] }] }; class SliderCarouselModule { } SliderCarouselModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, ], exports: [ SliderCarouselComponent ], declarations: [ SliderCarouselComponent, SliderCarouselPreviewComponent ], entryComponents: [ SliderCarouselComponent, SliderCarouselPreviewComponent ], providers: [ Helper ] },] } ]; /** * Generated bundle index. Do not edit. */ export { SliderCarouselComponent, SliderCarouselModule, Helper as ɵa, SliderCarouselPreviewComponent as ɵb }; //# sourceMappingURL=slider-carousel.js.map