function firstEvent(element, name) { return new Promise(resolve => { element.addEventListener(name, resolve); }); } function gridCellDimensions() { const element = document.createElement("div"); element.style.position = "fixed"; element.style.height = "var(--line-height)"; element.style.width = "1ch"; document.body.appendChild(element); const rect = element.getBoundingClientRect(); document.body.removeChild(element); return { width: rect.width, height: rect.height }; } // Set the ratio variable on each media. function setRatios() { const cell = gridCellDimensions(); function onMediaLoaded(media) { var width, height; switch (media.tagName) { case "IMG": width = media.naturalWidth; height = media.naturalHeight; break; case "VIDEO": width = media.videoWidth; height = media.videoHeight; break; } if (width > 0 && height > 0) { const rect = media.getBoundingClientRect(); const ratio = width / height; const realHeight = rect.width / ratio; const diff = cell.height - (realHeight % cell.height); media.style.setProperty("padding-bottom", `${diff}px`); } } const medias = document.querySelectorAll("img, video"); for (media of medias) { switch (media.tagName) { case "IMG": if (media.complete) { onMediaLoaded(media); } else { media.addEventListener("load", () => onMediaLoaded(media)); media.addEventListener("error", function() { console.error(media); }); } break; case "VIDEO": switch (media.readyState) { case HTMLMediaElement.HAVE_CURRENT_DATA: case HTMLMediaElement.HAVE_FUTURE_DATA: case HTMLMediaElement.HAVE_ENOUGH_DATA: onMediaLoaded(media); break; default: media.addEventListener("loadeddata", () => onMediaLoaded(media)); media.addEventListener("error", function() { console.error(media); }); break; } break; } } } setRatios(); window.addEventListener("load", setRatios); window.addEventListener("resize", setRatios); function checkOffsets() { const ignoredTagNames = new Set([ "THEAD", "TBODY", "TFOOT", "TR", "TD", "TH", ]); const cell = gridCellDimensions(); const elements = document.querySelectorAll("body :not(.debug-grid, .debug-toggle)"); for (const element of elements) { if (ignoredTagNames.has(element.tagName)) { continue; } const rect = element.getBoundingClientRect(); if (rect.width === 0 && rect.height === 0) { continue; } const top = rect.top + window.scrollY; const left = rect.left + window.scrollX; const offset = top % (cell.height / 2); if(offset > 0) { element.classList.add("off-grid"); console.error("Incorrect vertical offset for", element, "with remainder", top % cell.height, "when expecting divisible by", cell.height / 2); } else { element.classList.remove("off-grid"); } } } const debugToggle = document.querySelector(".debug-toggle"); function onDebugToggle() { document.body.classList.toggle("debug", debugToggle.checked); } debugToggle.addEventListener("change", onDebugToggle); onDebugToggle();