UNPKG

@ciri/ngx-carousel

Version:
921 lines (911 loc) 28.8 kB
import { Directive, TemplateRef, InjectionToken, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, ChangeDetectorRef, Inject, ContentChild, EventEmitter, forwardRef, Renderer2, Input, Output, ViewChild, ContentChildren, NgModule } from '@angular/core'; import { Observable, Subject, BehaviorSubject, timer, interval } from 'rxjs'; import { takeUntil, startWith, debounceTime, skip, filter, distinctUntilChanged } from 'rxjs/operators'; import { animationFrame } from 'rxjs/internal/scheduler/animationFrame'; import { DomSanitizer, HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser'; import ResizeObserver from 'resize-observer-polyfill'; import { CommonModule } from '@angular/common'; import 'hammerjs'; import { __extends } from 'tslib'; /** * @fileoverview added by tsickle * Generated from: lib/lazy-render.directive.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var LazyRenderDirective = /** @class */ (function () { function LazyRenderDirective(content) { this.content = content; } LazyRenderDirective.decorators = [ { type: Directive, args: [{ selector: '[lazyRender]' },] } ]; /** @nocollapse */ LazyRenderDirective.ctorParameters = function () { return [ { type: TemplateRef } ]; }; return LazyRenderDirective; }()); if (false) { /** @type {?} */ LazyRenderDirective.prototype.content; } /** * @fileoverview added by tsickle * Generated from: utils.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * 监听元素大小变动 * \@param target 被监听元素 * @type {?} */ var resize = (/** * @param {?} target * @return {?} */ function (target) { return new Observable((/** * @param {?} observer * @return {?} */ function (observer) { /** @type {?} */ var ro = new ResizeObserver((/** * @param {?} entries * @return {?} */ function (entries) { observer.next(entries); })); ro.observe(target); return (/** * @return {?} */ function () { ro.disconnect(); }); })); }); /** * @param {?} value * @param {?} min * @param {?} max * @return {?} */ function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } /** * @param {?} number * @param {?} start * @param {?} end * @return {?} */ function inRange(number, start, end) { return number >= start && number <= end; } /** @type {?} */ var CAROUSEL = new InjectionToken('CarouselToken'); /** * @fileoverview added by tsickle * Generated from: lib/carousel-item/carousel-item.component.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var CarouselItemComponent = /** @class */ (function () { function CarouselItemComponent(elRef, cdr, sanitizer, parent // 之所以不声明具体类型是因为会警告循环引用,虽然它并未发生 ) { this.elRef = elRef; this.cdr = cdr; this.sanitizer = sanitizer; this.parent = parent; this.rendered = false; this.destroy$ = new Subject(); } Object.defineProperty(CarouselItemComponent.prototype, "isLazyRender", { // 这种方式不兼容 ie11,废弃掉此方案 // @HostBinding('style') // get style() { // return this.sanitizer.bypassSecurityTrustStyle(` // width: ${this.parent.width}px; // `) // } get: // 这种方式不兼容 ie11,废弃掉此方案 // @HostBinding('style') // get style() { // return this.sanitizer.bypassSecurityTrustStyle(` // width: ${this.parent.width}px; // `) // } /** * @return {?} */ function () { return !!this.lazyContent; }, enumerable: true, configurable: true }); Object.defineProperty(CarouselItemComponent.prototype, "shouldRender", { get: /** * @return {?} */ function () { return !this.isLazyRender || this.rendered; }, enumerable: true, configurable: true }); /** * @return {?} */ CarouselItemComponent.prototype.ngOnInit = /** * @return {?} */ function () { }; /** * @return {?} */ CarouselItemComponent.prototype.ngAfterViewInit = /** * @return {?} */ function () { var _this = this; var _a = (/** @type {?} */ (this.parent)), active$ = _a.active$, cache = _a.cache, offset = _a.lazyRenderOffset; active$.pipe(takeUntil(this.destroy$)).subscribe((/** * @param {?} index * @return {?} */ function (index) { _this.rendered = (cache && _this.rendered) || inRange(_this.index, index - offset, index + offset); _this.cdr.markForCheck(); })); }; /** * @return {?} */ CarouselItemComponent.prototype.ngOnDestroy = /** * @return {?} */ function () { this.destroy$.next(); this.destroy$.complete(); }; CarouselItemComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-carousel-item', template: "<ng-container *ngIf=\"shouldRender\" [ngTemplateOutlet]=\"lazyContent && lazyContent.content\">\n <ng-content></ng-content>\n</ng-container>\n", encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.ngx-carousel__item]': "true" }, styles: [".ngx-carousel__item{display:inline-block;vertical-align:top}.ngx-carousel__item.pre-mirror-node{position:absolute;left:0;transform:translateX(-100%)}.ngx-carousel__item.post-mirror-node{position:absolute;right:0;transform:translateX(100%)}"] }] } ]; /** @nocollapse */ CarouselItemComponent.ctorParameters = function () { return [ { type: ElementRef }, { type: ChangeDetectorRef }, { type: DomSanitizer }, { type: undefined, decorators: [{ type: Inject, args: [CAROUSEL,] }] } ]; }; CarouselItemComponent.propDecorators = { lazyContent: [{ type: ContentChild, args: [LazyRenderDirective, { static: false },] }] }; return CarouselItemComponent; }()); if (false) { /** @type {?} */ CarouselItemComponent.prototype.lazyContent; /** @type {?} */ CarouselItemComponent.prototype.index; /** @type {?} */ CarouselItemComponent.prototype.rendered; /** * @type {?} * @private */ CarouselItemComponent.prototype.destroy$; /** @type {?} */ CarouselItemComponent.prototype.elRef; /** * @type {?} * @private */ CarouselItemComponent.prototype.cdr; /** * @type {?} * @private */ CarouselItemComponent.prototype.sanitizer; /** * @type {?} * @private */ CarouselItemComponent.prototype.parent; } /** * @fileoverview added by tsickle * Generated from: lib/carousel/carousel.component.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var CarouselComponent = /** @class */ (function () { function CarouselComponent(renderer, hostElRef, cdr) { this.renderer = renderer; this.hostElRef = hostElRef; this.cdr = cdr; /** * 是否开启无缝模式 */ this.loop = false; /** * 切换速度(ms) */ this.speed = 300; /** * 自动轮播时间间隔,0 代表关闭自动轮播 */ this.autoplay = 0; /** * 是否跟随手指滑动,设为 false 代表只在松手后进行移动判断 */ this.followFinger = true; /** * 是否允许手动滑动,设为 false 代表只能通过 api 翻页 */ this.allowTouchMove = true; /** * 默认激活项 */ this.initialIndex = 0; /** * lazyRender 模式下预渲染个数,1 代表左右多渲染一个,2 代表左右多渲染两个,... */ this.lazyRenderOffset = 0; /** * 是否缓存 lazyRender 模式下渲染过的 item,不从 dom 树中删除 */ this.cache = false; /** * 索引变动时触发 */ this.indexChange = new EventEmitter(); this.active$ = new BehaviorSubject(null); this.destroy$ = new Subject(); this.percent = 0; // 手指滑动距离所占宽度总和百分比 // 手指滑动距离所占宽度总和百分比 this.offset = 0; // 偏移量(%) // 偏移量(%) this.animating = false; // 是否处于过渡效果中 } Object.defineProperty(CarouselComponent.prototype, "active", { get: /** * @return {?} */ function () { return this.active$.value; }, enumerable: true, configurable: true }); Object.defineProperty(CarouselComponent.prototype, "count", { get: /** * @return {?} */ function () { return (this.items || []).length; }, enumerable: true, configurable: true }); Object.defineProperty(CarouselComponent.prototype, "viewport", { get: /** * @return {?} */ function () { return this.hostElRef.nativeElement; }, enumerable: true, configurable: true }); Object.defineProperty(CarouselComponent.prototype, "width", { get: /** * @return {?} */ function () { return this.viewport.offsetWidth; }, enumerable: true, configurable: true }); Object.defineProperty(CarouselComponent.prototype, "canMove", { get: /** * @return {?} */ function () { return this.allowTouchMove && !this.animating; }, enumerable: true, configurable: true }); Object.defineProperty(CarouselComponent.prototype, "data", { get: /** * @return {?} */ function () { return { active: this.active, count: this.count, offset: this.offset, animating: this.animating, atFirst: this.active === 0, atLast: this.active === this.count - 1 }; }, enumerable: true, configurable: true }); /** * @return {?} */ CarouselComponent.prototype.ngOnInit = /** * @return {?} */ function () { }; /** * @return {?} */ CarouselComponent.prototype.ngAfterViewInit = /** * @return {?} */ function () { var _this = this; this.items.changes .pipe(takeUntil(this.destroy$), startWith(null), debounceTime(0, animationFrame)) .subscribe((/** * @return {?} */ function () { _this.init(); })); this.active$ .pipe(takeUntil(this.destroy$), skip(1), filter((/** * @param {?} v * @return {?} */ function (v) { return v !== null && inRange(v, 0, _this.count - 1); })), distinctUntilChanged()) .subscribe((/** * @param {?} res * @return {?} */ function (res) { _this.indexChange.emit(res); _this.cdr.markForCheck(); })); // resize 功能待开发 // resize(this.viewport) // .pipe(takeUntil(this.destroy$), debounceTime(0, animationFrame)) // .subscribe(() => { // // this.updateWidth() // // this.goTo(this.active, true) // }) }; /** * @return {?} */ CarouselComponent.prototype.ngOnDestroy = /** * @return {?} */ function () { this.destroy$.next(); this.destroy$.complete(); }; /** * @param {?} e * @return {?} */ CarouselComponent.prototype.onPanStart = /** * @param {?} e * @return {?} */ function (e) { this.stopAutoplay(); }; /** * @param {?} e * @return {?} */ CarouselComponent.prototype.onPanMove = /** * @param {?} e * @return {?} */ function (e) { if (!this.canMove) { return; } /** @type {?} */ var deltaX = this.getSafeDeltaX(e.deltaX); this.percent = ((100 / this.count) * deltaX) / this.width; if (this.followFinger) { /** @type {?} */ var offset = this.percent - (100 / this.count) * this.active; this.move(offset, true); } }; /** * @param {?} e * @return {?} */ CarouselComponent.prototype.onPanEnd = /** * @param {?} e * @return {?} */ function (e) { if (!this.canMove) { return; } // 轻拂或者滑动距离大于等于一个节点宽度的 50% 才进行跳转 /** @type {?} */ var newActive = this.active; /** @type {?} */ var isSwipeLeft = e.direction === Hammer.DIRECTION_LEFT && e.velocityX < -0.3; /** @type {?} */ var isSwipeRight = e.direction === Hammer.DIRECTION_RIGHT && e.velocityX > 0.3; if (isSwipeLeft || this.percent <= -50 / this.count) { newActive++; } else if (isSwipeRight || this.percent >= 50 / this.count) { newActive--; } this.goTo(newActive); this.startAutoplay(); }; /** * @param {?=} target * @param {?=} immediate * @return {?} */ CarouselComponent.prototype.goTo = /** * @param {?=} target * @param {?=} immediate * @return {?} */ function (target, immediate) { var _this = this; if (target === void 0) { target = 0; } if (immediate === void 0) { immediate = false; } if (this.animating) { return; } /** @type {?} */ var active = this.getSafeActive(target); /** @type {?} */ var realActive = this.getRealActive(active); this.active$.next(realActive); // 到达第一个或最后一个时更新镜像节点 if (this.loop && (realActive === 0 || realActive === this.count - 1)) { this.handleMirrorNodes(); } this.animating = true; this.move(-(100 / this.count) * active, immediate).subscribe((/** * @return {?} */ function () { _this.animating = false; if (active === -1 || active === _this.count) { _this.goTo(realActive, true); } })); }; /** * @return {?} */ CarouselComponent.prototype.prev = /** * @return {?} */ function () { this.goTo(this.active - 1); }; /** * @return {?} */ CarouselComponent.prototype.next = /** * @return {?} */ function () { this.goTo(this.active + 1); }; /** * @private * @return {?} */ CarouselComponent.prototype.init = /** * @private * @return {?} */ function () { var _this = this; if (this.items.length === 0) { return; } this.items.forEach((/** * @param {?} el * @param {?} index * @return {?} */ function (el, index) { el.index = index; _this.renderer.setStyle(el.elRef.nativeElement, 'width', _this.width + "px"); })); this.goTo(this.getSafeActive(this.initialIndex, true), true); this.startAutoplay(); }; /** * @private * @param {?} deltaX * @return {?} */ CarouselComponent.prototype.getSafeDeltaX = /** * @private * @param {?} deltaX * @return {?} */ function (deltaX) { /** @type {?} */ var w = this.width; return clamp(deltaX, -w, w); }; /** * @private * @param {?} active * @param {?=} strict * @return {?} */ CarouselComponent.prototype.getSafeActive = /** * @private * @param {?} active * @param {?=} strict * @return {?} */ function (active, strict) { if (strict === void 0) { strict = false; } /** @type {?} */ var min = this.loop && !strict ? -1 : 0; /** @type {?} */ var max = this.loop && !strict ? this.count : this.count - 1; return clamp(active, min, max); }; // 计算真实索引 // 由于 loop 模式下拷贝了俩节点,所以 active 有误差 // 假设有三个节点,那么 active 非 loop 模式下为 0 ~ 2,loop 模式下为 -1 ~ 3 // 计算真实索引 // 由于 loop 模式下拷贝了俩节点,所以 active 有误差 // 假设有三个节点,那么 active 非 loop 模式下为 0 ~ 2,loop 模式下为 -1 ~ 3 /** * @private * @param {?} active * @return {?} */ CarouselComponent.prototype.getRealActive = // 计算真实索引 // 由于 loop 模式下拷贝了俩节点,所以 active 有误差 // 假设有三个节点,那么 active 非 loop 模式下为 0 ~ 2,loop 模式下为 -1 ~ 3 /** * @private * @param {?} active * @return {?} */ function (active) { return (active + this.count) % this.count; }; // loop 模式下首尾拷贝一个节点,模拟无缝轮播 // 0 1 2 => 2 0 1 2 0 // TODO: 也许能找到一个不用手动复制 dom,并且可以自动更新内容的方式 // loop 模式下首尾拷贝一个节点,模拟无缝轮播 // 0 1 2 => 2 0 1 2 0 // TODO: 也许能找到一个不用手动复制 dom,并且可以自动更新内容的方式 /** * @private * @return {?} */ CarouselComponent.prototype.handleMirrorNodes = // loop 模式下首尾拷贝一个节点,模拟无缝轮播 // 0 1 2 => 2 0 1 2 0 // TODO: 也许能找到一个不用手动复制 dom,并且可以自动更新内容的方式 /** * @private * @return {?} */ function () { /** @type {?} */ var trackEl = this.track.nativeElement // 清理镜像节点 ; // 清理镜像节点 try { this.renderer.removeChild(trackEl, this.preMirrorNode); this.renderer.removeChild(trackEl, this.postMirrorNode); } catch (e) { } var _a = this.items, first = _a.first, last = _a.last; this.preMirrorNode = last.elRef.nativeElement.cloneNode(true); this.postMirrorNode = first.elRef.nativeElement.cloneNode(true); this.renderer.addClass(this.preMirrorNode, 'pre-mirror-node'); this.renderer.addClass(this.postMirrorNode, 'post-mirror-node'); this.renderer.insertBefore(trackEl, this.preMirrorNode, first.elRef.nativeElement); this.renderer.appendChild(trackEl, this.postMirrorNode); }; /** * @private * @param {?} offset * @param {?=} immediate * @return {?} */ CarouselComponent.prototype.move = /** * @private * @param {?} offset * @param {?=} immediate * @return {?} */ function (offset, immediate) { if (immediate === void 0) { immediate = false; } /** @type {?} */ var el = this.track.nativeElement; /** @type {?} */ var oldOffset = this.offset; /** @type {?} */ var newOffset = (this.offset = offset); this.renderer.setStyle(el, 'transition', immediate ? 'none' : "transform " + this.speed + "ms"); this.renderer.setStyle(el, 'transform', "translate3d(" + offset + "%, 0, 0)"); return timer(immediate || newOffset === oldOffset ? 0 : this.speed).pipe(takeUntil(this.destroy$)); }; /** * @private * @return {?} */ CarouselComponent.prototype.startAutoplay = /** * @private * @return {?} */ function () { var _this = this; if (!this.autoplay || this.count <= 1) { return; } this.stopAutoplay(); this.intervalSub = interval(this.autoplay + this.speed) .pipe(takeUntil(this.destroy$)) .subscribe((/** * @return {?} */ function () { /** @type {?} */ var oldActive = _this.active; /** @type {?} */ var newActive = _this.loop ? oldActive + 1 : _this.getRealActive(oldActive + 1); _this.goTo(newActive); })); }; /** * @private * @return {?} */ CarouselComponent.prototype.stopAutoplay = /** * @private * @return {?} */ function () { this.intervalSub && this.intervalSub.unsubscribe(); }; CarouselComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-carousel', template: "<div\n class=\"ngx-carousel__track\"\n #track\n (dragstart)=\"$event.preventDefault()\"\n (panstart)=\"onPanStart($event)\"\n (panmove)=\"onPanMove($event)\"\n (panend)=\"onPanEnd($event)\"\n (pancancel)=\"onPanEnd($event)\"\n>\n <ng-content></ng-content>\n</div>\n\n<div class=\"ngx-carousel__indicator\" *ngIf=\"!indicator\">\n <div\n *ngFor=\"let item of items; let i = index\"\n [class.active]=\"i === active\"\n ></div>\n</div>\n\n<ng-container *ngTemplateOutlet=\"indicator; context: { $implicit: data }\"></ng-container>\n", encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.ngx-carousel]': "true" }, providers: [ { provide: CAROUSEL, useExisting: forwardRef((/** * @return {?} */ function () { return CarouselComponent; })) } ], styles: [".ngx-carousel{position:relative;display:block;overflow:hidden}.ngx-carousel__track{position:relative;display:inline-block;white-space:nowrap}.ngx-carousel__indicator{position:absolute;bottom:10px;width:100%;text-align:center;white-space:nowrap;font-size:0;pointer-events:none}.ngx-carousel__indicator div{display:inline-block;width:6px;height:6px;margin:0 3px;border-radius:50%;background:rgba(0,0,0,.25);pointer-events:auto}.ngx-carousel__indicator div.active{background:rgba(0,0,0,.75)}"] }] } ]; /** @nocollapse */ CarouselComponent.ctorParameters = function () { return [ { type: Renderer2 }, { type: ElementRef }, { type: ChangeDetectorRef } ]; }; CarouselComponent.propDecorators = { loop: [{ type: Input }], speed: [{ type: Input }], autoplay: [{ type: Input }], followFinger: [{ type: Input }], allowTouchMove: [{ type: Input }], indicator: [{ type: Input }], initialIndex: [{ type: Input }], lazyRenderOffset: [{ type: Input }], cache: [{ type: Input }], indexChange: [{ type: Output }], track: [{ type: ViewChild, args: ['track', { static: false },] }], items: [{ type: ContentChildren, args: [CarouselItemComponent,] }] }; return CarouselComponent; }()); if (false) { /** * 是否开启无缝模式 * @type {?} */ CarouselComponent.prototype.loop; /** * 切换速度(ms) * @type {?} */ CarouselComponent.prototype.speed; /** * 自动轮播时间间隔,0 代表关闭自动轮播 * @type {?} */ CarouselComponent.prototype.autoplay; /** * 是否跟随手指滑动,设为 false 代表只在松手后进行移动判断 * @type {?} */ CarouselComponent.prototype.followFinger; /** * 是否允许手动滑动,设为 false 代表只能通过 api 翻页 * @type {?} */ CarouselComponent.prototype.allowTouchMove; /** * 自定义指示器 * @type {?} */ CarouselComponent.prototype.indicator; /** * 默认激活项 * @type {?} */ CarouselComponent.prototype.initialIndex; /** * lazyRender 模式下预渲染个数,1 代表左右多渲染一个,2 代表左右多渲染两个,... * @type {?} */ CarouselComponent.prototype.lazyRenderOffset; /** * 是否缓存 lazyRender 模式下渲染过的 item,不从 dom 树中删除 * @type {?} */ CarouselComponent.prototype.cache; /** * 索引变动时触发 * @type {?} */ CarouselComponent.prototype.indexChange; /** @type {?} */ CarouselComponent.prototype.track; /** @type {?} */ CarouselComponent.prototype.items; /** @type {?} */ CarouselComponent.prototype.active$; /** * @type {?} * @private */ CarouselComponent.prototype.destroy$; /** * @type {?} * @private */ CarouselComponent.prototype.intervalSub; /** * @type {?} * @private */ CarouselComponent.prototype.percent; /** * @type {?} * @private */ CarouselComponent.prototype.offset; /** * @type {?} * @private */ CarouselComponent.prototype.animating; /** * @type {?} * @private */ CarouselComponent.prototype.preMirrorNode; /** * @type {?} * @private */ CarouselComponent.prototype.postMirrorNode; /** * @type {?} * @private */ CarouselComponent.prototype.renderer; /** * @type {?} * @private */ CarouselComponent.prototype.hostElRef; /** * @type {?} * @private */ CarouselComponent.prototype.cdr; } /** * @fileoverview added by tsickle * Generated from: lib/hammer.config.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var HammerConfig = /** @class */ (function (_super) { __extends(HammerConfig, _super); function HammerConfig() { return _super !== null && _super.apply(this, arguments) || this; } /** * @param {?} element * @return {?} */ HammerConfig.prototype.buildHammer = /** * @param {?} element * @return {?} */ function (element) { /** @type {?} */ var mc = new Hammer(element, { inputClass: Hammer.TouchMouseInput }); return mc; }; return HammerConfig; }(HammerGestureConfig)); /** * @fileoverview added by tsickle * Generated from: lib/carousel.module.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ var CarouselModule = /** @class */ (function () { function CarouselModule() { } CarouselModule.decorators = [ { type: NgModule, args: [{ declarations: [CarouselComponent, CarouselItemComponent, LazyRenderDirective], imports: [CommonModule], exports: [CarouselComponent, CarouselItemComponent, LazyRenderDirective], providers: [{ provide: HAMMER_GESTURE_CONFIG, useClass: HammerConfig }] },] } ]; return CarouselModule; }()); /** * @fileoverview added by tsickle * Generated from: public-api.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * Generated from: ciri-ngx-carousel.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ export { CarouselComponent, CarouselItemComponent, CarouselModule, LazyRenderDirective, CAROUSEL as ɵa, HammerConfig as ɵb }; //# sourceMappingURL=ciri-ngx-carousel.js.map