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="list" ref="sliderGalleryImagesList">
4
- <div v-for="(item, index) in galleryData" :key="index" class="item">
5
- <img :src="item.src" />
6
- <div class="content">
7
- <div class="author">{{ item.stylist }}</div>
8
- <div class="title">{{ item.title }}</div>
9
- <div class="topic">{{ item.category }}</div>
10
- <div class="des">{{ item.description }}</div>
11
- <div class="buttons">
12
- <button>SEE MORE</button>
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
- <div class="thumbnail" ref="sliderGalleryThumbnailsList">
19
- <div v-for="(item, index) in galleryData" :key="index" class="item">
20
- <img :src="item.src" />
21
- <div class="content">
22
- <div class="title" v-show="item.thumbnail?.title !== ''">{{ item.thumbnail?.title }}</div>
23
- <div class="description" v-show="item.thumbnail?.description !== ''">{{ item.thumbnail?.description }}</div>
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
- <div class="arrows">
29
- <button id="prev" ref="prevDom" @click.prevent="doPrevious()"><</button>
30
- <button id="next" ref="nextDom" @click.prevent="doNext()">></button>
31
- </div>
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
- <div class="time"></div>
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
- // const animationDuration = 3000;
78
- const autoRunInterval = 7000;
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 = setTimeout(() => {
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
- // 2. Force reflow to ensure the DOM changes are applied
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 any helper classes we added
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "2.2.1",
4
+ "version": "2.2.2",
5
5
  "main": "nuxt.config.ts",
6
6
  "scripts": {
7
7
  "clean": "rm -rf .nuxt && rm -rf .output && rm -rf .playground/.nuxt && rm -rf .playground/.output",