---
name: boostify
description: Use when working with Boostify — a JavaScript toolkit that defers scripts, loads resources on demand, and reduces main-thread blocking. Invoke when the user asks about Boostify API, wants to defer GTM/GA/Stripe/HubSpot/Intercom, lazy-load scripts/styles/videos, or reduce page weight.
---

# Boostify

Boostify is a lightweight JS library for deferred resource loading. Install once, then use triggers to decide when resources load.

---

## How to use this skill

When a user asks about Boostify or wants to implement it, **do not dump the full documentation**. Instead, follow this flow:

**Step 1 — Ask what they're trying to do:**
> "What are you trying to defer or lazy-load? (e.g. Google Analytics, a video, a slider, a modal, a payment SDK…)"

**Step 2 — Ask about their context:**
> "What's your setup? Are you working in plain HTML, a framework (React, Vue, Astro…)? Do you already have Boostify installed?"

**Step 3 — Recommend the right API:**
Based on their answers, pick the right trigger + content injection combination and give them a single, copy-paste ready example tailored to their exact case. Reference only the relevant section of this skill — don't show everything.

**Decision guide:**
- Third-party tracking script (GA, GTM, Pixel, Hotjar…) → `type="text/boostify"` + `onload`
- Load something when user scrolls to a section → `scroll()` with the right `distance`
- Load something when an element enters the viewport → `observer()`
- Load something only when user clicks a button → `click()`
- Show a prompt / auto-logout after idle time → `inactivity()`
- Inject a JS library on demand → `loadScript()` inside a trigger
- Inject CSS on demand → `loadStyle()` inside a trigger
- Lazy YouTube / Vimeo iframe → `videoEmbed()` inside `click()` or `observer()`
- Lazy self-hosted video → `videoPlayer()` inside `click()` or `observer()`

---

## Install

```html
<script src="https://unpkg.com/boostify@latest/dist/Boostify.umd.js"></script>
<script>
  var bstf = new Boostify({ debug: true }); // debug: true logs all events to console
  bstf.onload({ worker: true });
</script>
```

---

## Drop-in deferral — `type="text/boostify"` & `onload`

**Primary use case: third-party tracking scripts.** Google Analytics, Google Tag Manager, Facebook Pixel, Hotjar, Microsoft Clarity, LinkedIn Insight, TikTok Analytics — these scripts block your main thread and delay your content. `type="text/boostify"` defers them until the user is actually engaged with the page, with zero refactoring.

Change the `type` attribute — nothing else:

```html
<!-- ❌ Before: blocks page load -->
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"></script>

<!-- ✅ After: loads after page is interactive -->
<script type="text/boostify" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"></script>
```

Inline scripts work the same way:

```html
<script type="text/boostify">
  window.dataLayer = window.dataLayer || [];
  window.gtag = function(){ dataLayer.push(arguments); }
  gtag('js', new Date());
  gtag('config', 'G-XXXXX');
</script>
```

Scripts fire when the user first interacts — mousemove, scroll, touchstart — or after `maxTime` as a fallback.

**Full example with Google Analytics:**

```html
<head>
  <script type="text/boostify" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX"></script>
  <script type="text/boostify">
    window.dataLayer = window.dataLayer || [];
    window.gtag = function(){ dataLayer.push(arguments); }
    gtag('js', new Date());
    gtag('config', 'G-XXXXX');
  </script>
</head>
<body>
  <!-- your content -->
  <script src="https://unpkg.com/boostify@latest/dist/Boostify.umd.js"></script>
  <script>
    var bstf = new Boostify({ debug: true });
    bstf.onload({ worker: true });
  </script>
</body>
```

**`onload` arguments:**

