UNPKG

drab

Version:

Interactivity for You

128 lines (127 loc) 5.58 kB
import { Lifecycle, Trigger } from "../base/index.js"; /** * The `Prefetch` element can prefetch a url, or enhance the `HTMLAnchorElement` by loading the HTML for a page before it is navigated to. This element speeds up the navigation for multi-page applications (MPAs). * * If you are using a framework that already has a prefetch feature or uses a client side router, it is best to use the framework's feature instead of this element to ensure prefetching is working in accordance with the router. * * ### Attributes * * `strategy` * * Set the `strategy` attribute to specify the when the prefetch will take place. * * - `"hover"` - (default) After `mouseover`, `focus`, or `touchstart` for > 200ms * - `"visible"` - Uses an intersection observer to prefetch when the anchor is within the viewport. * - `"load"` - Immediately prefetches when the element is loaded, use carefully. * * `prerender` * * Use the `prerender` attribute to use the Speculation Rules API when supported to prerender on the client. This allows you to run client side JavaScript in advance instead of only fetching the HTML. * * Browsers that do not support will still use `<link rel="prefetch">` instead. * * [Speculation Rules Reference](https://developer.mozilla.org/en-US/docs/Web/API/Speculation_Rules_API) * * `url` * * Add a `url` attribute to immediately prefetch a url without having to provide * (or in addition to) `trigger` anchor elements. * * This element can be deprecated once the Speculation Rules API is supported across browsers. The API will be able to prefetch assets in a similar way with the `source: "document"` and `eagerness` features, and will work without JavaScript. */ export class Prefetch extends Lifecycle(Trigger()) { #prefetchedUrls = new Set(); constructor() { super(); } /** When to prefetch the url. */ get #strategy() { return this.getAttribute("strategy"); } /** Prerender with the Speculation Rules API. */ get #prerender() { return this.hasAttribute("prerender"); } /** `url` to prefetch on `mount`. */ get #url() { return this.getAttribute("url"); } /** * Appends `<link rel="prefetch">` or `<script type="speculationrules">` * to the head of the document. * * @param options Configuration options. */ prefetch(options) { const { url } = options; // if not the current page and not already prefetched if (!(url === window.location.href) && !this.#prefetchedUrls.has(url)) { this.#prefetchedUrls.add(url); if (HTMLScriptElement.supports && HTMLScriptElement.supports("speculationrules")) { const rules = { // Currently, adding `prefetch` is required to fallback if `prerender` fails. // Possibly will be automatic in the future, in which case it can be removed. // https://github.com/WICG/nav-speculation/issues/162#issuecomment-1977818473 prefetch: [{ source: "list", urls: [url] }], }; if (options.prerender) rules.prerender = rules.prefetch; const script = document.createElement("script"); script.type = "speculationrules"; script.textContent = JSON.stringify(rules); document.head.append(script); } else { const link = document.createElement("link"); link.rel = "prefetch"; link.as = "document"; link.href = url; document.head.append(link); } } } mount() { // immediately prefetch the `url` attribute if it exists if (this.#url) this.prefetch({ url: this.#url, prerender: this.#prerender }); // prefetch the `trigger` elements const anchors = this.triggers(HTMLAnchorElement); const prerender = this.#prerender; const strategy = this.#strategy; let prefetchTimer; const listener = (delay = 200) => (e) => { const { href } = e.currentTarget; prefetchTimer = setTimeout(() => this.prefetch({ url: href, prerender }), delay); }; const reset = () => clearTimeout(prefetchTimer); const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { this.prefetch({ url: entry.target.href, prerender, }); } } }); for (const anchor of anchors) { if (strategy === "load") { this.prefetch({ url: anchor.href, prerender }); } else if (strategy === "visible") { observer.observe(anchor); } else { // "hover" - default anchor.addEventListener("mouseover", listener()); anchor.addEventListener("mouseout", reset); anchor.addEventListener("focus", listener()); anchor.addEventListener("focusout", reset); // immediately append on touchstart, no delay // passive: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners anchor.addEventListener("touchstart", listener(0), { passive: true }); } } } }