我正在使用 Angular 17、HTML、SCSS 和 TypeScript 开发无限滑块。滑块最初按预期工作,但在列表中的所有图标都显示一次后,在动画从头开始之前几秒钟内什么都看不到。
我希望滑块无缝循环,再次显示相同的图标而无需任何暂停。
我的 HTML:
<div class="slide-track" #sliderTrack>
@for (item of slider_items; track item) {
<div class="slide mr-8">
<img ngSrc="{{ item.image_url }}" width="64" height="64"
alt="Clank ~ Discord-Bot (Guild {{ item.guild_name}} Icon)" class="img-fluid rounded-full" />
</div>
}
</div>
来自组件的 TypeScript:
export class LandingPageComponent implements AfterViewInit {
@ViewChild('sliderTrack')
protected sliderTrack!: ElementRef<HTMLDivElement>;
protected readonly nav_items = nav_items;
protected slider_items: SliderItems[] = [
{
image_url: 'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist'
},
// additional items...
];
constructor(private animations: AnimationService) {
// clone the list for the slider; is needed for the infinite loop
this.slider_items = [...this.slider_items, ...this.slider_items];
}
ngAfterViewInit(): void {
// set amount of slides for the infinite loop
this.sliderTrack.nativeElement.style.width = `calc(150px * ${this.sliderTrack.nativeElement.children.length / 2})`;
}
}
我的SCSS:
.slider {
background: transparent;
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, .125);
margin: auto;
overflow:hidden;
position: relative;
width: 960px;
&::before,
&::after {
content: "";
height: 100px;
position: absolute;
width: 200px;
z-index: 2;
}
&::after {
right: 0;
top: 0;
transform: rotateZ(180deg);
}
&::before {
left: 0;
top: 0;
}
.slide-track {
animation: scroll 40s linear infinite;
display: flex;
width: calc(150px * 10);
}
.slide {
height: 64px;
width: 64px;
}
}
Lun Dev Code
中的这个精彩教程,它解释了如何使用动画创建无限滑块。
我们可以定义滑块作用的 CSS 变量,例如项目的
height
、width
和 quantity
。我们还为每个项目声明一个 CSS 变量,以跟踪项目的位置,我们可以在 CSS 中使用它来定义动画延迟。
<div class="slider" style="
--width: 64px;
--height: 100px;
--quantity: {{slider_items.length}};
">
<div class="slide-track" #sliderTrack>
@for (item of slider_items; track item) {
<div class="slide mr-8" style="--position: {{$index}}">
<img ngSrc="{{ item.image_url }}" width="64" height="64"
alt="Clank ~ Discord-Bot (Guild {{ item.guild_name}} Icon)" class="img-fluid rounded-full" />
</div>
}
</div>
</div>
请注意,我们使用
{{slider_items.length}}
以编程方式设置元素数量,并使用 style="--position: {{$index}}"
设置每个 slide
的位置。
我们可以根据CSS变量定义滑块高度
height
:
.滑块{ 宽度:100%; 高度:var(--高度); 溢出:隐藏; }
然后我们可以使用我们在顶部滑块元素上指定的尺寸来计算滑块轨道的宽度。
.slider .slide-track {
display: flex;
width: 100%;
min-width: calc(var(--width) * var(--quantity));
position: relative;
}
然后使用我们在 HTML 中定义的
position
CSS 变量,我们可以使用 animation-delay
属性从右到左以增量方式延迟动画的开始,以给出无限滑块的错觉。最初,所有元素都使用 position: absolute;left: 100%;
定位到右侧。然后动画无限运行autoRun
,动画延迟在增量延迟后开始每个元素。
.slider .slide-track .slide { 宽度:var(--宽度); 高度:var(--高度); 位置:绝对; 左:100%; 动画:autoRun 10s线性无限; 过渡:过滤0.5s; 动画延迟: calc( (10s / var(--数量)) * (var(--位置) - 1) - 10s ) !重要的; }
下面是无限滚动的动画定义,因此对于每个元素,它都会向左移动 1
width
CSS 属性长度无限。
@keyframes autoRun {
from {
left: 100%;
}
to {
left: calc(var(--width) * -1);
}
}
我们可以使用
animation-play-state: paused !important;
来停止悬停时的动画。滤镜用于聚焦效果,但如果需要可以移除。
.slider:hover .slide {
animation-play-state: paused !important;
filter: grayscale(1);
}
.slider .slide:hover {
filter: grayscale(0);
}
.slider {
width: 100%;
height: var(--height);
overflow: hidden;
}
.slider .slide-track {
display: flex;
width: 100%;
min-width: calc(var(--width) * var(--quantity));
position: relative;
}
.slider .slide-track .slide {
width: var(--width);
height: var(--height);
position: absolute;
left: 100%;
animation: autoRun 10s linear infinite;
transition: filter 0.5s;
animation-delay: calc(
(10s / var(--quantity)) * (var(--position) - 1) - 10s
) !important;
}
.slider .slide-track .slide img {
width: 100%;
}
@keyframes autoRun {
from {
left: 100%;
}
to {
left: calc(var(--width) * -1);
}
}
.slider:hover .slide {
animation-play-state: paused !important;
filter: grayscale(1);
}
.slider .slide:hover {
filter: grayscale(0);
}
import { Component, ElementRef, ViewChild } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { NgOptimizedImage } from '@angular/common';
@Component({
selector: 'app-root',
imports: [NgOptimizedImage],
template: `
<div class="slider" style="
--width: 64px;
--height: 100px;
--quantity: {{slider_items.length}};
">
<div class="slide-track" #sliderTrack>
@for (item of slider_items; track item) {
<div class="slide mr-8" style="--position: {{$index}}">
<img ngSrc="{{ item.image_url }}" width="64" height="64"
alt="Clank ~ Discord-Bot (Guild {{ item.guild_name}} Icon)" class="img-fluid rounded-full" />
</div>
}
</div>
</div>
`,
styleUrls: ['./main.styles.scss'],
})
export class App {
@ViewChild('sliderTrack')
protected sliderTrack!: ElementRef<HTMLDivElement>;
protected readonly nav_items = [];
protected slider_items: any[] = [
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
{
image_url:
'https://cdn.discordapp.com/icons/671065574821986348/313528b52bc81e964c3bd6c1bb406b9b.png?size=64',
guild_name: 'Bl4cklist',
guild_invite: 'https://discord.gg/bl4cklist',
},
];
}
bootstrapApplication(App);