class VisitAnalytics { landmarksObserver: IntersectionObserver; lazyloadObserver: IntersectionObserver; lastWidth: number = 0; resizeTimeout: number | null = null; images: HTMLImageElement[] = []; suggestionImages: HTMLImageElement[] = []; constructor() { (window as any).plausible = (window as any).plausible || function () { ((window as any).plausible.q = (window as any).plausible.q || []).push(arguments); }; // Hopefull this won't miss anything if (document.readyState == "interactive") this.init() else document.addEventListener("DOMContentLoaded", () => this.init(), false); } init() { this.landmarksObserver = new IntersectionObserver( (entries, observer) => this.handleLandmarks(entries, observer), { root: null, rootMargin: "0px", threshold: 1.0 } ); this.lazyloadObserver = new IntersectionObserver( (entries, observer) => this.handleLazyload(entries, observer), { root: null, rootMargin: "75% 0px 75% 0px", threshold: 0.1 } ); { var all = document.querySelectorAll(".chapter-announce"); all.forEach(elem => this.landmarksObserver.observe(elem)); this.landmarksObserver.observe(document.querySelector("footer")); } { var all = document.querySelectorAll(".image-post:not(.suggestion-post)"); all.forEach(elem => { this.lazyloadObserver.observe(elem); this.images.push(elem.querySelector(".post-image > img")); }); var suggestions = document.querySelectorAll(".image-post.suggestion-post"); all.forEach(elem => { this.lazyloadObserver.observe(elem); this.suggestionImages.push(elem.querySelector(".post-image > img")); }); this.resizeTimeout = setTimeout(() => this.resizeDebounced(), 200); window.addEventListener("resize", (event) => this.handleResize(event)); } console.log("Event handlers and observers ready"); } handleLandmarks(entries: IntersectionObserverEntry[], observer: IntersectionObserver) { entries.filter(e => e.isIntersecting).forEach(e => { const announcingPost = e.target.getAttribute("data-announcing-post"); if (announcingPost != null && !e.target.parentElement.querySelector(`#post-${announcingPost} img`).complete) return; if (window['plausible']) (window as any).plausible("landmark", {props: {id: e.target.id ? e.target.id : e.target.tagName}}); observer.unobserve(e.target); console.log("Reached landmark " + e.target.id ? e.target.id : e.target.tagName); } ); } resizeDebounced() { clearTimeout(this.resizeTimeout); this.resizeTimeout = null; const elem = document.querySelector(".post-content"); // Note: depends on main.css const elemWidth = Math.floor(elem.clientWidth * 0.95); const elemSuggestion = document.querySelector(".suggestion-post .post-content"); const elemSuggestionWidth = Math.floor((elemSuggestion?.clientWidth ?? 0) * 0.95); if (elemWidth != this.lastWidth) { this.lastWidth = elemWidth; this.images.forEach(img => { const naturalHeight = +img.getAttribute("data-height"); const naturalWidth = +img.getAttribute("data-width"); img.height = Math.floor(naturalHeight * Math.min(1, elemWidth / naturalWidth)); }) this.suggestionImages.forEach(img => { const naturalHeight = +img.getAttribute("data-height"); const naturalWidth = +img.getAttribute("data-width"); img.height = Math.floor(naturalHeight * Math.min(1, elemSuggestionWidth / naturalWidth)); }) } } handleResize(event) { if (event && event.type == "resize") { if (this.resizeTimeout != null) clearTimeout(this.resizeTimeout); this.resizeTimeout = setTimeout(() => this.resizeDebounced(), 200); } } handleLazyload(entries: IntersectionObserverEntry[], observer: IntersectionObserver) { entries.filter(e => e.isIntersecting).forEach(e => { const imgElem = e.target.querySelector(`img`); imgElem.addEventListener("load", () => { imgElem.classList.add("loaded"); }); imgElem.src = imgElem.getAttribute("data-src"); observer.unobserve(e.target); console.log(`Lazyloaded ${ e.target.id ? e.target.id : e.target.tagName }`); } ); } } new VisitAnalytics();