@asciisd/vue-progressive-iframe
Version:
A Vue 3 component for progressive iframe loading with advanced content detection
395 lines (315 loc) • 14.2 kB
Markdown
# Vue Progressive Iframe
A Vue 3 component library for progressive iframe loading with advanced content detection. Perfect for embedding third-party content like analytics dashboards, trading platforms, or any iframe-based applications.
## ✨ Features
- **Multiple Loading Strategies**: Choose from 5 optimized loading strategies based on [Aaron Peters' research](https://www.aaronpeters.nl/blog/iframe-loading-techniques-and-performance/)
- **Progressive Loading**: Show iframe immediately with loading overlay (default)
- **Dynamic Async**: Meebo's ultra-awesome technique - no onload blocking, no busy indicators
- **Smart Content Detection**: Automatically detect when iframe content is actually ready
- **Performance Optimized**: Prevent main page onload blocking and minimize browser busy indicators
- **Cross-Origin Support**: Works with cross-origin iframes using intelligent heuristics
- **TypeScript Support**: Full TypeScript support with proper type definitions
- **Customizable UI**: Flexible loading states and error handling
- **Vue 3 Composition API**: Built with modern Vue 3 patterns
- **Lightweight**: Minimal dependencies, optimized bundle size
## 🚀 Installation
[](https://www.npmjs.com/package/@asciisd/vue-progressive-iframe)
[](https://www.npmjs.com/package/@asciisd/vue-progressive-iframe)
[](https://github.com/aemaddin/vue-progressive-iframe/blob/main/LICENSE)
```bash
npm install /vue-progressive-iframe
```
**Requirements:**
- Vue 3.5+
- Node.js 16+
- TypeScript 5+ (if using TypeScript)
### Plugin Installation (Optional)
If you want to register the component globally:
```typescript
import { createApp } from "vue";
import { VueProgressiveIframe } from "@asciisd/vue-progressive-iframe";
const app = createApp({});
app.use(VueProgressiveIframe);
```
## 📖 Usage
### Basic Usage
```vue
<template>
<ProgressiveIframe
:src="iframeUrl"
:height="600"
-loaded="onContentLoaded"
-error="onLoadError"
/>
</template>
<script setup>
import { ProgressiveIframe } from "@asciisd/vue-progressive-iframe";
const iframeUrl = "https://example.com/dashboard";
const onContentLoaded = (loadTime) => {
console.log(`Content loaded in ${loadTime}ms`);
};
const onLoadError = (error) => {
console.error("Failed to load iframe content:", error);
};
</script>
```
### Performance-Optimized Usage
```vue
<template>
<ProgressiveIframe
:src="iframeUrl"
:height="800"
loading-strategy="dynamic-async"
:prevent-onload-blocking="true"
:minimize-busy-indicators="true"
:content-detection-timeout="30000"
:show-debug-info="true"
-loaded="onContentLoaded"
-progress="onProgress"
>
<template #loading>
<div class="custom-loading">
<h3>Loading Dashboard...</h3>
<progress :value="progress" max="100"></progress>
</div>
</template>
<template #error="{ error, retry }">
<div class="custom-error">
<h3>Failed to Load</h3>
<p>{{ error.message }}</p>
<button ="retry">Try Again</button>
</div>
</template>
</ProgressiveIframe>
</template>
```
### Using the Composable
```vue
<script setup>
import { useProgressiveIframe } from "@asciisd/vue-progressive-iframe";
const {
iframeRef,
isContentLoading,
hasError,
errorMessage,
contentCheckCount,
forceLoad,
refresh,
} = useProgressiveIframe({
src: "https://example.com/dashboard",
contentDetectionTimeout: 30000,
maxContentChecks: 30,
crossOriginWaitTime: 10000,
});
</script>
<template>
<div class="relative">
<iframe
ref="iframeRef"
:src="src"
class="w-full h-96"
:class="{ 'opacity-30': isContentLoading }"
/>
<div
v-if="isContentLoading"
class="absolute inset-0 flex items-center justify-center"
>
<div>Loading... ({{ contentCheckCount }}/30)</div>
</div>
<div
v-if="hasError"
class="absolute inset-0 flex items-center justify-center bg-red-50"
>
<div>
<p>{{ errorMessage }}</p>
<button ="forceLoad">Force Load</button>
<button ="refresh">Retry</button>
</div>
</div>
</div>
</template>
```
## 🚀 Loading Strategies
Choose the optimal loading strategy based on your use case:
| Strategy | Blocks Onload | Busy Indicator | Best For | Performance |
| --------------- | ------------- | -------------- | ------------------------------------ | ----------- |
| `progressive` | ❌ No | ❌ No | Default choice, immediate visibility | ⭐⭐⭐⭐⭐ |
| `dynamic-async` | ❌ No | ❌ No | Third-party widgets, ads | ⭐⭐⭐⭐⭐ |
| `friendly` | ❌ No | ❌ No | Same-domain content, widgets | ⭐⭐⭐⭐⭐ |
| `after-onload` | ❌ No | ✅ Yes | Below-fold content | ⭐⭐⭐⭐ |
| `settimeout` | ❌ No\* | ✅ Yes | Legacy compatibility | ⭐⭐⭐ |
| `traditional` | ✅ Yes | ✅ Yes | Critical content only | ⭐⭐ |
\*Note: setTimeout strategy may block onload in IE8
### Strategy Recommendations
```typescript
import { getRecommendedStrategy } from "@asciisd/vue-progressive-iframe";
// For third-party analytics dashboard
const strategy = getRecommendedStrategy({
isThirdParty: true,
isAboveFold: true,
isCritical: false,
blockingTolerance: "none",
}); // Returns: 'dynamic-async'
// For critical above-the-fold content
const strategy = getRecommendedStrategy({
isThirdParty: false,
isAboveFold: true,
isCritical: true,
blockingTolerance: "medium",
}); // Returns: 'progressive'
```
### Friendly Iframe for Same-Domain Content
The **Friendly Iframe** technique is perfect for same-domain content, ads, and widgets. Inspired by [vue-friendly-iframe](https://github.com/officert/vue-friendly-iframe) and IAB recommendations:
```vue
<template>
<!-- Embed HTML content directly -->
<ProgressiveIframe
loading-strategy="friendly"
:html-content="widgetHtml"
:styles="['body { margin: 0; padding: 20px; }']"
:auto-resize="true"
:enable-bridge="true"
-message="handleMessage"
/>
</template>
<script setup>
const widgetHtml = `
<div>
<h3>Analytics Widget</h3>
<div id="stats">Loading...</div>
<button onclick="updateStats()">Refresh</button>
<script>
// Set flag for friendly iframe
window.inDapIF = true;
function updateStats() {
// Send message to parent
parent.postMessage({
type: 'iframe_bridge_stats_updated',
payload: { views: 1234, users: 567 }
}, '*');
}
</script>
</div>
`
const handleMessage = (data) => {
if (data.stats_updated) {
console.log('Stats updated:', data.payload)
}
}
</script>
```
#### Friendly Iframe Benefits
- ✅ **Same-domain access** - Full control over iframe content
- ✅ **Auto-resize** - Automatically adjusts to content height
- ✅ **PostMessage bridge** - Seamless parent-iframe communication
- ✅ **No onload blocking** - Doesn't interfere with main page loading
- ✅ **Keep-alive support** - Preserve state across route changes
## 📦 Available Exports
```typescript
import {
// Main component
ProgressiveIframe,
// Composable
useProgressiveIframe,
// Plugin for global registration
VueProgressiveIframe,
// Utilities
createFriendlyIframe,
generateSandboxAttributes,
detectContentReady,
// Types
type IframeLoadingStrategy,
type ProgressiveIframeOptions,
type ProgressiveIframeEvents,
} from "@asciisd/vue-progressive-iframe";
```
## 🔧 API Reference
### ProgressiveIframe Component
#### Props
| Prop | Type | Default | Description |
| ------------------------- | ----------------------- | ------------------------------------------------------------ | ----------------------------------------- |
| `src` | `string` | **required** | The iframe source URL |
| `height` | `number \| string` | `600` | Iframe height in pixels or CSS value |
| `width` | `number \| string` | `'100%'` | Iframe width in pixels or CSS value |
| `contentDetectionTimeout` | `number` | `30000` | Timeout for content detection (ms) |
| `maxContentChecks` | `number` | `30` | Maximum number of content checks |
| `crossOriginWaitTime` | `number` | `10000` | Wait time for cross-origin detection (ms) |
| `showDebugInfo` | `boolean` | `false` | Show debug information |
| `allowedOrigins` | `string[]` | `[]` | Allowed origins for postMessage |
| `sandbox` | `string` | `'allow-scripts allow-same-origin allow-forms allow-popups'` | Iframe sandbox attributes |
| `loadingStrategy` | `IframeLoadingStrategy` | `'progressive'` | Loading strategy to use |
| `preventOnloadBlocking` | `boolean` | `true` | Prevent blocking main page onload |
| `minimizeBusyIndicators` | `boolean` | `true` | Minimize browser busy indicators |
| `htmlContent` | `string` | `undefined` | HTML content for friendly iframes |
| `styles` | `string[]` | `[]` | CSS styles for friendly iframes |
| `autoResize` | `boolean` | `false` | Auto-resize iframe to content height |
| `enableKeepAlive` | `boolean` | `false` | Enable iframe state preservation |
| `enableBridge` | `boolean` | `false` | Enable PostMessage communication bridge |
#### Events
| Event | Payload | Description |
| -------------------- | ------------------------------------------- | ---------------------------------------- |
| `content-loaded` | `{ loadTime: number, checkCount: number }` | Fired when content is detected as loaded |
| `load-error` | `{ error: Error, errorCount: number }` | Fired when loading fails |
| `detection-progress` | `{ checkCount: number, maxChecks: number }` | Fired during content detection |
| `force-loaded` | `{ loadTime: number }` | Fired when force load is used |
#### Slots
| Slot | Props | Description |
| --------- | ------------------------------------------------------------- | ----------------- |
| `loading` | `{ checkCount: number, maxChecks: number, loadTime: number }` | Custom loading UI |
| `error` | `{ error: Error, retry: Function, forceLoad: Function }` | Custom error UI |
### useProgressiveIframe Composable
```typescript
function useProgressiveIframe(options: {
src: string;
contentDetectionTimeout?: number;
maxContentChecks?: number;
crossOriginWaitTime?: number;
allowedOrigins?: string[];
}): {
// Refs
iframeRef: Ref<HTMLIFrameElement | undefined>;
isContentLoading: Ref<boolean>;
hasError: Ref<boolean>;
errorMessage: Ref<string>;
contentCheckCount: Ref<number>;
loadStartTime: Ref<number>;
// Methods
forceLoad: () => void;
refresh: () => void;
startContentDetection: () => void;
// Computed
loadTime: ComputedRef<number>;
isTimeout: ComputedRef<boolean>;
};
```
## 🎯 How It Works
1. **Immediate Visibility**: The iframe is rendered and visible immediately
2. **Progressive Detection**: Content detection starts after iframe initialization
3. **Smart Heuristics**: Uses different detection methods for same-origin vs cross-origin
4. **Fallback Layers**: Multiple fallback mechanisms ensure content eventually loads
5. **User Control**: Force load option available if automatic detection fails
### Content Detection Methods
- **Same-Origin**: Direct DOM inspection of iframe content
- **Cross-Origin**: Property-based detection with timing heuristics
- **PostMessage**: Listens for messages from iframe content
- **Timeout Fallback**: Graceful degradation after timeout
## 🔧 Development
```bash
# Install dependencies
npm install
# Start development server
npm run dev
# Build package
npm run build
# Run tests
npm run test
# Type checking
npm run typecheck
# Lint code
npm run lint
```
## 📄 License
MIT License - see LICENSE file for details.
## 🤝 Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests.
## 🐛 Issues
If you encounter any issues, please file them on our [GitHub Issues](https://github.com/aemaddin/vue-progressive-iframe/issues) page.