```js
bstf.onload({
  worker: true,
  maxTime: 2000,
  eventsHandler: ['mousemove', 'scroll', 'touchstart'],
  callback: (result) => {
    // result.success     — boolean
    // result.method      — 'worker' | 'traditional'
    // result.triggeredBy — 'mousemove' | 'scroll' | 'touchstart' | 'timeout'
    // result.loadTime    — ms elapsed
    // result.scripts     — array of { url, success, type, proxied }
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `worker` | `false` | `true` = load scripts through Boostify's proxy in a separate thread — main thread never touches them. Supported: GTM, GA, Meta Pixel, Hotjar, Clarity, LinkedIn, TikTok, jsDelivr, unpkg, cdnjs. Unsupported domains fall back to direct loading automatically. |
| `maxTime` | `600` | Milliseconds before loading regardless of user interaction — prevents scripts never loading on bounce traffic. |
| `eventsHandler` | `['mousemove', 'load', 'scroll', 'touchstart']` | User events that trigger loading. |
| `callback` | `null` | Called once all scripts finish loading, with a result object describing what happened. |

---

## Trigger Events

Triggers decide **when** content loads. Always put the actual loading inside the callback.

### `bstf.scroll()` — fire at a scroll distance

**Primary use case: staggered loading at different scroll depths.** Use multiple `scroll()` calls with different `distance` values to load resources progressively — charts at 300px, a slider at 600px, comments at 1200px. Avoids loading everything at once.

```js
bstf.scroll({
  distance: 300,
  callback: async () => {
    // load something when user reaches 300px
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `distance` | `100` | Pixels from top of page at which the callback fires. |
| `name` | — | Optional. Use when two triggers share the same distance and you need to tell them apart. |
| `callback` | — | Async function called once when the distance is reached. |

**Destroy a scroll trigger:**

```js
bstf.destroyscroll({ distance: 300 });
// If two triggers share the same distance, target by name:
bstf.destroyscroll({ distance: 300, name: 'B' });
```

**Multiple triggers at the same distance** — no conflict, both fire:

```js
bstf.scroll({ distance: 300, name: 'A', callback: async () => { /* ... */ } });
bstf.scroll({ distance: 300, name: 'B', callback: async () => { /* ... */ } });
```

**Real-world example — load Tiny Slider at 200px:**

```js
bstf.scroll({
  distance: 200,
  callback: async () => {
    await bstf.loadStyle({
      url: 'https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.4/tiny-slider.css',
      appendTo: 'head',
    });
    await bstf.loadScript({
      url: 'https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.2/min/tiny-slider.js',
      appendTo: 'body',
    });
    tns({ container: '.my-slider', items: 3, slideBy: 'page', autoplay: true });
  },
});
```

**Recommendation:** use different `distance` values for different sections — not a single trigger for everything.

---

### `bstf.click()` — fire on element click

**Primary use case: load heavy resources only when the user explicitly asks for them.** Modals, payment SDKs, sliders, video players — attach the cost to the click, not the page load. Zero bytes downloaded until the user actually interacts.

```js
bstf.click({
  element: document.getElementById('js--fire'),
  callback: async () => {
    // runs only when the element is clicked
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `element` | `document.querySelector('.js--click-boostify')` | DOM element (or array of DOM elements) to attach the click listener to. |
| `callback` | — | Async function executed when the click fires. |

**Destroy a click trigger:**

```js
bstf.destroyclick({ element: document.getElementById('js--fire') });
```

**Load-once pattern** — import runs once, subsequent clicks reuse the cached module:

```js
let modal = null;
bstf.click({
  element: document.getElementById('open-modal'),
  callback: async () => {
    modal ??= (await import('./modal-lib.js')).modal;
    modal.show();
  },
});
```

**Real-world example — load Tiny Slider on click:**

```js
bstf.click({
  element: document.getElementById('js--click-boostify'),
  callback: async () => {
    await bstf.loadStyle({
      url: 'https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.4/tiny-slider.css',
      appendTo: 'head',
    });
    await bstf.loadScript({
      url: 'https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.2/min/tiny-slider.js',
      appendTo: 'body',
    });
    tns({ container: '.my-slider', items: 3, slideBy: 'page', autoplay: true });
  },
});
```

---

### `bstf.observer()` — fire when element enters viewport

**Primary use case: load content, ads, or widgets only when they become visible.** Infinite scroll, lazy-loaded sliders, third-party widgets that only matter if the user reaches that section. Resources stay at zero bytes until the element is on screen.

```js
bstf.observer({
  element: document.getElementById('target'),
  callback: async () => {
    // runs when element enters the viewport
  },
  options: {
    root: null,         // null = browser viewport
    rootMargin: '0px',  // CSS margin syntax — expand/shrink the trigger area
    threshold: 0.5,     // 0.5 = fire when 50% of element is visible
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `element` | `document.querySelector('.js--observer-boostify')` | DOM element to observe. |
| `callback` | — | Async function executed when visibility crosses the threshold. |
| `options.root` | `null` | `null` = browser viewport. Pass a DOM element to observe within a scrollable container. |
| `options.rootMargin` | `'0px'` | CSS margin around the root — e.g. `'100px'` triggers 100px before the element enters view. |
| `options.threshold` | `0.01` | `0` to `1`. `0.01` = fires when 1% visible. `1.0` = fires only when fully visible. Array for multiple thresholds. |

**Destroy an observer:**

```js
bstf.destroyobserver({ element: document.querySelector('.observed-element') });
```

**Refresh an observer** — if the page height changes (dynamic content, accordions, tabs), call this to reset:

```js
bstf.refreshObserverEvents({ element: document.querySelector('.observed-element') });
```

> **Important:** if your page can change height after load, always call `refreshObserverEvents` after the layout change. The observer won't reposition itself automatically.

---

### `bstf.inactivity()` — fire when user is idle

**Use with caution — the least convenient trigger but very useful for the right cases.** Best for: re-engagement prompts, auto-logout, pausing resource-intensive operations, deferring background tasks.

Two modes:
- **User Idle Mode** — fires after N ms of no activity (`idleTime` provided)
- **Native Idle Mode** — fires when the browser itself is idle via `requestIdleCallback` (`idleTime` omitted)

```js
// User Idle Mode — fires after 5s of no interaction
bstf.inactivity({
  idleTime: 5000,
  name: 'my-instance',
  callback: () => {
    document.getElementById('idle-message').style.display = 'block';
  },
});

// Native Idle Mode — fires when browser has spare time
bstf.inactivity({
  name: 'background-tasks',
  maxTime: 3000,
  callback: () => {
    performBackgroundSync();
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `callback` | — | Required. Function executed when idle is detected. |
| `idleTime` | — | Ms of no user activity before firing. Omit to use native browser idle detection. Must be less than `maxTime`. |
| `maxTime` | `2000` | Hard deadline in ms — callback fires regardless. Safety net to guarantee execution. |
| `events` | `['mousemove','scroll','keydown','mousedown','mouseup','click','touchstart','touchend']` | Events that reset the idle timer. Pass `'none'` to disable (native idle only). |
| `name` | — | Required when you need to destroy a specific instance. |
| `debug` | `false` | Logs events to console. |

**Destroy** (`name` is required):

```js
bstf.destroyinactivity({ name: 'my-instance' });
```

**Auto-logout example:**

```js
const startMonitor = () => {
  bstf.inactivity({
    idleTime: 300000, // 5 minutes
    maxTime: 310000,
    name: 'auto-logout',
    callback: () => {
      if (confirm('You have been inactive. Stay logged in?')) {
        bstf.destroyinactivity({ name: 'auto-logout' });
        startMonitor();
      } else {
        window.location.href = '/logout';
      }
    },
  });
};
startMonitor();
```

**Important constraints:**
- `idleTime` must be less than `maxTime` — if not, Boostify auto-adjusts to `maxTime - 100ms`
- `name` is required to call `destroyinactivity`
- Don't set `idleTime` too short — causes unnecessary callback executions

---

## Content Injection

Content injection APIs load resources into the DOM on demand. **Always call them inside a trigger callback** — called alone they fire at page open, identical to a plain `<script>` or `<link>` tag but with Boostify runtime overhead on top.

### `bstf.loadScript()` — inject a `<script>` tag on demand

**Primary use case: load a third-party library or SDK only when it's actually needed** — complex charts, payment SDKs, interactive widgets. Avoids paying the download and parse cost on page load for features the user may never reach.

```js
// Load from URL
await bstf.loadScript({
  url: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js',
  appendTo: 'head',
  attributes: ['id=jquery', 'data-test=1'],
});

// Inline script
await bstf.loadScript({
  inlineScript: `console.log('inline execution');`,
  appendTo: 'head',
});
```

`loadScript` returns a promise — always use `await` inside an `async` callback:

```js
bstf.scroll({
  distance: 300,
  callback: async () => {
    try {
      await bstf.loadScript({
        url: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js',
        appendTo: 'head',
      });
      // jQuery is ready here
    } catch (error) {
      console.error(error);
    }
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `url` | — | URL of the script to load. Use this or `inlineScript`, not both. |
| `inlineScript` | — | JavaScript string to execute inline instead of loading from a URL. |
| `attributes` | — | Array of strings added as attributes on the `<script>` element — e.g. `['id=my-script', 'data-foo=bar', 'async']`. |
| `appendTo` | `'head'` | Where to append the script. `'head'`, `'body'`, or a DOM element reference. |

### `bstf.loadStyle()` — inject a `<link>` or `<style>` tag on demand

**Primary use case: load component-specific CSS only when that component is needed.** Keeps the critical CSS path lean — styles for modals, sliders, or below-the-fold sections don't block first paint.

```js
// Load from URL
await bstf.loadStyle({
  url: 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css',
  appendTo: 'head',
  attributes: ['id=bootstrap', 'data-test=2'],
});

// Inline CSS
await bstf.loadStyle({
  inlineStyle: 'h2 { background-color: coral; }',
  appendTo: 'head',
});
```

`loadStyle` returns a promise — always use `await` inside an `async` callback:

```js
bstf.observer({
  element: document.getElementById('slider'),
  callback: async () => {
    try {
      await bstf.loadStyle({
        url: 'https://cdnjs.cloudflare.com/ajax/libs/tiny-slider/2.9.4/tiny-slider.css',
        appendTo: 'head',
      });
      // CSS is applied here
    } catch (error) {
      console.error(error);
    }
  },
});
```

| Option | Default | Description |
|--------|---------|-------------|
| `url` | — | URL of the stylesheet to load. Use this or `inlineStyle`, not both. |
| `inlineStyle` | — | CSS string injected in a `<style>` tag instead of loading from a URL. |
| `attributes` | — | Array of strings added as attributes on the element — e.g. `['id=my-style', 'media=print']`. |
| `appendTo` | `'head'` | Where to append. `'head'`, `'body'`, or a DOM element reference. |

### `bstf.videoEmbed()` — lazy YouTube / Vimeo iframe

**Primary use case: defer the ~320KB iframe payload until the user actually wants to watch.** YouTube and Vimeo iframes load tracking scripts, cookies, and heavy JS on page open — even if the user never clicks play. `videoEmbed` keeps the iframe out of the DOM entirely until a trigger fires.

**Always pair with a trigger.** Called alone, `videoEmbed` fires at page open — identical to a plain `<iframe>` but with Boostify overhead on top.

```html
<!-- Placeholder: same aspect ratio as the video to prevent CLS -->
<div class="js--boostify-embed" style="aspect-ratio: 16/9;"
     data-url="https://www.youtube.com/embed/1y_kfWUCFDQ">
</div>
```

```js
// Inject iframe when element enters viewport
document.querySelectorAll('.js--boostify-embed').forEach(element => {
  bstf.observer({
    element: element,
    callback: () => {
      bstf.videoEmbed({
        url: element.getAttribute('data-url'),
        autoplay: true,
        appendTo: element,
        style: { height: 'auto' },
      });
    },
  });
});
```

Note: use `https://www.youtube.com/embed/VIDEO_ID` format (not the watch URL).

| Option | Default | Description |
|--------|---------|-------------|
| `url` | — | Embed URL of the video (`youtube.com/embed/ID` or `player.vimeo.com/video/ID`). |
| `autoplay` | `false` | Adds `autoplay=1` to the iframe src. |
| `style` | `{ width: '100%', height: '100%' }` | CSS object applied to the iframe — e.g. `{ height: 'auto' }`. |
| `attributes` | — | Array of strings added as attributes on the `<iframe>` element. |
| `appendTo` | `'head'` | Element to append the iframe to. Pass the placeholder DOM element directly. |

> **CLS tip:** give the placeholder the same `aspect-ratio` as the video (`16/9` for standard YouTube). When the iframe injects, it fills the same space and the layout doesn't shift.

### `bstf.videoPlayer()` — lazy native `<video>` tag

**Primary use case: defer self-hosted video download until the user actually needs it.** A video file starts downloading as soon as a `<video>` tag hits the DOM — even with `autoplay` off. `videoPlayer` keeps the tag out of the DOM entirely until a trigger fires, so bandwidth stays at zero until the right moment.

**Always pair with a trigger.** Called alone, `videoPlayer` fires at page open — same result as a plain `<video>` tag.

```html
<!-- Placeholder: same aspect ratio as the video to prevent CLS -->
<div class="js--boostify-player" style="aspect-ratio: 16/9;"
     data-url-mp4="path/to/video.mp4">
</div>
```

```js
// Inject video when element enters viewport
document.querySelectorAll('.js--boostify-player').forEach(element => {
  bstf.observer({
    element: element,
    callback: () => {
      bstf.videoPlayer({
        url: {
          mp4: element.getAttribute('data-url-mp4'),
        },
        attributes: {
          loop: true,
          muted: true,
          controls: true,
          autoplay: true,
          playsinline: true,
        },
        appendTo: element,
      });
    },
  });
});
```

**Best practice:** store the video URL (and any config) as `data-*` attributes on the placeholder element. Keeps the JS generic and works with any number of players on the page.

| Option | Default | Description |
|--------|---------|-------------|
| `url.mp4` | — | URL of the MP4 video file. |
| `url.ogg` | — | URL of the Ogg video file (for cross-browser fallback). |
| `attributes.loop` | — | `true` = video loops continuously. |
| `attributes.muted` | — | `true` = starts without sound (required for autoplay in most browsers). |
| `attributes.controls` | — | `true` = shows native player controls. |
| `attributes.autoplay` | — | `true` = plays as soon as the element is injected. |
| `attributes.playsinline` | — | `true` = plays inline on mobile instead of going fullscreen. |
| `attributes.class` | — | CSS class(es) for styling the `<video>` element. |
| `attributes.id` | — | ID for the `<video>` element. |
| `style` | `{ width: '100%', height: 'auto' }` | CSS object applied to the video — e.g. `{ height: '200px' }`. Width is always `100%`. |
| `appendTo` | — | DOM element or CSS selector string where the `<video>` will be appended. |

> **CLS tip:** give the placeholder the same `aspect-ratio` as the video (`16/9` for standard widescreen). The injected `<video>` fills the exact same space — no layout shift, no PageSpeed penalty.

---

## Docs

https://boostifyjs.com/guides/introduction/
