srcdev-nuxt-components 2.2.0 → 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,41 +1,62 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="slider-gallery" 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
|
|
|
37
46
|
<script setup lang="ts">
|
|
38
47
|
const props = defineProps({
|
|
48
|
+
autoRun: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
default: true,
|
|
51
|
+
},
|
|
52
|
+
autoRunInterval: {
|
|
53
|
+
type: Number,
|
|
54
|
+
default: 7000,
|
|
55
|
+
},
|
|
56
|
+
animationDuration: {
|
|
57
|
+
type: Number,
|
|
58
|
+
default: 3000,
|
|
59
|
+
},
|
|
39
60
|
styleClassPassthrough: {
|
|
40
61
|
type: Array as PropType<string[]>,
|
|
41
62
|
default: () => [],
|
|
@@ -49,6 +70,10 @@ interface IGalleryData {
|
|
|
49
70
|
title?: string;
|
|
50
71
|
category?: string;
|
|
51
72
|
description?: string;
|
|
73
|
+
thumbnail?: {
|
|
74
|
+
title: string;
|
|
75
|
+
description: string;
|
|
76
|
+
};
|
|
52
77
|
}
|
|
53
78
|
|
|
54
79
|
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
@@ -57,31 +82,82 @@ const galleryData = defineModel<IGalleryData[]>('galleryData');
|
|
|
57
82
|
const sliderGalleryWrapper = useTemplateRef('sliderGalleryWrapper');
|
|
58
83
|
const sliderGalleryImagesList = useTemplateRef('sliderGalleryImagesList');
|
|
59
84
|
const sliderGalleryThumbnailsList = useTemplateRef('sliderGalleryThumbnailsList');
|
|
60
|
-
const timeDom = useTemplateRef('timeDom');
|
|
61
85
|
|
|
62
|
-
const
|
|
63
|
-
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
|
+
};
|
|
64
142
|
|
|
65
143
|
const doNext = () => {
|
|
144
|
+
if (isLoading.value) return;
|
|
66
145
|
showSlider('next');
|
|
67
146
|
};
|
|
68
147
|
|
|
69
148
|
const doPrevious = () => {
|
|
149
|
+
if (isLoading.value) return;
|
|
70
150
|
showSlider('prev');
|
|
71
151
|
};
|
|
72
152
|
|
|
73
153
|
let runTimeOut: any;
|
|
74
|
-
let runNextAuto =
|
|
75
|
-
doNext();
|
|
76
|
-
}, timeAutoNext);
|
|
154
|
+
let runNextAuto: any = null;
|
|
77
155
|
|
|
78
156
|
function showSlider(type: string) {
|
|
79
|
-
// Get fresh references to all items by querying the DOM directly
|
|
80
157
|
const currentSliderItems = Array.from(sliderGalleryImagesList.value?.children || []);
|
|
81
158
|
const currentThumbnailItems = Array.from(sliderGalleryThumbnailsList.value?.children || []);
|
|
82
159
|
|
|
83
160
|
if (type === 'next') {
|
|
84
|
-
// Move the first item to the end
|
|
85
161
|
if (currentSliderItems.length) {
|
|
86
162
|
const firstItem = currentSliderItems[0];
|
|
87
163
|
sliderGalleryImagesList.value?.appendChild(firstItem);
|
|
@@ -94,52 +170,129 @@ function showSlider(type: string) {
|
|
|
94
170
|
|
|
95
171
|
sliderGalleryWrapper.value?.classList.add('next');
|
|
96
172
|
} else {
|
|
97
|
-
// Move the last item to the beginning
|
|
98
173
|
if (currentSliderItems.length) {
|
|
99
174
|
const lastItem = currentSliderItems[currentSliderItems.length - 1];
|
|
175
|
+
lastItem.classList.add('prepend-item');
|
|
100
176
|
sliderGalleryImagesList.value?.prepend(lastItem);
|
|
101
177
|
}
|
|
102
178
|
|
|
103
179
|
if (currentThumbnailItems.length) {
|
|
104
180
|
const lastThumb = currentThumbnailItems[currentThumbnailItems.length - 1];
|
|
181
|
+
lastThumb.classList.add('prepend-item');
|
|
105
182
|
sliderGalleryThumbnailsList.value?.prepend(lastThumb);
|
|
106
183
|
}
|
|
107
184
|
|
|
185
|
+
sliderGalleryWrapper.value?.offsetWidth; // Force reflow
|
|
108
186
|
sliderGalleryWrapper.value?.classList.add('prev');
|
|
109
187
|
}
|
|
110
188
|
|
|
111
189
|
clearTimeout(runTimeOut);
|
|
112
190
|
runTimeOut = setTimeout(() => {
|
|
191
|
+
// Your existing cleanup code
|
|
113
192
|
if (sliderGalleryWrapper.value) {
|
|
114
193
|
sliderGalleryWrapper.value.classList.remove('next');
|
|
115
194
|
sliderGalleryWrapper.value.classList.remove('prev');
|
|
195
|
+
|
|
196
|
+
// Remove helper classes
|
|
197
|
+
const items = sliderGalleryImagesList.value?.querySelectorAll('.prepend-item');
|
|
198
|
+
items?.forEach((item) => item.classList.remove('prepend-item'));
|
|
199
|
+
|
|
200
|
+
const thumbs = sliderGalleryThumbnailsList.value?.querySelectorAll('.prepend-item');
|
|
201
|
+
thumbs?.forEach((thumb) => thumb.classList.remove('prepend-item'));
|
|
116
202
|
}
|
|
117
|
-
},
|
|
203
|
+
}, props.animationDuration);
|
|
118
204
|
|
|
205
|
+
// Reset auto-run timer
|
|
119
206
|
clearTimeout(runNextAuto);
|
|
120
207
|
runNextAuto = setTimeout(() => {
|
|
208
|
+
if (!props.autoRun || isLoading.value) return;
|
|
121
209
|
doNext();
|
|
122
|
-
},
|
|
210
|
+
}, props.autoRunInterval);
|
|
123
211
|
}
|
|
124
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
|
+
|
|
125
223
|
watch(
|
|
126
224
|
() => props.styleClassPassthrough,
|
|
127
225
|
() => {
|
|
128
226
|
resetElementClasses(props.styleClassPassthrough);
|
|
129
227
|
}
|
|
130
228
|
);
|
|
229
|
+
|
|
230
|
+
onBeforeUnmount(() => {
|
|
231
|
+
clearTimeout(runTimeOut);
|
|
232
|
+
clearTimeout(runNextAuto);
|
|
233
|
+
});
|
|
131
234
|
</script>
|
|
132
235
|
|
|
133
236
|
<style lang="css">
|
|
134
237
|
/* slider-gallery */
|
|
135
238
|
.slider-gallery {
|
|
239
|
+
--_animationDuration: v-bind(animationDuration + 'ms');
|
|
240
|
+
|
|
241
|
+
--_thembnailAspectRatio: 150 /220;
|
|
242
|
+
|
|
136
243
|
height: 100svh;
|
|
137
|
-
/* margin-top: -50px; */
|
|
138
244
|
width: 100vw;
|
|
139
245
|
overflow: hidden;
|
|
140
246
|
position: absolute;
|
|
141
247
|
inset: 0 0 0 0;
|
|
142
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
|
+
|
|
143
296
|
.list {
|
|
144
297
|
.item {
|
|
145
298
|
width: 100%;
|
|
@@ -246,6 +399,32 @@ watch(
|
|
|
246
399
|
flex-shrink: 0;
|
|
247
400
|
position: relative;
|
|
248
401
|
|
|
402
|
+
border: var(--_thumbnailBorder, 1px solid transparent);
|
|
403
|
+
outline: var(--_thumbnailOutline, 1px solid transparent);
|
|
404
|
+
border-radius: var(--_thumbnailBorderRadius, 20px);
|
|
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
|
+
|
|
249
428
|
img {
|
|
250
429
|
width: 100%;
|
|
251
430
|
height: 100%;
|
|
@@ -339,6 +518,12 @@ watch(
|
|
|
339
518
|
animation: effectNext 0.5s linear 1 forwards;
|
|
340
519
|
|
|
341
520
|
.item {
|
|
521
|
+
&:first-child {
|
|
522
|
+
/* Add the animated border effect */
|
|
523
|
+
&::before {
|
|
524
|
+
animation: countdownBorder 7s linear 1 forwards;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
342
527
|
&:nth-last-child(1) {
|
|
343
528
|
overflow: hidden;
|
|
344
529
|
animation: showThumbnail 0.5s linear 1 forwards;
|
|
@@ -347,7 +532,7 @@ watch(
|
|
|
347
532
|
}
|
|
348
533
|
|
|
349
534
|
.time {
|
|
350
|
-
animation: runningTime
|
|
535
|
+
animation: runningTime var(--_animationDuration) linear 1 forwards;
|
|
351
536
|
}
|
|
352
537
|
}
|
|
353
538
|
|
|
@@ -378,6 +563,11 @@ watch(
|
|
|
378
563
|
z-index: 100;
|
|
379
564
|
}
|
|
380
565
|
}
|
|
566
|
+
|
|
567
|
+
.item.prepend-item {
|
|
568
|
+
z-index: 1; /* Ensure it's visible */
|
|
569
|
+
/* Any initial styles needed */
|
|
570
|
+
}
|
|
381
571
|
}
|
|
382
572
|
|
|
383
573
|
.arrows {
|
|
@@ -387,16 +577,30 @@ watch(
|
|
|
387
577
|
}
|
|
388
578
|
|
|
389
579
|
.thumbnail {
|
|
580
|
+
/* Add a transform to the entire thumbnail container */
|
|
581
|
+
animation: effectPrev 0.5s linear 1 forwards;
|
|
582
|
+
|
|
390
583
|
.item {
|
|
584
|
+
&:first-child {
|
|
585
|
+
/* Add the animated border effect */
|
|
586
|
+
&::before {
|
|
587
|
+
animation: countdownBorder 7s linear 1 forwards;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
391
590
|
&:nth-child(1) {
|
|
392
591
|
overflow: hidden;
|
|
393
|
-
|
|
394
|
-
animation: showThumbnail 0.5s linear 1 forwards;
|
|
592
|
+
animation: showThumbnailPrev 0.5s linear 1 forwards;
|
|
395
593
|
}
|
|
396
594
|
}
|
|
595
|
+
|
|
596
|
+
.item.prepend-item {
|
|
597
|
+
opacity: 0;
|
|
598
|
+
transform: translateX(-20px);
|
|
599
|
+
/* Initial state for thumbnail animation */
|
|
600
|
+
}
|
|
397
601
|
}
|
|
398
602
|
.time {
|
|
399
|
-
animation: runningTime
|
|
603
|
+
animation: runningTime var(--_animationDuration) linear 1 forwards;
|
|
400
604
|
}
|
|
401
605
|
}
|
|
402
606
|
}
|
|
@@ -458,6 +662,36 @@ watch(
|
|
|
458
662
|
opacity: 0;
|
|
459
663
|
}
|
|
460
664
|
}
|
|
665
|
+
|
|
666
|
+
@keyframes effectPrev {
|
|
667
|
+
from {
|
|
668
|
+
transform: translateX(-150px);
|
|
669
|
+
}
|
|
670
|
+
to {
|
|
671
|
+
transform: translateX(0);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
@keyframes showThumbnailPrev {
|
|
676
|
+
from {
|
|
677
|
+
opacity: 0;
|
|
678
|
+
transform: translateX(-20px);
|
|
679
|
+
}
|
|
680
|
+
to {
|
|
681
|
+
opacity: 1;
|
|
682
|
+
transform: translateX(0);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
@keyframes spinner {
|
|
687
|
+
0% {
|
|
688
|
+
transform: rotate(0deg);
|
|
689
|
+
}
|
|
690
|
+
100% {
|
|
691
|
+
transform: rotate(360deg);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
461
695
|
@media screen and (max-width: 678px) {
|
|
462
696
|
.slider-gallery .list .item .content {
|
|
463
697
|
padding-right: 0;
|
|
@@ -466,4 +700,14 @@ watch(
|
|
|
466
700
|
font-size: 30px;
|
|
467
701
|
}
|
|
468
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
|
+
}
|
|
469
713
|
</style>
|
package/package.json
CHANGED