srcdev-nuxt-components 2.2.1 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,36 +1,45 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="slider-gallery" :class="elementClasses" ref="sliderGalleryWrapper">
|
|
3
|
-
<div class="
|
|
4
|
-
<div
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
2
|
+
<div class="slider-gallery" :class="[elementClasses]" ref="sliderGalleryWrapper">
|
|
3
|
+
<div class="loading-state" :class="[{ loaded: !isLoading }]">
|
|
4
|
+
<div class="loading-spinner"></div>
|
|
5
|
+
<p>Loading gallery...</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="gallery-content" :class="[{ loaded: isLoading }]">
|
|
9
|
+
<div class="list" ref="sliderGalleryImagesList">
|
|
10
|
+
<div v-for="(item, index) in galleryData" :key="index" class="item">
|
|
11
|
+
<img :src="item.src" :alt="item.alt" @load="handleImageLoad(index)" @error="handleImageError(index)" loading="lazy" />
|
|
12
|
+
<div class="content">
|
|
13
|
+
<div class="author">{{ item.stylist }}</div>
|
|
14
|
+
<div class="title">{{ item.title }}</div>
|
|
15
|
+
<div class="topic">{{ item.category }}</div>
|
|
16
|
+
<div class="des">{{ item.description }}</div>
|
|
17
|
+
<div class="buttons">
|
|
18
|
+
<button>SEE MORE</button>
|
|
19
|
+
</div>
|
|
13
20
|
</div>
|
|
14
21
|
</div>
|
|
15
22
|
</div>
|
|
16
|
-
</div>
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
<div class="thumbnail" ref="sliderGalleryThumbnailsList">
|
|
25
|
+
<div v-for="(item, index) in galleryData" :key="index" class="item">
|
|
26
|
+
<div class="inner">
|
|
27
|
+
<img :src="item.src" :alt="item.alt" loading="lazy" />
|
|
28
|
+
<div class="content">
|
|
29
|
+
<div class="title" v-show="item.thumbnail?.title !== ''">{{ item.thumbnail?.title }}</div>
|
|
30
|
+
<div class="description" v-show="item.thumbnail?.description !== ''">{{ item.thumbnail?.description }}</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
24
33
|
</div>
|
|
25
34
|
</div>
|
|
26
|
-
</div>
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
<div class="arrows">
|
|
37
|
+
<button id="prev" ref="prevDom" @click.prevent="doPrevious()"><</button>
|
|
38
|
+
<button id="next" ref="nextDom" @click.prevent="doNext()">></button>
|
|
39
|
+
</div>
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
<div class="time"></div>
|
|
42
|
+
</div>
|
|
34
43
|
</div>
|
|
35
44
|
</template>
|
|
36
45
|
|
|
@@ -74,22 +83,75 @@ const sliderGalleryWrapper = useTemplateRef('sliderGalleryWrapper');
|
|
|
74
83
|
const sliderGalleryImagesList = useTemplateRef('sliderGalleryImagesList');
|
|
75
84
|
const sliderGalleryThumbnailsList = useTemplateRef('sliderGalleryThumbnailsList');
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
const
|
|
86
|
+
const isLoading = ref(true);
|
|
87
|
+
const loadedImages = ref<Set<number>>(new Set());
|
|
88
|
+
const preloadedImages = ref<Array<HTMLImageElement>>([]);
|
|
89
|
+
|
|
90
|
+
onMounted(async () => {
|
|
91
|
+
await nextTick();
|
|
92
|
+
|
|
93
|
+
// If no images or galleryData is empty, stop loading
|
|
94
|
+
if (!galleryData.value || galleryData.value.length === 0) {
|
|
95
|
+
isLoading.value = false;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Create an array to hold image loading promises
|
|
100
|
+
const imageLoadPromises: Promise<void>[] = [];
|
|
101
|
+
|
|
102
|
+
// Preload the first image at minimum
|
|
103
|
+
const firstImageIndex = 0;
|
|
104
|
+
if (galleryData.value[firstImageIndex]) {
|
|
105
|
+
const img = new Image();
|
|
106
|
+
img.src = galleryData.value[firstImageIndex].src;
|
|
107
|
+
|
|
108
|
+
const promise = new Promise<void>((resolve) => {
|
|
109
|
+
img.onload = () => {
|
|
110
|
+
console.log('Image preloaded:', firstImageIndex);
|
|
111
|
+
loadedImages.value.add(firstImageIndex);
|
|
112
|
+
resolve();
|
|
113
|
+
};
|
|
114
|
+
img.onerror = () => {
|
|
115
|
+
console.error('Failed to preload image:', firstImageIndex);
|
|
116
|
+
loadedImages.value.add(firstImageIndex); // Count as loaded anyway
|
|
117
|
+
resolve();
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
imageLoadPromises.push(promise);
|
|
122
|
+
preloadedImages.value.push(img);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Wait for at least the first image to load
|
|
126
|
+
await Promise.race(imageLoadPromises);
|
|
127
|
+
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
isLoading.value = false;
|
|
130
|
+
}, 500);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const handleImageLoad = (index: number) => {
|
|
134
|
+
console.log('Image loaded:', index);
|
|
135
|
+
loadedImages.value.add(index);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleImageError = (index: number) => {
|
|
139
|
+
console.error(`Failed to load image at index ${index}`);
|
|
140
|
+
loadedImages.value.add(index);
|
|
141
|
+
};
|
|
79
142
|
|
|
80
143
|
const doNext = () => {
|
|
144
|
+
if (isLoading.value) return;
|
|
81
145
|
showSlider('next');
|
|
82
146
|
};
|
|
83
147
|
|
|
84
148
|
const doPrevious = () => {
|
|
149
|
+
if (isLoading.value) return;
|
|
85
150
|
showSlider('prev');
|
|
86
151
|
};
|
|
87
152
|
|
|
88
153
|
let runTimeOut: any;
|
|
89
|
-
let runNextAuto =
|
|
90
|
-
if (!props.autoRun) return;
|
|
91
|
-
doNext();
|
|
92
|
-
}, autoRunInterval);
|
|
154
|
+
let runNextAuto: any = null;
|
|
93
155
|
|
|
94
156
|
function showSlider(type: string) {
|
|
95
157
|
const currentSliderItems = Array.from(sliderGalleryImagesList.value?.children || []);
|
|
@@ -108,38 +170,30 @@ function showSlider(type: string) {
|
|
|
108
170
|
|
|
109
171
|
sliderGalleryWrapper.value?.classList.add('next');
|
|
110
172
|
} else {
|
|
111
|
-
// For prev animation:
|
|
112
|
-
// 1. First modify the DOM (prepend the items)
|
|
113
173
|
if (currentSliderItems.length) {
|
|
114
174
|
const lastItem = currentSliderItems[currentSliderItems.length - 1];
|
|
115
|
-
// Set initial state before prepending (if needed)
|
|
116
175
|
lastItem.classList.add('prepend-item');
|
|
117
176
|
sliderGalleryImagesList.value?.prepend(lastItem);
|
|
118
177
|
}
|
|
119
178
|
|
|
120
179
|
if (currentThumbnailItems.length) {
|
|
121
180
|
const lastThumb = currentThumbnailItems[currentThumbnailItems.length - 1];
|
|
122
|
-
// Set initial state before prepending (if needed)
|
|
123
181
|
lastThumb.classList.add('prepend-item');
|
|
124
182
|
sliderGalleryThumbnailsList.value?.prepend(lastThumb);
|
|
125
183
|
}
|
|
126
184
|
|
|
127
|
-
//
|
|
128
|
-
// This is a standard technique to ensure CSS transitions work properly
|
|
129
|
-
// when you need to apply styles immediately after DOM changes
|
|
130
|
-
sliderGalleryWrapper.value?.offsetWidth;
|
|
131
|
-
|
|
132
|
-
// 3. Add the class for animation
|
|
185
|
+
sliderGalleryWrapper.value?.offsetWidth; // Force reflow
|
|
133
186
|
sliderGalleryWrapper.value?.classList.add('prev');
|
|
134
187
|
}
|
|
135
188
|
|
|
136
189
|
clearTimeout(runTimeOut);
|
|
137
190
|
runTimeOut = setTimeout(() => {
|
|
191
|
+
// Your existing cleanup code
|
|
138
192
|
if (sliderGalleryWrapper.value) {
|
|
139
193
|
sliderGalleryWrapper.value.classList.remove('next');
|
|
140
194
|
sliderGalleryWrapper.value.classList.remove('prev');
|
|
141
195
|
|
|
142
|
-
// Remove
|
|
196
|
+
// Remove helper classes
|
|
143
197
|
const items = sliderGalleryImagesList.value?.querySelectorAll('.prepend-item');
|
|
144
198
|
items?.forEach((item) => item.classList.remove('prepend-item'));
|
|
145
199
|
|
|
@@ -148,19 +202,35 @@ function showSlider(type: string) {
|
|
|
148
202
|
}
|
|
149
203
|
}, props.animationDuration);
|
|
150
204
|
|
|
205
|
+
// Reset auto-run timer
|
|
151
206
|
clearTimeout(runNextAuto);
|
|
152
207
|
runNextAuto = setTimeout(() => {
|
|
153
|
-
if (!props.autoRun) return;
|
|
208
|
+
if (!props.autoRun || isLoading.value) return;
|
|
154
209
|
doNext();
|
|
155
|
-
}, autoRunInterval);
|
|
210
|
+
}, props.autoRunInterval);
|
|
156
211
|
}
|
|
157
212
|
|
|
213
|
+
// Initialize auto-run only after loading completes
|
|
214
|
+
watch(isLoading, (isLoadingNow) => {
|
|
215
|
+
if (!isLoadingNow && props.autoRun) {
|
|
216
|
+
clearTimeout(runNextAuto);
|
|
217
|
+
runNextAuto = setTimeout(() => {
|
|
218
|
+
doNext();
|
|
219
|
+
}, props.autoRunInterval);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
158
223
|
watch(
|
|
159
224
|
() => props.styleClassPassthrough,
|
|
160
225
|
() => {
|
|
161
226
|
resetElementClasses(props.styleClassPassthrough);
|
|
162
227
|
}
|
|
163
228
|
);
|
|
229
|
+
|
|
230
|
+
onBeforeUnmount(() => {
|
|
231
|
+
clearTimeout(runTimeOut);
|
|
232
|
+
clearTimeout(runNextAuto);
|
|
233
|
+
});
|
|
164
234
|
</script>
|
|
165
235
|
|
|
166
236
|
<style lang="css">
|
|
@@ -171,12 +241,58 @@ watch(
|
|
|
171
241
|
--_thembnailAspectRatio: 150 /220;
|
|
172
242
|
|
|
173
243
|
height: 100svh;
|
|
174
|
-
/* margin-top: -50px; */
|
|
175
244
|
width: 100vw;
|
|
176
245
|
overflow: hidden;
|
|
177
246
|
position: absolute;
|
|
178
247
|
inset: 0 0 0 0;
|
|
179
248
|
|
|
249
|
+
.loading-state {
|
|
250
|
+
position: absolute;
|
|
251
|
+
inset: 0 0 0 0;
|
|
252
|
+
z-index: 1000;
|
|
253
|
+
display: flex;
|
|
254
|
+
flex-direction: column;
|
|
255
|
+
background-color: var(--page-bg);
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
color: var(--grayscale-text-body);
|
|
259
|
+
opacity: 1;
|
|
260
|
+
transition: display 0.5s, opacity 0.5s;
|
|
261
|
+
transition-behavior: allow-discrete;
|
|
262
|
+
|
|
263
|
+
&.loaded {
|
|
264
|
+
display: none;
|
|
265
|
+
opacity: 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.loading-spinner {
|
|
269
|
+
width: 50px;
|
|
270
|
+
height: 50px;
|
|
271
|
+
border: 5px solid rgba(0, 0, 0, 0.1);
|
|
272
|
+
border-radius: 50%;
|
|
273
|
+
border-top-color: #f1683a;
|
|
274
|
+
animation: spinner 1s ease-in-out infinite;
|
|
275
|
+
margin-bottom: 20px;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
p {
|
|
279
|
+
font-size: 1.2em;
|
|
280
|
+
font-weight: 500;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.gallery-content {
|
|
285
|
+
width: 100%;
|
|
286
|
+
height: 100%;
|
|
287
|
+
position: relative;
|
|
288
|
+
/* opacity: 0; */
|
|
289
|
+
/* transition: opacity 0.5s ease-in-out; */
|
|
290
|
+
|
|
291
|
+
&.loaded {
|
|
292
|
+
/* opacity: 1; */
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
180
296
|
.list {
|
|
181
297
|
.item {
|
|
182
298
|
width: 100%;
|
|
@@ -287,6 +403,28 @@ watch(
|
|
|
287
403
|
outline: var(--_thumbnailOutline, 1px solid transparent);
|
|
288
404
|
border-radius: var(--_thumbnailBorderRadius, 20px);
|
|
289
405
|
|
|
406
|
+
.inner {
|
|
407
|
+
position: absolute;
|
|
408
|
+
inset: 0 0 0 0;
|
|
409
|
+
background-color: #0004;
|
|
410
|
+
border-radius: var(--_thumbnailBorderRadius, 20px);
|
|
411
|
+
z-index: 2;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
&:first-child {
|
|
415
|
+
/* Add the animated border effect */
|
|
416
|
+
&::before {
|
|
417
|
+
content: '';
|
|
418
|
+
position: absolute;
|
|
419
|
+
inset: -3px; /* Border outside the thumbnail */
|
|
420
|
+
border-radius: 20px;
|
|
421
|
+
/* background: conic-gradient(transparent 0deg, transparent 360deg); */
|
|
422
|
+
z-index: 1;
|
|
423
|
+
animation: none;
|
|
424
|
+
pointer-events: none;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
290
428
|
img {
|
|
291
429
|
width: 100%;
|
|
292
430
|
height: 100%;
|
|
@@ -380,6 +518,12 @@ watch(
|
|
|
380
518
|
animation: effectNext 0.5s linear 1 forwards;
|
|
381
519
|
|
|
382
520
|
.item {
|
|
521
|
+
&:first-child {
|
|
522
|
+
/* Add the animated border effect */
|
|
523
|
+
&::before {
|
|
524
|
+
animation: countdownBorder 7s linear 1 forwards;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
383
527
|
&:nth-last-child(1) {
|
|
384
528
|
overflow: hidden;
|
|
385
529
|
animation: showThumbnail 0.5s linear 1 forwards;
|
|
@@ -437,6 +581,12 @@ watch(
|
|
|
437
581
|
animation: effectPrev 0.5s linear 1 forwards;
|
|
438
582
|
|
|
439
583
|
.item {
|
|
584
|
+
&:first-child {
|
|
585
|
+
/* Add the animated border effect */
|
|
586
|
+
&::before {
|
|
587
|
+
animation: countdownBorder 7s linear 1 forwards;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
440
590
|
&:nth-child(1) {
|
|
441
591
|
overflow: hidden;
|
|
442
592
|
animation: showThumbnailPrev 0.5s linear 1 forwards;
|
|
@@ -533,6 +683,15 @@ watch(
|
|
|
533
683
|
}
|
|
534
684
|
}
|
|
535
685
|
|
|
686
|
+
@keyframes spinner {
|
|
687
|
+
0% {
|
|
688
|
+
transform: rotate(0deg);
|
|
689
|
+
}
|
|
690
|
+
100% {
|
|
691
|
+
transform: rotate(360deg);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
536
695
|
@media screen and (max-width: 678px) {
|
|
537
696
|
.slider-gallery .list .item .content {
|
|
538
697
|
padding-right: 0;
|
|
@@ -541,4 +700,14 @@ watch(
|
|
|
541
700
|
font-size: 30px;
|
|
542
701
|
}
|
|
543
702
|
}
|
|
703
|
+
|
|
704
|
+
/* Keyframe for the border animation */
|
|
705
|
+
@keyframes countdownBorder {
|
|
706
|
+
0% {
|
|
707
|
+
background: conic-gradient(brightgreen 0deg, transparent 0deg);
|
|
708
|
+
}
|
|
709
|
+
100% {
|
|
710
|
+
background: conic-gradient(brightgreen 0deg, brightgreen 360deg);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
544
713
|
</style>
|
package/package.json
CHANGED