itube-specs 0.0.850 → 0.0.852

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.
@@ -29,21 +29,53 @@ defineProps<{
29
29
  url: string
30
30
  }>();
31
31
 
32
+ /** Стартовая ширина полосы: видна сразу по ховеру/касанию, не дожидаясь первого скачанного куска файла. */
33
+ const MIN_PROGRESS = 0.05;
34
+ /** Докуда полоса подползает сама, пока нет данных о реальной загрузке. */
35
+ const CREEP_LIMIT = 0.45;
36
+ const CREEP_INTERVAL = 150;
37
+ const CREEP_STEP = 0.18;
38
+
32
39
  const videoRef = ref<HTMLVideoElement | null>(null);
33
40
  const isPlaying = ref(false);
34
41
  const isLoaded = ref(false);
35
- const progress = ref(0);
42
+ const progress = ref(MIN_PROGRESS);
43
+
44
+ let creepTimer: ReturnType<typeof setInterval> | null = null;
45
+
46
+ function stopCreep() {
47
+ if (creepTimer) clearInterval(creepTimer);
48
+ creepTimer = null;
49
+ }
50
+
51
+ /** Имитация ожидания: пока идёт коннект к CDN и реального прогресса нет, полоса замедляясь подползает к CREEP_LIMIT. */
52
+ function startCreep() {
53
+ creepTimer = setInterval(() => {
54
+ const next = progress.value + (CREEP_LIMIT - progress.value) * CREEP_STEP;
55
+
56
+ if (CREEP_LIMIT - next < CREEP_STEP / 100) {
57
+ stopCreep();
58
+ return;
59
+ }
60
+
61
+ progress.value = next;
62
+ }, CREEP_INTERVAL);
63
+ }
36
64
 
37
65
  /** Доля скачанного файла превью: правый край последнего буферизованного отрезка к общей длительности. */
38
66
  function updateProgress() {
39
67
  const video = videoRef.value;
40
68
  if (!video || !video.buffered.length || !Number.isFinite(video.duration)) return;
41
69
 
42
- progress.value = video.buffered.end(video.buffered.length - 1) / video.duration;
43
- if (progress.value >= 1) isLoaded.value = true;
70
+ const loaded = video.buffered.end(video.buffered.length - 1) / video.duration;
71
+ if (loaded > 0) stopCreep();
72
+
73
+ progress.value = Math.max(loaded, progress.value);
74
+ if (loaded >= 1) isLoaded.value = true;
44
75
  }
45
76
 
46
77
  function onPlaying() {
78
+ stopCreep();
47
79
  isPlaying.value = true;
48
80
  isLoaded.value = true;
49
81
  }
@@ -55,6 +87,8 @@ function onWaiting() {
55
87
  }
56
88
 
57
89
  onMounted(() => {
90
+ startCreep();
91
+
58
92
  const video = videoRef.value;
59
93
  if (!video) return;
60
94
 
@@ -63,6 +97,8 @@ onMounted(() => {
63
97
  isPlaying.value = false;
64
98
  });
65
99
  });
100
+
101
+ onBeforeUnmount(stopCreep);
66
102
  </script>
67
103
 
68
104
  <style lang="scss">
@@ -37,9 +37,9 @@
37
37
  :top-chips="topChips"
38
38
  :playlist-id="playlistId"
39
39
  :playlist="playlist"
40
+ :hydrate-on-visible="HYDRATE_MARGIN"
40
41
  class="grid-videos__card"
41
42
  loading="lazy"
42
- hydrate-on-visible
43
43
  />
44
44
  <slot name="grid-end"/>
45
45
  </div>
@@ -49,6 +49,8 @@
49
49
  import type { IVideoCard } from '../../types';
50
50
 
51
51
  const IDLE_COUNT = 4;
52
+ /** Запас гидрации до появления карточки на экране: без него обработчики превью не успевают появиться к тапу. */
53
+ const HYDRATE_MARGIN = { rootMargin: '300px' };
52
54
 
53
55
  const props = defineProps<{
54
56
  items: Array<IVideoCard>
@@ -176,7 +176,7 @@ function linkTo(value: string | number) {
176
176
  }
177
177
 
178
178
  watch(() => route.query['page'], () => {
179
- document.documentElement.querySelector('#anchor')?.scrollIntoView({ behavior: 'smooth' });
179
+ document.documentElement.querySelector('#anchor')?.scrollIntoView({ behavior: 'instant' });
180
180
  });
181
181
 
182
182
  // canonical/hreflang выпускает app.vue — отсюда уходит только факт, который знает лишь пагинация
@@ -1,12 +1,32 @@
1
1
  let isTouchUsed = false;
2
- let isOutsideTouchWatched = false;
2
+ let isResetWatched = false;
3
3
 
4
4
  /** Хранит id карточки с активным превью: играет только одно, запуск нового гасит предыдущее. */
5
5
  export const useVideoPreview = () => {
6
6
  const activePreviewId = useState<string | null>('active-video-preview', () => null);
7
+ const router = useRouter();
8
+
9
+ const stopPreview = () => {
10
+ activePreviewId.value = null;
11
+ };
12
+
13
+ /**
14
+ * Ставится один раз при первом превью. Гасит его на любом касании страницы (слушатель на capture-фазе,
15
+ * поэтому тап по другой карточке сначала гасит текущее превью, а потом запускает своё) и на старте любого перехода —
16
+ * чтобы при уходе на страницу видео превью с индикатором не доживало до смены роута.
17
+ */
18
+ const watchPreviewReset = () => {
19
+ if (isResetWatched) return;
20
+ isResetWatched = true;
21
+
22
+ document.addEventListener('touchstart', stopPreview, { capture: true, passive: true });
23
+ router.beforeEach(stopPreview);
24
+ };
7
25
 
8
26
  const startPreviewByHover = (id: string) => {
9
27
  if (isTouchUsed) return;
28
+
29
+ watchPreviewReset();
10
30
  activePreviewId.value = id;
11
31
  };
12
32
 
@@ -16,23 +36,11 @@ export const useVideoPreview = () => {
16
36
  if (activePreviewId.value === id) activePreviewId.value = null;
17
37
  };
18
38
 
19
- /**
20
- * Гасит превью на любом касании страницы. Слушатель на capture-фазе, поэтому срабатывает раньше обработчика
21
- * карточки: тап по другой карточке сначала гасит текущее превью, а потом запускает своё, тап по пустому месту — только гасит.
22
- */
23
- const watchOutsideTouch = () => {
24
- if (isOutsideTouchWatched) return;
25
- isOutsideTouchWatched = true;
26
-
27
- document.addEventListener('touchstart', () => {
28
- activePreviewId.value = null;
29
- }, { capture: true, passive: true });
30
- };
31
-
32
39
  /** Тач-устройства дублируют тап синтетическими mouse-событиями — с первого касания ховерный путь глушим. */
33
40
  const startPreviewByTouch = (id: string) => {
34
41
  isTouchUsed = true;
35
- watchOutsideTouch();
42
+
43
+ watchPreviewReset();
36
44
  activePreviewId.value = id;
37
45
  };
38
46
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.850",
4
+ "version": "0.0.852",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {