Skip to content

Commit 1f5144b

Browse files
authored
Added skeleton (#617)
1 parent a66c2f8 commit 1f5144b

File tree

3 files changed

+115
-19
lines changed

3 files changed

+115
-19
lines changed
Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
<div class="carousel-host-container">
22
<div class="carousel-frame">
33
<div class="carousel-scroll-area" #scrollContainer>
4-
<div class="card-wrapper" *ngFor="let item of displayItems" #cardItem>
5-
<mat-card class="inner-material-card">
6-
<img [src]="item.img" alt="Card Image" />
7-
<div class="card-overlay">
8-
<h3>{{ item.title }}</h3>
9-
</div>
10-
</mat-card>
11-
</div>
4+
<ng-container *ngIf="isLoading">
5+
<div class="card-wrapper" *ngFor="let item of skeletonItems">
6+
<mat-card class="inner-material-card">
7+
<!-- Shimmer Animation -->
8+
<div class="skeleton-shimmer"></div>
9+
<!-- Fake Title Bar -->
10+
<div class="card-overlay">
11+
<div class="skeleton-text"></div>
12+
</div>
13+
</mat-card>
14+
</div>
15+
</ng-container>
16+
<ng-container *ngIf="!isLoading">
17+
<div class="card-wrapper" *ngFor="let item of displayItems" #cardItem>
18+
<mat-card class="inner-material-card">
19+
<img [src]="item.img" alt="Card Image" />
20+
<div class="card-overlay">
21+
<h3>{{ item.title }}</h3>
22+
</div>
23+
</mat-card>
24+
</div>
25+
</ng-container>
1226
</div>
1327
</div>
14-
<div class="carousel-footer"></div>
1528
</div>

src/app/_samples/mediaco/components/carousel/carousel.component.scss

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,34 @@
8484
}
8585
}
8686
}
87+
.skeleton-shimmer {
88+
width: 100%;
89+
height: 100%;
90+
background: #e0e0e0;
91+
position: relative;
92+
overflow: hidden;
93+
94+
&::after {
95+
content: '';
96+
position: absolute;
97+
top: 0;
98+
left: 0;
99+
width: 100%;
100+
height: 100%;
101+
transform: translateX(-100%);
102+
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.1) 50%, transparent 100%);
103+
animation: shimmer 1.5s infinite;
104+
}
105+
}
106+
.skeleton-text {
107+
height: 16px;
108+
width: 80%;
109+
background: rgba(255, 255, 255, 0.3);
110+
border-radius: 4px;
111+
}
112+
113+
@keyframes shimmer {
114+
100% {
115+
transform: translateX(100%);
116+
}
117+
}

src/app/_samples/mediaco/components/carousel/carousel.component.ts

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,31 @@ export class CarouselComponent implements AfterViewInit, OnDestroy, OnChanges {
3131

3232
originalItems: any[] = [];
3333
displayItems: any[] = [];
34+
isLoading: boolean = true;
35+
skeletonItems: any[] = new Array(6).fill(0);
3436

3537
constructor(
3638
private ngZone: NgZone,
3739
private dialog: MatDialog
3840
) {}
3941

4042
ngOnChanges(changes: SimpleChanges) {
41-
if (changes['data'] && this.data) {
42-
this.buildCarouselItems();
43+
if (changes['data']) {
44+
if (this.data && this.data.length > 0) {
45+
this.buildCarouselItems();
46+
} else {
47+
this.isLoading = true;
48+
}
49+
}
50+
}
51+
52+
initializeScroll() {
53+
const container = this.scrollContainer?.nativeElement;
54+
if (container && container.scrollWidth > 0) {
55+
const singleSetWidth = container.scrollWidth / 3;
56+
container.scrollLeft = singleSetWidth;
57+
// Trigger one calculation to set initial sizes
58+
this.onScroll({ target: container } as any);
4359
}
4460
}
4561

@@ -51,31 +67,64 @@ export class CarouselComponent implements AfterViewInit, OnDestroy, OnChanges {
5167
...item
5268
};
5369
});
70+
5471
this.originalItems = mappedData;
5572
let loopList = [...mappedData];
73+
5674
// If you have 2 items, we duplicate them until we have at least 12.
5775
const MIN_ITEMS = 12;
5876
if (loopList.length > 0) {
5977
while (loopList.length < MIN_ITEMS) {
6078
loopList = [...loopList, ...loopList];
6179
}
6280
}
63-
//CREATE 3 SETS: [Left Buffer] [Middle (Active)] [Right Buffer]
81+
6482
this.displayItems = [...loopList, ...loopList, ...loopList];
83+
84+
this.preloadImages();
85+
}
86+
87+
//Logic to download all images before showing UI
88+
preloadImages() {
89+
this.isLoading = true;
90+
const uniqueUrls = [...new Set(this.displayItems.map(item => item.img))].filter(url => url);
91+
92+
let loadedCount = 0;
93+
const total = uniqueUrls.length;
94+
95+
if (total === 0) {
96+
this.finishLoading();
97+
return;
98+
}
99+
100+
uniqueUrls.forEach(url => {
101+
const img = new Image();
102+
img.src = url;
103+
104+
const onImageComplete = () => {
105+
loadedCount++;
106+
if (loadedCount === total) {
107+
this.finishLoading();
108+
}
109+
};
110+
111+
img.onload = onImageComplete;
112+
img.onerror = onImageComplete;
113+
});
114+
}
115+
116+
finishLoading() {
117+
this.isLoading = false;
118+
setTimeout(() => {
119+
this.initializeScroll();
120+
}, 0);
65121
}
66122

67123
ngAfterViewInit() {
68124
this.ngZone.runOutsideAngular(() => {
69125
const container = this.scrollContainer?.nativeElement;
70126
if (container) {
71127
container.addEventListener('scroll', this.onScroll.bind(this));
72-
setTimeout(() => {
73-
if (container.scrollWidth > 0) {
74-
const singleSetWidth = container.scrollWidth / 3;
75-
container.scrollLeft = singleSetWidth;
76-
this.onScroll({ target: container } as any);
77-
}
78-
}, 50);
79128
}
80129
});
81130
}
@@ -88,6 +137,8 @@ export class CarouselComponent implements AfterViewInit, OnDestroy, OnChanges {
88137
}
89138

90139
onScroll(event: Event) {
140+
if (this.isLoading) return;
141+
91142
const container = event.target as HTMLElement;
92143
if (!container) return;
93144

@@ -101,6 +152,7 @@ export class CarouselComponent implements AfterViewInit, OnDestroy, OnChanges {
101152
} else if (currentScroll >= singleSetWidth * 2 - 100) {
102153
container.scrollLeft = currentScroll - singleSetWidth;
103154
}
155+
104156
const containerRect = container.getBoundingClientRect();
105157
if (containerRect.width === 0) return;
106158

0 commit comments

Comments
 (0)