PageSourceSearch

https://novayagazeta.eu/assets/scripts/lqip.js?v=04bf822e4263

js novayagazeta.eu collected 2026-09-24 07:52:46 UTC 3,578 bytes, 77 lines download raw bytes

1// Убирает LQIP-плейсхолдер ([data-lqip]) после загрузки настоящей картинки над ним.
2// После загрузки он полностью перекрыт, но его размытый увеличенный битмап всё
3// равно живёт отдельным композитным слоем на каждую карточку — на слабых GPU
4// Safari из-за этого не успевает растеризовать тайлы при скролле и показывает
5// пустые блоки.
6(function () {
7  const isLoaded = (img) => img.complete && img.naturalWidth > 0;
8
9  // Настоящая картинка — первый <img> в том же контейнере, не внутри плейсхолдера.
10  const realImageFor = (placeholder) => {
11    const box = placeholder.parentElement;
12    if (!box) return null;
13    for (const img of box.querySelectorAll("img")) {
14      if (!placeholder.contains(img)) return img;
15    }
16    return null;
17  };
18
19  // Обратный поиск: плейсхолдер — прямой ребёнок одного из ближайших предков картинки.
20  const placeholderFor = (img) => {
21    let node = img.parentElement;
22    for (let depth = 0; node && depth < 4; depth++) {
23      const found = node.querySelector(":scope > [data-lqip]");
24      if (found) return found;
25      node = node.parentElement;
26    }
27    return null;
28  };
29
30  // Two rAFs = wait for a real completed paint. On an overloaded compositor
31  // decode() alone can resolve before the frame it decoded for has actually
32  // been drawn to the screen (the first rAF just gets us to "next frame is
33  // scheduled", not "previous frame landed") — one more round trip forces us
34  // to wait until that paint has actually happened before we pull the
35  // placeholder out from under it.
36  const nextPaint = () =>
37    new Promise((resolve) =>
38      requestAnimationFrame(() => requestAnimationFrame(resolve)),
39    );
40
41  // img.complete goes true as soon as the bytes are downloaded, not once the
42  // pixels are actually decoded and paintable — on a slow device those can be
43  // seconds apart. Removing the placeholder on `complete` alone left the
44  // not-yet-decoded rows unblurred (visible as bare stripes while the image
45  // paints in). img.decode() resolves once it's ready to draw, but on a
46  // sufficiently backed-up compositor even that can land before the actual
47  // paint — nextPaint() covers that last gap.
48  const settle = async (placeholder) => {
49    const img = realImageFor(placeholder);
50    if (!img || !isLoaded(img)) return;
51    if (img.decode) {
52      try {
53        await img.decode();
54      } catch {
55        // decode() rejects if the image errors or is swapped mid-flight —
56        // still safe to drop the placeholder, there's nothing left to blur.
57      }
58    }
59    await nextPaint();
60    placeholder.remove();
61  };
62
63  // Картинки, догрузившиеся позже (lazy, htmx-вставки): load не всплывает, но
64  // ловится на capture-фазе.
65  document.addEventListener(
66    "load",
67    (e) => {
68      if (!(e.target instanceof HTMLImageElement)) return;
69      const placeholder = placeholderFor(e.target);
70      if (placeholder) settle(placeholder);
71    },
72    true,
73  );
74
75  // Картинки, успевшие загрузиться из кеша до того, как скрипт отработал.
76  document.querySelectorAll("[data-lqip]").forEach(settle);
77})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.