- case-media-slider: обрамление+hover как у карточек, стрелки только при наведении (fade-in), группа пред/след-пилюля в правом нижнем углу, disabled-кнопки видны (не скрываются), убран border-radius у img/figcaption (скругление у viewport), неактивные слайды не скрываются visibility (нет 'дырки' при перелистывании), настраиваемая ширина --wide/--content/--natural - image-load-placeholder: исключён .case-media-slider (нет блика/серого фона) - icon-press: универсальный эффект 'продавливания' иконки при нажатии (.icon-press + :active/.is-pressed), применён к слайдеру и всем кнопкам просмотрщика
75 lines
2.4 KiB
JavaScript
75 lines
2.4 KiB
JavaScript
import { isTinyPlaceholderSrc } from "../shared/image-src.js";
|
|
|
|
const HOST_SELECTOR =
|
|
".case-media, .case-media-gallery__item, .case-link-card__media, .case-media-tall__clip, .mp-card__media, .portfolio-card__cover, figure";
|
|
|
|
const EXCLUDE_ANCESTOR = ".contact-card__icon, .cookie-notice, .case-image-viewer, .case-media-slider, [data-no-img-placeholder]";
|
|
|
|
function shouldWatch(img) {
|
|
if (!(img instanceof HTMLImageElement)) return false;
|
|
if (img.closest(EXCLUDE_ANCESTOR) || img.hasAttribute("data-no-img-placeholder")) return false;
|
|
const src = (img.getAttribute("src") || "").toLowerCase();
|
|
if (src.endsWith(".svg")) return false;
|
|
const w = Number(img.getAttribute("width"));
|
|
const h = Number(img.getAttribute("height"));
|
|
if (w > 0 && h > 0 && Math.max(w, h) <= 40) return false;
|
|
return true;
|
|
}
|
|
|
|
function isPending(img) {
|
|
if (img.hasAttribute("data-src") || img.hasAttribute("data-defer-src")) return true;
|
|
const src = img.currentSrc || img.getAttribute("src") || "";
|
|
if (isTinyPlaceholderSrc(src)) return true;
|
|
return !img.complete || img.naturalWidth === 0;
|
|
}
|
|
|
|
function getHost(img) {
|
|
const host = img.closest(HOST_SELECTOR);
|
|
if (host) return host;
|
|
const parent = img.parentElement;
|
|
if (parent && parent !== document.body && parent !== document.documentElement) return parent;
|
|
return null;
|
|
}
|
|
|
|
function setLoading(img, loading) {
|
|
const host = getHost(img);
|
|
if (!host) return;
|
|
if (loading) {
|
|
img.classList.add("is-img-loading");
|
|
host.classList.add("has-img-loading");
|
|
} else {
|
|
img.classList.remove("is-img-loading");
|
|
if (!host.querySelector("img.is-img-loading")) host.classList.remove("has-img-loading");
|
|
}
|
|
}
|
|
|
|
function syncImage(img) {
|
|
if (!shouldWatch(img)) {
|
|
setLoading(img, false);
|
|
return;
|
|
}
|
|
setLoading(img, isPending(img));
|
|
}
|
|
|
|
function bindImage(img) {
|
|
if (!shouldWatch(img)) return;
|
|
if (img.dataset.imgLoadPhBound === "1") {
|
|
syncImage(img);
|
|
return;
|
|
}
|
|
img.dataset.imgLoadPhBound = "1";
|
|
|
|
const onChange = () => syncImage(img);
|
|
img.addEventListener("load", onChange);
|
|
img.addEventListener("error", onChange);
|
|
|
|
const mo = new MutationObserver(onChange);
|
|
mo.observe(img, { attributes: true, attributeFilter: ["src", "data-src", "data-defer-src"] });
|
|
|
|
syncImage(img);
|
|
}
|
|
|
|
export function initImageLoadPlaceholder() {
|
|
document.querySelectorAll("img").forEach(bindImage);
|
|
}
|