itube-specs 0.0.970 → 0.0.972

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.
@@ -56,13 +56,17 @@ const props = defineProps<{
56
56
  const fallbackImage = '/img/model-placeholder.webp';
57
57
  const imageError = ref(false);
58
58
 
59
+ /** Аватар 48px, крупнее мелкой ступени сюда не надо: при DPR3 нужно всего 144. */
59
60
  const currentImage = computed(() => {
60
61
  if (imageError.value) return fallbackImage;
61
- return props.card.primaryImageUrl || props.card.thumbUrl || fallbackImage;
62
+ return props.card.primaryImageSmallUrl || props.card.primaryImageUrl || props.card.thumbUrl || fallbackImage;
62
63
  });
63
64
 
64
65
  const currentImageUrls = computed(() => (imageError.value ? undefined : props.card.primaryImageUrls));
65
- const currentAvifSrc = computed(() => (imageError.value ? undefined : props.card.primaryImageAvifUrl));
66
+ const currentAvifSrc = computed(() => {
67
+ if (imageError.value) return undefined;
68
+ return props.card.primaryImageSmallAvifUrl || props.card.primaryImageAvifUrl;
69
+ });
66
70
  </script>
67
71
 
68
72
  <style lang="scss">
@@ -15,6 +15,7 @@
15
15
  <UiImg
16
16
  v-if="card.img"
17
17
  :src="currentImage"
18
+ :avif-src="currentAvifSrc"
18
19
  :image-urls="currentImageUrls"
19
20
  :alt="title"
20
21
  :sizes="IMG_SIZES"
@@ -217,6 +218,8 @@ const IMG_SIZES = `auto, (max-width: ${CSS_BREAKPOINTS.sm - 1}px) 80px, 96px`;
217
218
 
218
219
  const currentImageUrls = computed(() => (imageError.value ? undefined : props.card.imgUrls));
219
220
 
221
+ const currentAvifSrc = computed(() => (imageError.value ? undefined : props.card.imgAvifUrl));
222
+
220
223
  function getParameter(name: string) {
221
224
  return props.card.parameters?.find(p => p.name === name)?.values?.[ 0 ]?.name;
222
225
  }
@@ -11,6 +11,7 @@
11
11
  <UiImg
12
12
  v-if="isModel"
13
13
  :src="item?.primaryImageUrl || `/img/model-placeholder.webp`"
14
+ :avif-src="item?.primaryImageAvifUrl"
14
15
  :image-urls="item?.primaryImageUrls"
15
16
  :alt="title"
16
17
  class="search-item__icon"
@@ -2,27 +2,32 @@ export interface ITestModelPortrait {
2
2
  primaryImageUrl: string;
3
3
  thumbUrl: string;
4
4
  primaryImageAvifUrl?: string;
5
+ primaryImageSmallUrl?: string;
6
+ primaryImageSmallAvifUrl?: string;
5
7
  }
6
8
 
7
9
  const PORTRAIT_COUNT = 16;
8
10
 
9
11
  /**
10
- * Тестовые портреты моделей из `public/img/models/`. Везде один файл 640w:
12
+ * Тестовые портреты моделей из `public/img/models/`. `?img=png` отдаёт исходники для
13
+ * сравнения, без параметра — нарезку: avif через `<picture>`, фолбэк webp.
11
14
  *
12
- * - `?img=png` — исходники;
13
- * - `?img=webp` — только webp, без avif-ветки;
14
- * - без параметра — avif q63 4:4:4 через `<picture>`, фолбэк webp `-q 85 -m 6 -pass 10 -sharp_yuv`.
15
+ * Две ширины, выбирает их КОМПОНЕНТ, а не браузер: srcset мы не ставим. 640w идёт в карточку
16
+ * сетки, 300w во всё мелкое — круглые аватары, мини-карточки, подсказки поиска. Качество
17
+ * везде одно: webp q85 / avif q63.
15
18
  *
16
- * Качество ступеней подобрано по равной ошибке в высоких частотах: webp q85 даёт 0.00405,
17
- * avif q63 — 0.00399. Вес при этом 645 против 434 KB на 16 картинок, то есть avif легче на
18
- * треть при том же качестве. Разрыв держится на всём диапазоне, от точки не зависит.
19
- *
20
- * Один файл, а НЕ ступени через srcset. Исходники 640, слот карточки максимум 297 CSS, так
21
- * что 640w закрывает любое устройство вплоть до DPR3. Ступень мельче слота при этом заведомо
22
- * хуже полного файла: её портит двойной пересчёт (наш ресайз плюс браузерный), а её артефакты
19
+ * Ступеней через srcset НЕ делаем. Исходники 640, слот карточки максимум 297 CSS, так что
20
+ * 640w закрывает любое устройство вплоть до DPR3. Ступень мельче слота при этом заведомо хуже
21
+ * полного файла: её портит двойной пересчёт (наш ресайз плюс браузерный), а её артефакты
23
22
  * показываются почти 1:1, тогда как 640w в том же слоте ужимается в 2.6 раза и они
24
- * размазываются. Проверено на DPR1-мониторе: ладдер мылит, один файл нет. Ступени станут
25
- * осмысленны, только если исходники вырастут заметно выше 640.
23
+ * размазываются. Проверено на DPR1-мониторе: ладдер мылит, один файл нет.
24
+ *
25
+ * На мелкой ступени при разглядывании видна лёгкая разница с исходником, и это не брак
26
+ * генерации. Кажется, что при слоте 96px и файле 300w запас трёхкратный, но браузер уменьшает
27
+ * через мип-уровни делением пополам: реально показывается уровень 150, то есть уменьшение
28
+ * всего в 1.5 раза, и артефакты сжатия почти не размываются. Полностью лечится только отдачей
29
+ * туда полного 640w — на глаз идеально, но 27 KB на аватар 48–96px перебор, плюс Lighthouse
30
+ * справедливо ругается на такой перерасход. Осознанный размен.
26
31
  *
27
32
  * Кроп под 3:4 не делаем — обрезает `object-fit`. Почему именно так, см. `IModelImageUrls`.
28
33
  *
@@ -30,19 +35,21 @@ const PORTRAIT_COUNT = 16;
30
35
  * по query. Для замера сети это и нужно: он честен только с чистого захода.
31
36
  */
32
37
  export function useTestModelImages() {
33
- const format = useRoute().query['img'];
38
+ const isSource = useRoute().query['img'] === 'png';
34
39
 
35
40
  const portraits: ITestModelPortrait[] = Array.from({ length: PORTRAIT_COUNT }, (_, index) => {
36
41
  const base = `/img/models/portrait-${index + 1}`;
37
42
 
38
- if (format === 'png') {
43
+ if (isSource) {
39
44
  return { primaryImageUrl: `${base}.png`, thumbUrl: `${base}.png` };
40
45
  }
41
46
 
42
47
  return {
43
- primaryImageUrl: `${base}-q85.webp`,
44
- thumbUrl: `${base}-q85.webp`,
45
- primaryImageAvifUrl: format === 'webp' ? undefined : `${base}-q63.avif`,
48
+ primaryImageUrl: `${base}-640.webp`,
49
+ thumbUrl: `${base}-300.webp`,
50
+ primaryImageSmallUrl: `${base}-300.webp`,
51
+ primaryImageAvifUrl: `${base}-640.avif`,
52
+ primaryImageSmallAvifUrl: `${base}-300.avif`,
46
53
  };
47
54
  });
48
55
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.970",
4
+ "version": "0.0.972",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -5,9 +5,10 @@ import { convertString } from './convert-string';
5
5
  export function convertModelToRoundCard(model: IModelCard): IRoundCard {
6
6
  return {
7
7
  title: model.title,
8
- primaryImageUrl: model.primaryImageUrl || model.thumbUrl || '',
8
+ // Круглому аватару крупнее мелкой ступени не нужно: 96px при DPR3 просит 288.
9
+ primaryImageUrl: model.primaryImageSmallUrl || model.primaryImageUrl || model.thumbUrl || '',
9
10
  primaryImageUrls: model.primaryImageUrls,
10
- primaryImageAvifUrl: model.primaryImageAvifUrl,
11
+ primaryImageAvifUrl: model.primaryImageSmallAvifUrl || model.primaryImageAvifUrl,
11
12
  videosCount: model.videosCount,
12
13
  link: `/models/${convertString().toSlug(model.title)}`,
13
14
  };
@@ -8,6 +8,8 @@ export interface ICardInfo {
8
8
  description: string;
9
9
  videosCount: number;
10
10
  img?: string;
11
+ /** Тот же кадр в avif. Есть — рендерится через `<picture>`, `img` фолбэком. */
12
+ imgAvifUrl?: string;
11
13
  /** Ладдер портрета под srcset. Нет — рендерится один `img`. */
12
14
  imgUrls?: IModelImageUrls;
13
15
  guid?: string;
@@ -7,6 +7,9 @@ export interface IModelCard {
7
7
  primaryImageUrl?: string;
8
8
  /** Тот же портрет в avif. Есть — карточка отдаёт его через `<picture>`, webp фолбэком. */
9
9
  primaryImageAvifUrl?: string;
10
+ /** Мелкая ступень 300w: круглые аватары, мини-карточки, подсказки поиска. */
11
+ primaryImageSmallUrl?: string;
12
+ primaryImageSmallAvifUrl?: string;
10
13
  /** Ладдер портрета под srcset. Нет — карточка рендерит один `primaryImageUrl`. */
11
14
  primaryImageUrls?: IModelImageUrls;
12
15
  guid: string;
@@ -5,6 +5,7 @@ export interface ISearchTopModelsType {
5
5
  name: string
6
6
  title: string
7
7
  primaryImageUrl?: string
8
+ primaryImageAvifUrl?: string
8
9
  /** Ладдер портрета под srcset. Нет — рендерится один `primaryImageUrl`. */
9
10
  primaryImageUrls?: IModelImageUrls
10
11
  }