Listener System
How _Listener({ svg, lazyload }) sets up global IntersectionObserver instances for image lazyload and SVG inlining.
Entry Point
window._Listener (src/prototype/window.ts) is the single entry point. Calling it with no argument enables both observers; calling it with an object gates each observer independently — target.svg controls whether setLazyloadListener() runs, and target.lazyload controls whether setSvgListener() runs. Both setup functions (src/listener/LazyloadListener.ts, src/listener/SVGListener.ts) are idempotent: each checks a module-level lazyloadListener/svgListener flag and returns immediately if already initialized, so calling _Listener multiple times is safe.
The RJS constructor (src/model/RJS.ts) also calls $window._Listener(body[listener] || {}) on every instantiation, so any page using RJS gets the listener(s) configured by that instance's listener option.
graph TB
subgraph Listener
Init["_Listener(target)"] --> SVGGate{"target.lazyload?"}
Init --> LazyGate{"target.svg?"}
SVGGate -->|true| SVGObs["setSvgListener(): new IntersectionObserver"]
LazyGate -->|true| LazyObs["setLazyloadListener(): new IntersectionObserver"]
LazyObs --> LoadImg["img[lazyload] enters viewport → fetch + swap src"]
SVGObs --> ReplaceSVG["temp-svg[src] enters viewport → fetch + inline <svg>"]
end
Lazyload Observer
setLazyloadListener() creates one IntersectionObserver and immediately attaches it to every img[lazyload]:not([lazyload='']) already in document.body. New img elements built via "img"._({ lazyload: url }) (see API Reference) register with the same observer as soon as they're created, provided the observer already exists.
When a lazyload image intersects the viewport:
- The observer unobserves it immediately (one-shot).
- It reads the
lazyloadattribute as the real image URL. url.$$200(true)fetches the URL and resolves an object-URL blob for the image.- On success,
img.srcis set to the resolved blob URL; on failure,img.srcfalls back to a built-in inline "photo unavailable" SVG data URI (photo404insrc/data.ts). - The
lazyloadattribute is removed either way.
SVG Inlining Observer
setSvgListener() creates a second IntersectionObserver and attaches it to every temp-svg[src]:not([src='']) element already present. When a matched element intersects:
src.$$200()fetches the SVG URL (without forcing image-blob conversion, since SVG responses are handled as text).- The fetched text is parsed into a detached
<div>, and its first child (the<svg>element) is extracted. - The new
<svg>inherits the placeholder'sid, every class from itsclassList, and itsonclickhandler. - The
<svg>is inserted immediately before the placeholder, which is then removed. - On any failure (fetch or parse), the placeholder's
innerHTMLis replaced with☒.
Re-entrancy During RJS Rendering
RJS.ts's #SET_DOM also observes newly-created child elements inline as it walks the DOM tree: any child carrying the lazyload class is registered with lazyloadObserver, and any SPAN carrying the svg class is registered with svgObserver — this is what lets elements produced by :for loops or :path-loaded fragments participate in lazyload/SVG inlining without a separate _Listener() call per fragment.