srcdev-nuxt-components 2.2.1 → 2.2.3
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 v-if="showGallery" 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,76 @@ 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 showGallery = ref(false);
|
|
88
|
+
const loadedImages = ref<Set<number>>(new Set());
|
|
89
|
+
const preloadedImages = ref<Array<HTMLImageElement>>([]);
|
|
90
|
+
|
|
91
|
+
onMounted(async () => {
|
|
92
|
+
await nextTick();
|
|
93
|
+
|
|
94
|
+
// If no images or galleryData is empty, stop loading
|
|
95
|
+
if (!galleryData.value || galleryData.value.length === 0) {
|
|
96
|
+
isLoading.value = false;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create an array to hold image loading promises
|
|
101
|
+
const imageLoadPromises: Promise<void>[] = [];
|
|
102
|
+
|
|
103
|
+
// Preload the first image at minimum
|
|
104
|
+
const firstImageIndex = 0;
|
|
105
|
+
if (galleryData.value[firstImageIndex]) {
|
|
106
|
+
const img = new Image();
|
|
107
|
+
img.src = galleryData.value[firstImageIndex].src;
|
|
108
|
+
|
|
109
|
+
const promise = new Promise<void>((resolve) => {
|
|
110
|
+
img.onload = () => {
|
|
111
|
+
console.log('Image preloaded:', firstImageIndex);
|
|
112
|
+
loadedImages.value.add(firstImageIndex);
|
|
113
|
+
resolve();
|
|
114
|
+
};
|
|
115
|
+
img.onerror = () => {
|
|
116
|
+
console.error('Failed to preload image:', firstImageIndex);
|
|
117
|
+
loadedImages.value.add(firstImageIndex); // Count as loaded anyway
|
|
118
|
+
resolve();
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
imageLoadPromises.push(promise);
|
|
123
|
+
preloadedImages.value.push(img);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Wait for at least the first image to load
|
|
127
|
+
await Promise.race(imageLoadPromises);
|
|
128
|
+
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
isLoading.value = false;
|
|
131
|
+
}, 500);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const handleImageLoad = (index: number) => {
|
|
135
|
+
console.log('Image loaded:', index);
|
|
136
|
+
loadedImages.value.add(index);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const handleImageError = (index: number) => {
|
|
140
|
+
console.error(`Failed to load image at index ${index}`);
|
|
141
|
+
loadedImages.value.add(index);
|
|
142
|
+
};
|
|
79
143
|
|
|
80
144
|
const doNext = () => {
|
|
145
|
+
if (isLoading.value) return;
|
|
81
146
|
showSlider('next');
|
|
82
147
|
};
|
|
83
148
|
|
|
84
149
|
const doPrevious = () => {
|
|
150
|
+
if (isLoading.value) return;
|
|
85
151
|
showSlider('prev');
|
|
86
152
|
};
|
|
87
153
|
|
|
88
154
|
let runTimeOut: any;
|
|
89
|
-
let runNextAuto =
|
|
90
|
-
if (!props.autoRun) return;
|
|
91
|
-
doNext();
|
|
92
|
-
}, autoRunInterval);
|
|
155
|
+
let runNextAuto: any = null;
|
|
93
156
|
|
|
94
157
|
function showSlider(type: string) {
|
|
95
158
|
const currentSliderItems = Array.from(sliderGalleryImagesList.value?.children || []);
|
|
@@ -108,38 +171,30 @@ function showSlider(type: string) {
|
|
|
108
171
|
|
|
109
172
|
sliderGalleryWrapper.value?.classList.add('next');
|
|
110
173
|
} else {
|
|
111
|
-
// For prev animation:
|
|
112
|
-
// 1. First modify the DOM (prepend the items)
|
|
113
174
|
if (currentSliderItems.length) {
|
|
114
175
|
const lastItem = currentSliderItems[currentSliderItems.length - 1];
|
|
115
|
-
// Set initial state before prepending (if needed)
|
|
116
176
|
lastItem.classList.add('prepend-item');
|
|
117
177
|
sliderGalleryImagesList.value?.prepend(lastItem);
|
|
118
178
|
}
|
|
119
179
|
|
|
120
180
|
if (currentThumbnailItems.length) {
|
|
121
181
|
const lastThumb = currentThumbnailItems[currentThumbnailItems.length - 1];
|
|
122
|
-
// Set initial state before prepending (if needed)
|
|
123
182
|
lastThumb.classList.add('prepend-item');
|
|
124
183
|
sliderGalleryThumbnailsList.value?.prepend(lastThumb);
|
|
125
184
|
}
|
|
126
185
|
|
|
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
|
|
186
|
+
sliderGalleryWrapper.value?.offsetWidth; // Force reflow
|
|
133
187
|
sliderGalleryWrapper.value?.classList.add('prev');
|
|
134
188
|
}
|
|
135
189
|
|
|
136
190
|
clearTimeout(runTimeOut);
|
|
137
191
|
runTimeOut = setTimeout(() => {
|
|
192
|
+
// Your existing cleanup code
|
|
138
193
|
if (sliderGalleryWrapper.value) {
|
|
139
194
|
sliderGalleryWrapper.value.classList.remove('next');
|
|
140
195
|
sliderGalleryWrapper.value.classList.remove('prev');
|
|
141
196
|
|
|
142
|
-
// Remove
|
|
197
|
+
// Remove helper classes
|
|
143
198
|
const items = sliderGalleryImagesList.value?.querySelectorAll('.prepend-item');
|
|
144
199
|
items?.forEach((item) => item.classList.remove('prepend-item'));
|
|
145
200
|
|
|
@@ -148,19 +203,39 @@ function showSlider(type: string) {
|
|
|
148
203
|
}
|
|
149
204
|
}, props.animationDuration);
|
|
150
205
|
|
|
206
|
+
// Reset auto-run timer
|
|
151
207
|
clearTimeout(runNextAuto);
|
|
152
208
|
runNextAuto = setTimeout(() => {
|
|
153
|
-
if (!props.autoRun) return;
|
|
209
|
+
if (!props.autoRun || isLoading.value) return;
|
|
154
210
|
doNext();
|
|
155
|
-
}, autoRunInterval);
|
|
211
|
+
}, props.autoRunInterval);
|
|
156
212
|
}
|
|
157
213
|
|
|
214
|
+
// Initialize auto-run only after loading completes
|
|
215
|
+
watch(isLoading, (isLoadingNow) => {
|
|
216
|
+
if (!isLoadingNow && props.autoRun) {
|
|
217
|
+
clearTimeout(runNextAuto);
|
|
218
|
+
runNextAuto = setTimeout(() => {
|
|
219
|
+
doNext();
|
|
220
|
+
}, props.autoRunInterval);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
158
224
|
watch(
|
|
159
225
|
() => props.styleClassPassthrough,
|
|
160
226
|
() => {
|
|
161
227
|
resetElementClasses(props.styleClassPassthrough);
|
|
162
228
|
}
|
|
163
229
|
);
|
|
230
|
+
|
|
231
|
+
onBeforeUnmount(() => {
|
|
232
|
+
clearTimeout(runTimeOut);
|
|
233
|
+
clearTimeout(runNextAuto);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
onMounted(() => {
|
|
237
|
+
showGallery.value = true;
|
|
238
|
+
});
|
|
164
239
|
</script>
|
|
165
240
|
|
|
166
241
|
<style lang="css">
|
|
@@ -171,12 +246,58 @@ watch(
|
|
|
171
246
|
--_thembnailAspectRatio: 150 /220;
|
|
172
247
|
|
|
173
248
|
height: 100svh;
|
|
174
|
-
/* margin-top: -50px; */
|
|
175
249
|
width: 100vw;
|
|
176
250
|
overflow: hidden;
|
|
177
251
|
position: absolute;
|
|
178
252
|
inset: 0 0 0 0;
|
|
179
253
|
|
|
254
|
+
.loading-state {
|
|
255
|
+
position: absolute;
|
|
256
|
+
inset: 0 0 0 0;
|
|
257
|
+
z-index: 1000;
|
|
258
|
+
display: flex;
|
|
259
|
+
flex-direction: column;
|
|
260
|
+
background-color: var(--page-bg);
|
|
261
|
+
align-items: center;
|
|
262
|
+
justify-content: center;
|
|
263
|
+
color: var(--grayscale-text-body);
|
|
264
|
+
opacity: 1;
|
|
265
|
+
transition: display 0.5s, opacity 0.5s;
|
|
266
|
+
transition-behavior: allow-discrete;
|
|
267
|
+
|
|
268
|
+
&.loaded {
|
|
269
|
+
display: none;
|
|
270
|
+
opacity: 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.loading-spinner {
|
|
274
|
+
width: 50px;
|
|
275
|
+
height: 50px;
|
|
276
|
+
border: 5px solid rgba(0, 0, 0, 0.1);
|
|
277
|
+
border-radius: 50%;
|
|
278
|
+
border-top-color: #f1683a;
|
|
279
|
+
animation: spinner 1s ease-in-out infinite;
|
|
280
|
+
margin-bottom: 20px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
p {
|
|
284
|
+
font-size: 1.2em;
|
|
285
|
+
font-weight: 500;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.gallery-content {
|
|
290
|
+
width: 100%;
|
|
291
|
+
height: 100%;
|
|
292
|
+
position: relative;
|
|
293
|
+
/* opacity: 0; */
|
|
294
|
+
/* transition: opacity 0.5s ease-in-out; */
|
|
295
|
+
|
|
296
|
+
&.loaded {
|
|
297
|
+
/* opacity: 1; */
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
180
301
|
.list {
|
|
181
302
|
.item {
|
|
182
303
|
width: 100%;
|
|
@@ -287,6 +408,28 @@ watch(
|
|
|
287
408
|
outline: var(--_thumbnailOutline, 1px solid transparent);
|
|
288
409
|
border-radius: var(--_thumbnailBorderRadius, 20px);
|
|
289
410
|
|
|
411
|
+
.inner {
|
|
412
|
+
position: absolute;
|
|
413
|
+
inset: 0 0 0 0;
|
|
414
|
+
background-color: #0004;
|
|
415
|
+
border-radius: var(--_thumbnailBorderRadius, 20px);
|
|
416
|
+
z-index: 2;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
&:first-child {
|
|
420
|
+
/* Add the animated border effect */
|
|
421
|
+
&::before {
|
|
422
|
+
content: '';
|
|
423
|
+
position: absolute;
|
|
424
|
+
inset: -3px; /* Border outside the thumbnail */
|
|
425
|
+
border-radius: 20px;
|
|
426
|
+
/* background: conic-gradient(transparent 0deg, transparent 360deg); */
|
|
427
|
+
z-index: 1;
|
|
428
|
+
animation: none;
|
|
429
|
+
pointer-events: none;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
290
433
|
img {
|
|
291
434
|
width: 100%;
|
|
292
435
|
height: 100%;
|
|
@@ -380,6 +523,12 @@ watch(
|
|
|
380
523
|
animation: effectNext 0.5s linear 1 forwards;
|
|
381
524
|
|
|
382
525
|
.item {
|
|
526
|
+
&:first-child {
|
|
527
|
+
/* Add the animated border effect */
|
|
528
|
+
&::before {
|
|
529
|
+
animation: countdownBorder 7s linear 1 forwards;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
383
532
|
&:nth-last-child(1) {
|
|
384
533
|
overflow: hidden;
|
|
385
534
|
animation: showThumbnail 0.5s linear 1 forwards;
|
|
@@ -437,6 +586,12 @@ watch(
|
|
|
437
586
|
animation: effectPrev 0.5s linear 1 forwards;
|
|
438
587
|
|
|
439
588
|
.item {
|
|
589
|
+
&:first-child {
|
|
590
|
+
/* Add the animated border effect */
|
|
591
|
+
&::before {
|
|
592
|
+
animation: countdownBorder 7s linear 1 forwards;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
440
595
|
&:nth-child(1) {
|
|
441
596
|
overflow: hidden;
|
|
442
597
|
animation: showThumbnailPrev 0.5s linear 1 forwards;
|
|
@@ -533,6 +688,15 @@ watch(
|
|
|
533
688
|
}
|
|
534
689
|
}
|
|
535
690
|
|
|
691
|
+
@keyframes spinner {
|
|
692
|
+
0% {
|
|
693
|
+
transform: rotate(0deg);
|
|
694
|
+
}
|
|
695
|
+
100% {
|
|
696
|
+
transform: rotate(360deg);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
536
700
|
@media screen and (max-width: 678px) {
|
|
537
701
|
.slider-gallery .list .item .content {
|
|
538
702
|
padding-right: 0;
|
|
@@ -541,4 +705,14 @@ watch(
|
|
|
541
705
|
font-size: 30px;
|
|
542
706
|
}
|
|
543
707
|
}
|
|
708
|
+
|
|
709
|
+
/* Keyframe for the border animation */
|
|
710
|
+
@keyframes countdownBorder {
|
|
711
|
+
0% {
|
|
712
|
+
background: conic-gradient(brightgreen 0deg, transparent 0deg);
|
|
713
|
+
}
|
|
714
|
+
100% {
|
|
715
|
+
background: conic-gradient(brightgreen 0deg, brightgreen 360deg);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
544
718
|
</style>
|
package/package.json
CHANGED