@asciisd/vue-progressive-iframe
v1.1.0
Published
A Vue 3 component for progressive iframe loading with advanced content detection
Downloads
22
Maintainers
Readme
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
- 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
npm install @asciisd/vue-progressive-iframeRequirements:
- Vue 3.5+
- Node.js 16+
- TypeScript 5+ (if using TypeScript)
Plugin Installation (Optional)
If you want to register the component globally:
import { createApp } from "vue";
import { VueProgressiveIframe } from "@asciisd/vue-progressive-iframe";
const app = createApp({});
app.use(VueProgressiveIframe);📖 Usage
Basic Usage
<template>
<ProgressiveIframe
:src="iframeUrl"
:height="600"
@content-loaded="onContentLoaded"
@load-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
<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"
@content-loaded="onContentLoaded"
@detection-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 @click="retry">Try Again</button>
</div>
</template>
</ProgressiveIframe>
</template>Using the Composable
<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 @click="forceLoad">Force Load</button>
<button @click="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
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 and IAB recommendations:
<template>
<!-- Embed HTML content directly -->
<ProgressiveIframe
loading-strategy="friendly"
:html-content="widgetHtml"
:styles="['body { margin: 0; padding: 20px; }']"
:auto-resize="true"
:enable-bridge="true"
@bridge-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
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
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
- Immediate Visibility: The iframe is rendered and visible immediately
- Progressive Detection: Content detection starts after iframe initialization
- Smart Heuristics: Uses different detection methods for same-origin vs cross-origin
- Fallback Layers: Multiple fallback mechanisms ensure content eventually loads
- 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
# 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 page.
