Давно уже нет ответа на этот вопрос, но я не могу удержаться от другого подхода.
Отказ от ответственности Я украл 4 идеи о великом Нетанеле Базале и его удивительной карусели
Disclamer 2 это длинный, длинный ответ, поэтому для анимации читать, здесь мы собираемся получить
Карусель - это только HTML
<div class="wrap">
<div #carousel></div>
</div>
где div #carousel перемещается влево / вправо с одной анимацией.
Первая идея от Nenatel, которую я позаимствовал, - это анимация. Он имеет только две функции: (я использую несколько переменных для параметризации функций)
@Input() timing = '500ms ease-in';
slideWidth; //width of each slide
numberSlides; //number of slides
offset; //a variable to move the origen of movimient
private transitionCarousel(time: any, slide: number) {
const offset = this.offset - this.slideWidth * this.numberSlides;
const myAnimation: AnimationFactory = this.buildAnimation(offset, time);
this.player = myAnimation.create(this.carousel.nativeElement);
this.player.play();
}
private buildAnimation(offset, time: any) {
return this.builder.build([
animate(time == null ? this.timing : 0, style({ transform: `translateX(${offset}px)` }))
]);
}
Итак, мы звоним на
transitionCarousel(null,4) //to select the slide 4.
Хорошо, я собираюсь предположить, что у меня есть 6 разных изображений, и мы хотим показать 4 одновременно. Есть две критические позиции. Сильфон нашей карусели в разных штатах
a) [0 1 2 3 ] 4 5 6
b) 0 1 [2 3 4 5 ] 6
c) [* * * 0 ]1 2 3 4 5 6
d) 0 1 2 3 4 5 [6 * * * ]
Так, случай а) - это когда слайд = 0, случай б) - когда слайд = 2
Случай с), когда наш слайд 0 находится в 4-м положении, и случай d), когда слайд = 6
Очевидно, что состояния c) и d) создают нам проблему, потому что *
показывает пустые пробелы (а нам не нужны белые пробелы). Итак, почему бы не повторить сначала наши изображения изображения 4, 5 и 6, а в конце изображения 0 1 и 3, так что наши состояния с) и г) становится как
c) [4' 5' 6' 0 ] 1 2 3 4 5 6 0' 1' 2'
d) 4' 5' 6' 0 1 2 3 4 5 [6 0' 1' 2']
Хорошо, мы должны принять во внимание, что случай c) - когда слайд = -3, он идентичен слайду = 4
c) 4' 5' 6' 0 1 2 3 [4 5 6 0']1' 2'
Итак, если после того, как движение упало на слайде меньше 0, мы мгновенно переместимся влево
Другой случай, который нужно учитывать, это d) когда мы нажимаем на правую кнопку. В этом случае нам нужно сделать два движения
d) 4' 5' 6' 0 1 2 3 4 5 [6 0' 1' 2'] //slide=6
--click left button event--
//we move to the slide=-1 before transition
4' 5'[6' 0 1 2 ] 3 4 5 6 0' 1' 2'
//and move to slide=3 in transition
4' 5' 6' 0 1 2 [3 4 5 6 ] 0' 1' 2'
Итак, наша функция transitionCarousel становится похожей на
private transitionCarousel(time: any, slide: number) {
if (slide >= this.slides.length) {
this.transitionCarousel(0, this.currentSlide - this.slidesQuantity)
slide -= this.slidesQuantity;
}
const offset = this.offset - this.slideWidth * slide;
const myAnimation: AnimationFactory = this.buildAnimation(offset, time);
this.player = myAnimation.create(this.carousel.nativeElement);
if (time != 0) {
if (slide < 0) {
this.player.onDone(() => {
this.transitionCarousel(0, this.currentSlide)
})
}
else
this.currentSlide = slide;
} this.player.play();
}
Ну, нам нужна механика для дублирования слайдов. Мы собираемся сделать это, используя структурную директиву (2-я стая Нетанелу Базалу)
@Directive({
selector: '[carouselItem]'
})
export class CarouselDirective implements OnInit {
constructor(
public templateRef: TemplateRef<any>,
public viewContainer: ViewContainerRef) { }
ngOnInit()
{
this.viewContainer.createEmbeddedView(this.templateRef)
}
}
Обратите внимание, что templateRef и viewContainer объявлены как публичные, это позволяет нам копировать шаблон каждого элемента
<div *carouselItem>Hello world</div>
внутри другого.
Наша карусель становится как
<app-carousel #carousel>
<ng-container *ngFor=" let item of items">
<img *carouselItem [src]="item.img" >
</ng-container>
</app-carousel>
где, например
items = [{ img: 'https://picsum.photos/200/200?random=1' },
{ img: 'https://picsum.photos/200/200?random=2' },
{ img: 'https://picsum.photos/200/200?random=3' },
{ img: 'https://picsum.photos/200/200?random=4' },
{ img: 'https://picsum.photos/200/200?random=5' },
{ img: 'https://picsum.photos/200/200?random=6' },
{ img: 'https://picsum.photos/200/200?random=7' },
{ img: 'https://picsum.photos/200/200?random=8' }
];
Я хочу, чтобы моя карусель была масштабируемой, поэтому мне нужно знать, сколько слайдов можно разместить внутри карусели. Конечно, мы можем заключить карусель в div
<div style="width:600px">
<app-carousel #carousel>
....
</app-carousel>
</div>
И отправьте количество слайдов на нашу карусель, но лучше наша карусель сделает работу за нас. Поэтому нам нужно знать ширину слайдов. Здесь второй последний украл Нетанель. Идея Netanel состоит в том, чтобы иметь .html вроде
<div class="carousel-model">
<ng-container [ngTemplateOutlet]="slides && slides.first?slides.first.templateRef:null">
</ng-container>
</div>
если мы создадим директиву, которая выберет класс, такой как
@Directive({
selector: '.carousel-model'
})
export class CarouselSlideElement {
}
И определил ViewChild как
@ViewChild(CarouselSlideElement, { static: false, read: ElementRef }) slideElement: ElementRef
в ngAfterViewInit мы можем спросить о
this.slideElement.nativeElement.getBoundingClientRect()
чтобы получить размеры
Хорошо, я вставил «таймер», потому что, если я загружаю изображения, я могу забыть о ширине этого изображения.
ngAfterViewInit() {
timer(0, 200).pipe(takeWhile(() => !this.slideWidth || !this.slides || !this.slides.first)).subscribe(() => {
const square = this.slideElement.nativeElement.getBoundingClientRect();
this.slideWidth = square.width;
if (this.slideWidth && this.slides && this.slides.first)
this.resizeCarousel()
})
}
Еще один шаг. Более сложная часть - это дублирование слайдов. Помните, что slides.first - это наш первый слайд, а slides.last наш последний слайд. Я перекрашиваю все в функцию resizeCarousel
private resizeCarousel() {
if (this.carousel) {
let totalWidth = this.carousel.nativeElement.getBoundingClientRect().width;
this.increment = Math.floor(totalWidth / this.slideWidth);
let count = (this.increment * this.slideWidth) != totalWidth ? 1 : 0;
this.offset = (totalWidth - 3 * (this.increment) * this.slideWidth) / 2 - this.slideWidth * count;
console.log(totalWidth,count)
this.slides.first.viewContainer.clear()
this.slides.last.viewContainer.clear()
this.slides.last.viewContainer.createEmbeddedView(this.slides.last.templateRef);
let added = 0;
this.slides.forEach((x, index) => {
if (index && index >= (this.slides.length - this.increment - count)) {
this.slides.first.viewContainer.createEmbeddedView(x.templateRef)
added++
}
if (index < this.increment + count) {
this.slides.last.viewContainer.createEmbeddedView(x.templateRef)
added++
}
})
this.slides.first.viewContainer.createEmbeddedView(this.slides.first.templateRef)
this.currentSlide = 0;
this.transitionCarousel(0, this.currentSlide);
}
}
Я добавляю изображение на каждую сторону, если они не соответствуют точно изображениям
ну, последняя "идея заимствования", которую я украл в NetBasal, hotListener для изменения размера окна
@HostListener('window:resize', ['$event'])
onResize(event) {
if (this.slideWidth && this.slides && this.slides.first)
this.resizeCarousel();
}
И это все фолк, три функции для следующей, предыдущая и сет и "лист эль полло"
prev() {
this.transitionCarousel(null, this.currentSlide + this.increment);
}
next() {
this.transitionCarousel(null, this.currentSlide - this.increment);
}
setSlide(slide: number) {
slide = slide;
this.transitionCarousel(null, slide);
}
Обновление Лучше использовать анимацию
частный переходCarousel (время: любое, слайд: номер) {
const myAnimation: AnimationFactory = this.buildAnimation(time,slide);
this.player = myAnimation.create(this.carousel.nativeElement);
this.currentSlide = (slide >= this.slides.length) ? slide - this.slides.length :
(slide < 0) ? this.currentSlide = slide + this.slides.length :
slide
this.player.play();
}
приватная анимация сборки (время: любое, слайд: номер) {
const animation: number = (slide> = this.slides.length)? 1: (слайд <0)? 2: 0; </p>
const offsetInitial = (slide >= this.slides.length)?
this.offset - this.slideWidth * (this.currentSlide - this.slides.length):
0;
let offsetFinal = (slide < 0)?
this.offset - this.slideWidth * (slide + this.slides.length):
0;
const offset = (slide >= this.slides.length)?
this.offset - this.slideWidth * (slide-this.slides.length):
this.offset - this.slideWidth * slide;
return animation==1 ? this.builder.build([
style({ transform: `translateX(${offsetInitial}px)` }),
animate(time == null ? this.timing : 0, style({ transform: `translateX(${offset}px)` }))
]) : animation==2 ? this.builder.build(sequence([
animate(time == null ? this.timing : 0, style({ transform: `translateX(${offset}px)` })),
style({ transform: `translateX(${offsetFinal}px` })]))
: this.builder.build([
animate(time == null ? this.timing : 0, style({ transform: `translateX(${offset}px)` }))
]);
}