@phun-ky/frameport
v2.0.90
Published
A zero dependency package to effortlessly create responsive component previews in documentation using real media queries. Frameport dynamically generates iframes containing your HTML/CSS/JS, mimicking natural device viewports to test responsiveness, witho
Maintainers
Keywords
Readme
@phun-ky/frameport
⚡ Create interactive, responsive component previews for your style guides using real device viewports and media queries.
About
Frameport helps you embed live, responsive component previews in your documentation or style guide. It generates iframes with your HTML, CSS and JavaScript at the widths you choose, so media queries apply as they would on a real device.
✅ Zero dependencies ✅ Framework agnostic ✅ Real device viewport emulation ✅ Instant iframe-based previews
Requirements
A browser. The package is ES modules with types included, plus a UMD build for script tags. Importing it in Node (for example during server-side rendering) works and does nothing until frameport() is called in a browser; require() needs Node 22.12 or later.
Installation
npm i @phun-ky/frameportUsage
Frameport turns every element with a data-frameport attribute into one or more iframes. The element's content, or a template, becomes the body of each iframe.
Script tag
Place the script at the end of the page:
<script src="https://unpkg.com/@phun-ky/frameport/dist/frameport.js"></script>By default it renders when DOMContentLoaded fires. Add one of these attributes to the script element to change that:
| Attribute | Description |
| -------------- | ------------------------------------------------------------------ |
| data-manual | Renders nothing; call window.frameport() when you want to render |
| data-instant | Renders right away |
| data-dom | Renders when DOMContentLoaded fires (the default) |
| data-lazy | Renders each element when it scrolls into view |
<div data-frameport data-frameport-vw="320">
<p>Rendered when you call window.frameport()</p>
</div>
<script
src="https://unpkg.com/@phun-ky/frameport/dist/frameport.js"
data-manual
></script>
<script>
window.frameport();
</script>
<!-- Renders 1 iframe -->Calling frameport() again removes the iframes it generated and renders them anew.
ES modules
import frameport from '@phun-ky/frameport';
// Render every [data-frameport] element on the page
frameport();
// Renders 1 iframeLazy loading
modes.lazy() renders each [data-frameport] element when it scrolls into view:
import { modes } from '@phun-ky/frameport';
modes.lazy();
// Renders 1 iframeExamples
One width
The element's content is rendered in an iframe 667 pixels wide and 375 pixels high, placed right after the element. The element itself is hidden.
<div
data-frameport
data-frameport-vw="667"
data-frameport-vh="375"
data-frameport-style="body { background-color: #84a295; }"
>
<button type="button">Primary</button>
</div>
<!-- Renders 1 iframe -->Several viewports
data-frameport-viewports takes widthxheight pairs separated by commas. The iframes are placed right after the element. In 2.x they come out in reverse order: the last viewport in the list comes first. This changes in 3.0 (#240).
<div data-frameport data-frameport-viewports="375x667,360x740,768x1024">
<main style="height: 100vh">
<p>Look, I'm in an iframe</p>
</main>
</div>
<!-- Renders 3 iframes -->Content from a template
With data-frameport-template, the content comes from the element the selector matches, which is then hidden. The iframe is placed inside the data-frameport element.
<div
data-frameport
data-frameport-template="#button-template"
data-frameport-vw="667"
data-frameport-vh="375"
></div>
<div id="button-template">
<button type="button">Primary</button>
</div>
<!-- Renders 1 iframe -->Attributes
| Attribute | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data-frameport | Required. Marks the element to render. |
| data-frameport-vw | The iframe width in pixels. Required unless data-frameport-viewports is set. |
| data-frameport-vh | The iframe height in pixels. |
| data-frameport-viewports | One iframe per widthxheight pair, separated by commas, for example 375x667,768x1024. Replaces data-frameport-vw and data-frameport-vh. |
| data-frameport-template | A selector for the element whose content to render, instead of the element's own content. |
| data-frameport-css | A stylesheet path on the same origin, starting with /, for example /dist/styles.css. Added to the iframe's <head>. |
| data-frameport-style | CSS added in a <style> element in the iframe's <head>. |
| data-frameport-js | A script path on the same origin, starting with /, for example /dist/script.js. Added at the end of the iframe's <body>. |
| data-frameport-code | JavaScript added in a <script> element at the end of the iframe's <body>. |
| data-frameport-class | A class name for the generated iframes. Without it, the iframes get border: none. |
| data-frameport-headers | Extra HTML for the iframe's <head>, such as <meta> or <link> elements, separated by commas (so the HTML itself can't contain a comma). Added after frameport's default <meta> tags. |
Generated iframes have a data-frameport-iframe attribute.
Security
The iframes load from blob: URLs, which share your page's origin, and have no sandbox attribute. Code in the rendered content, data-frameport-code, data-frameport-js and data-frameport-headers can read and change your page. Only render content you control, never user input. See SECURITY.md.
API
Go here to read the full API documentation.
Demo
Click here for a demo on codepen.io
Contributing
If you want to contribute, please read the CONTRIBUTING.md and CODE_OF_CONDUCT.md
Sponsor me
I'm an Open Source evangelist, creating stuff that does not exist yet to help get rid of secondary activities and to enhance systems already in place, be it documentation or web sites.
The sponsorship is an unique opportunity to alleviate more hours for me to maintain my projects, create new ones and contribute to the large community we're all part of :)
