├── .editorconfig ├── .github └── workflows │ ├── ci.yaml │ └── package-size-report.yaml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── amp.html ├── basic.html ├── embed-pym.html ├── embed.html ├── pym.html └── server.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── auto.js ├── constants.js ├── framer.js ├── frames.js └── index.js └── tests ├── public ├── auto-embed.html ├── auto.html ├── embed.html ├── index.html ├── init-auto.js ├── init-framer-pym.js ├── init-framer.js ├── observe-iframe.js ├── pym-embed.html ├── pym.html ├── pym.js ├── send-frame-height-controller.html ├── send-frame-height.html ├── send-height-on-framer-init.html ├── send-height-on-load.html ├── send-height-on-poll.html ├── send-height-on-resize.html └── style.css ├── server.js └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | browser: [chromium, firefox, webkit] 13 | 14 | steps: 15 | - name: Checkout the repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Node.js v16 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: '16' 22 | cache: 'npm' 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | - name: Install Playwright browsers 28 | run: npx playwright install-deps ${{ matrix.browser }} 29 | 30 | - name: Run tests 31 | run: npm test 32 | env: 33 | BROWSER: ${{ matrix.browser }} 34 | -------------------------------------------------------------------------------- /.github/workflows/package-size-report.yaml: -------------------------------------------------------------------------------- 1 | name: Package Size Report 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | pkg-size-report: 10 | name: Package Size Report 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '16' 21 | cache: 'npm' 22 | 23 | - name: Package size report 24 | uses: pkg-size/action@v1 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | with: 28 | display-size: uncompressed, gzip 29 | hide-files: '*.{js,cjs}.map' 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless/ 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | 78 | # DynamoDB Local files 79 | .dynamodb/ 80 | 81 | # Project files 82 | dist 83 | mangle.json 84 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | package-lock.json 4 | dist -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.0.0] - 2022-01-04 11 | 12 | ### Added 13 | 14 | - Tests! Of everything! 15 | 16 | - The act of "observing" an iframe has been broken out of the `Framer` class into its own function — `observeIframe`! This makes it possible to observe `@newswire/frames` compatible-iframes that have been created independent of this library. This means it is now possible to use your own code to create iframes (perhaps lazy load them with `IntersectionObserver`!), have them added via your CMS/templating engine, etc. 17 | 18 | It's important to remember however that this method **does not** add any attributes to the existing iframe. It just sets up the observer and stops there. This means it's on you to use CSS or other methods to style the iframe. (Set width to `100%`, etc.) 19 | 20 | ```js 21 | // grab a reference to an existing iframe, assuming there's already a "src" on this 22 | const iframe = document.getElementById('my-embed'); 23 | 24 | // returns a `unobserve()` function if you need to stop listening 25 | const unobserve = observeIframe(iframe); 26 | 27 | // later, if you need to disconnect from the iframe 28 | unobserve(); 29 | ``` 30 | 31 | As the example shows above, you can _also_ now disable the observer using the `unobserve` function `observeIframe` returns. Unlike the `remove()` method on `Framer`, this will **not** remove the iframe from the DOM. 32 | 33 | - On the frames side there is a new method for notifying the parent `Framer` of an embed's size - `sendHeightOnFramerInit`. Once an iframe is observed (with either `observeIframe` or `Framer`), the parent page will notify the iframe it is now ready to receive height updates. In response, the iframe will send a message back to the parent `Framer` with the initial height of the iframe. This should help get the correct iframe height to the parent page sooner. 34 | 35 | `sendHeightOnFramerInit` has been added to both `initFrame` and `initFrameAndPoll`. 36 | 37 | - `@newswire/frames` now has legacy support for [Pym.js](http://blog.apps.npr.org/pym.js/) child frames. This means you can now use `@newswire/frames` to resize iframes that have been built with Pym.js. However - `@newswire/frames` only recognizes Pym.js' `height` events. All other events **will be ignored**. 38 | 39 | ### Changed 40 | 41 | - `Framer` still exists but its interface has changed. Because the `container` was never optional it is now the first expected parameter when creating a new instance. The second parameter is now an object with two optional properties - `src` and `attributes`. `src` does what you expect and sets the `src` attribute on the iframe, but the `attributes` object is the new way to configure any other attributes on the `iframe` that's created. It's now just a convienient way to loop over an object and call `setAttribute`. 42 | 43 | Why the change? The most common request to this library has been to add additional attributes that `Framer` can apply to the iframe it creates. (Or the ability to _not_ set one, [like `src`](https://github.com/rdmurphy/frames/pull/6)!) Instead of having to add support to `Framer` for every attribute you want to set on the iframe, it's now just a matter of adding a new property to the `attributes` object. 44 | 45 | - `Framer` is no longer a class and instead just a function that returns an object. It was never really intended to be subclassed and this makes it a bit more compact when bundled, but it is still compatible with `new` if you prefer that. 46 | 47 | - The auto loader now expects attributes to be set on containers using the `data-frame-attribute-` prefix. This is to match the new way of passing attributes to `Framer`. 48 | 49 | ```html 50 | 51 |
52 | 53 | 54 |
58 | ``` 59 | 60 | ## [0.3.1] - 2019-02-25 61 | 62 | ### Fixed 63 | 64 | - Previous release did not actually contain changes. 😣 65 | 66 | ## [0.3.0] - 2019-02-25 67 | 68 | ### Added 69 | 70 | - Added support for `title` attribute. 71 | 72 | ### Changed 73 | 74 | - The name of the library for the UMD build is now `newswireFrames` instead of `frames`. This change was necessary to prevent a clash with the native [`Window.frames`](https://developer.mozilla.org/en-US/docs/Web/API/Window/frames). 75 | 76 | ## [0.2.0] - 2019-02-12 77 | 78 | ### Changed 79 | 80 | - We no longer use [spread in object literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals), which was adding an [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) call in the compiled library. This breaks `@newswire/frames` in IE 11. We've moved to a tiny built-in extend implementation that restores IE 11 support. 81 | 82 | ## [0.1.0] - 2018-12-30 83 | 84 | ### Added 85 | 86 | - Initial release! 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ryan Murphy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | @newswire/frames 3 |

4 |

5 | npm 6 | ci 7 | gzip size 8 | brotli size 9 | install size 10 |

11 | 12 | `@newswire/frames` is a minimalistic take on responsive iframes in the spirit of [Pym.js](http://blog.apps.npr.org/pym.js/). 13 | 14 | ## Key features 15 | 16 | - 🐜 **~1 kilobyte** gzipped for both parent and frame code 17 | - 🌴 **Tree-shakable** - import only what you need for your use case 18 | - ⚡️ **Speaks [AMP](https://www.ampproject.org)** and is compatible with [`amp-iframe`](https://www.ampproject.org/docs/reference/components/amp-iframe) 19 | - 🕰 **Legacy [Pym.js](http://blog.apps.npr.org/pym.js/) support** - recognizes `height` updates from Pym.js iframes 20 | - 🧪 **Fully tested** in Safari, Chrome and Firefox with [Playwright](https://playwright.dev) 21 | 22 | ## Supported browsers 23 | 24 | | Browser | Supported | 25 | | ------------------------------ | --------- | 26 | | Safari | ✅ | 27 | | Mozilla Firefox | ✅ | 28 | | Google Chrome | ✅ | 29 | | Opera | ✅ | 30 | | Microsoft Edge | ✅ | 31 | | Internet Explorer 11 | ✅ | 32 | | Internet Explorer 10 and lower | ⛔️ | 33 | 34 | ## Installation 35 | 36 | `@newswire/frames` is available via `npm`. 37 | 38 | ```sh 39 | npm install @newswire/frames 40 | ``` 41 | 42 | You can also use it directly via [unpkg.com](https://unpkg.com/). 43 | 44 | ```html 45 | 46 | 47 | ``` 48 | 49 | You can also import it as a module via unpkg! 50 | 51 | ```html 52 | 57 | ``` 58 | 59 | ## Usage 60 | 61 | ### From the **host** page (_framer_ or _parent_) 62 | 63 | The page that contains the embeds needs to use the `Framer` class to set up instances for each embed. 64 | 65 | Assume we have the following markup in our HTML: 66 | 67 | ```html 68 |
Loading...
69 | ``` 70 | 71 | Then, in our script: 72 | 73 | ```js 74 | import { Framer } from '@newswire/frames'; 75 | 76 | const container = document.getElementById('embed-container'); 77 | const src = 'https://i-am-an-embed/'; 78 | const attributes = { sandbox: 'allow-scripts allow-same-origin' }; 79 | 80 | const framer = new Framer(container, { src, attributes }); 81 | // Now the iframe has been added to the page and is listening for height changes notifications from within the iframe 82 | ``` 83 | 84 | It is also possible to observe existing iframes on a page if the content of the frames are compatible with `@newswire/frames`. This is handy if you already have your own method to dynamically add iframes to the page, or are using a custom method to lazy load them and don't need the heavy hand of `Framer`. 85 | 86 | ```js 87 | import { observeIframe } from '@newswire/frames'; 88 | 89 | // grab a reference to an existing iframe 90 | const iframe = document.getElementById('my-embed'); 91 | 92 | // returns a `unobserve()` function if you need to stop listening 93 | const unobserve = observeIframe(iframe); 94 | 95 | // later, if you need to disconnect from the iframe 96 | unobserve(); 97 | ``` 98 | 99 | Pym.js had the ability to automatically initialize embeds that had matching attibutes on their container elements — `@newswire/frames` can do this as well. 100 | 101 | Assume we have the following markup in our HTML: 102 | 103 | ```html 104 |
105 |
106 |
107 | ``` 108 | 109 | Then in our script, we can skip the fanfare of setting up a `Framer` for each one and use the `data-frame-src` attribute to find them. 110 | 111 | ```js 112 | import { autoInitFrames } from '@newswire/frames'; 113 | 114 | // looks for any elements with `data-frame-src` that haven't been initialized yet, and sets them up 115 | autoInitFrames(); 116 | ``` 117 | 118 | If you're needing to pass any of the other options to `Framer` when you're automatically creating the embeds, you can add attributes that the initializer will pick up and pass along using the `data-frame-attribute-*` prefix. 119 | 120 | ```html 121 |
125 | 126 | 127 | 129 | ``` 130 | 131 | ### From the **embedded** page (_frame_ or _child_) 132 | 133 | While the code to setup the host page is similar to Pym's `Parent` class, the methods for making the iframed page communicate with the host page are a little different. 134 | 135 | Want to set it and forget it? You can import a function that sets up listeners and sends the initial height of the frame's content. 136 | 137 | ```js 138 | import { initFrame } from '@newswire/frames'; 139 | 140 | // 1. Sends the initial frame's content height 141 | // 2. Sets up an one-time istener to send the height on load 142 | // 3. Sets up a listener to send the height every time the frame resizes 143 | // 4. Sets up an event listener that sends the height once the parent window begins watching 144 | initFrame(); 145 | ``` 146 | 147 | You can also automatically set up long polling for height changes as well. 148 | 149 | ```js 150 | import { initFrameAndPoll } from '@newswire/frames'; 151 | 152 | // 1. Sends the initial frame's content height 153 | // 2. Sets up an one-time listener to send the height on load 154 | // 3. Sets up a listener to send the height every time the frame resizes 155 | // 4. Sets up an event listener that sends the height once the parent window begins watching 156 | // 5. Sets up an interval to send a new height update every 300ms 157 | initFrameAndPoll(); 158 | ``` 159 | 160 | Alternatively, you can set and use function independently depending on the needs of your frame's content. 161 | 162 | ```js 163 | import { 164 | sendFrameHeight, 165 | sendHeightOnLoad, 166 | sendHeightOnResize, 167 | sendHeightOnPoll, 168 | sendHeightOnFramerInit, 169 | } from '@newswire/frames'; 170 | 171 | // 1. Sends the initial frame's content height 172 | sendFrameHeight(); 173 | 174 | // 2. Sets up an one-time listener to send the height on load 175 | sendHeightOnLoad(); 176 | 177 | // 3. Sets up a listener to send the height every time the frame resizes 178 | sendHeightOnResize(); 179 | 180 | // 4. Sets up an event listener that sends the height once the parent window begins watching 181 | sendHeightOnFramerInit(); 182 | 183 | // 5. Sets up an interval to send a new height update every 150ms 184 | sendHeightOnPoll(150); 185 | 186 | // 1-4 is identical to initFrame()! 1-5 is identical to initFrameAndPoll()! 187 | ``` 188 | 189 | Typically using `initFrame()` will be enough, but if you have code that will potentially change the height of the frame's content (like with an `` or `