https://github.com/orestbida/iframemanager
🍪 GDPR friendly iframe manager written in vanilla js
https://github.com/orestbida/iframemanager
gdpr gdpr-consent iframe iframe-manager video vimeo youtube
Last synced: 8 months ago
JSON representation
🍪 GDPR friendly iframe manager written in vanilla js
- Host: GitHub
- URL: https://github.com/orestbida/iframemanager
- Owner: orestbida
- License: mit
- Created: 2021-06-13T16:52:56.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2024-09-08T18:56:42.000Z (almost 2 years ago)
- Last Synced: 2025-03-31T17:15:38.330Z (about 1 year ago)
- Topics: gdpr, gdpr-consent, iframe, iframe-manager, video, vimeo, youtube
- Language: JavaScript
- Homepage: https://orestbida.com/demo-projects/iframemanager/demo1/
- Size: 431 KB
- Stars: 272
- Watchers: 11
- Forks: 33
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
[Demo](https://orestbida.com/demo-projects/iframemanager/demo1/) | [Features](#features) | [Installation](#installation)
[](https://opensource.org/licenses/MIT)

[](https://github.com/orestbida/iframemanager/releases)
**IframeMananger** is a lightweight javascript plugin which helps you **comply with `GDPR`** by completely removing iframes initially and setting a notice relative to that service. **Iframes are loaded only after consent**.
The plugin was mainly developed to aid [**CookieConsent**](https://github.com/orestbida/cookieconsent) with iframe management.
## Table of Contents
- [**Key features**](#features)
- [**Installation**](#installation)
- [**Configuration options & API**](#configuration-options)
- [**Configuration examples**](#configuration-examples)
- [youtube](#configuration-examples)
- [dailymotion](#configuration-examples)
- [vimeo](#configuration-examples)
- [twitch](#configuration-examples)
- [google maps](#configuration-examples)
- [**Usage with CookieConsent**](#usage-with-cookieconsent-v120)
- [**License**](#license)
## Features
- Lightweight
- Complies with **GDPR**
- **Multilanguage** support
- Automatic/custom thumbnail [support *](#note)
- Allows to integrate any service which uses iframes
- Improves website **performance**:
- lazy-load thumbnails
- lazy-load iframes
- Can be integrated with any consent solution
## Installation
1. #### Download the [latest release](https://github.com/orestbida/iframemanager/releases/latest) or use via CDN/NPM:
```bash
https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.3.0/dist/iframemanager.js
https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.3.0/dist/iframemanager.css
```
using [`npm`](https://www.npmjs.com/package/@orestbida/iframemanager):
```bash
npm i @orestbida/iframemanager
```
2. #### Import script + stylesheet:
```html
...
...
```
3. #### Configure and run:
- As external script
- Create a .js file (e.g. `app.js`) and import it in your html markup:
```html
...
```
- Configure iframemanager inside `app.js`:
```javascript
(function(){
const im = iframemanager();
// Example with youtube embed
im.run({
currLang: 'en',
services : {
youtube : {
embedUrl: 'https://www.youtube-nocookie.com/embed/{data-id}',
thumbnailUrl: 'https://i3.ytimg.com/vi/{data-id}/hqdefault.jpg',
iframe : {
allow : 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;'
},
languages : {
en : {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of youtube.com.',
loadBtn: 'Load video',
loadAllBtn: "Don't ask again"
}
}
}
}
});
})();
```
- As inline script
```html
...
window.addEventListener('load', function(){
const im = iframemanager();
// Example with youtube embed
im.run({
currLang: 'en',
services : {
youtube : {
embedUrl: 'https://www.youtube-nocookie.com/embed/{data-id}',
thumbnailUrl: 'https://i3.ytimg.com/vi/{data-id}/hqdefault.jpg',
iframe : {
allow : 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;'
},
languages : {
en : {
notice: 'This content is hosted by a third party. By showing the external content you accept the <a rel="noreferrer noopener" href="https://www.youtube.com/t/terms" target="_blank">terms and conditions</a> of youtube.com.',
loadBtn: 'Load video',
loadAllBtn: "Don't ask again"
}
}
}
}
});
});
```
4. #### Create a div with `data-service` and `data-id` attributes:
```html
```
## Configuration options
All available options for the `
` element:
```html
```
- `data-service` : [String, Required] name of the service (must also be defined in the config. object)
- `data-id` : [String, Required] unique id of the resource (example: video id)
- `data-title` : [String] notice title
- `data-params` : [String] iframe query parameters
- `data-thumbnail` : [String] path to custom thumbnail
- `data-ratio` : [String] custom aspect ratio ([Available values.](#available-data-ratio))[v1.1.0]
- `data-autoscale` : specify for **responsive iframe** (fill parent width + scale proportionally)
- `data-widget` : ignore the default aspect ratio; specify when implementing a custom widget with explicit width and height (twitter, facebook, instagram ...)[v1.2.0]
### How to set attributes on the `iframe` element
You can set any attribute by using the following syntax:
- `data-iframe-` [String] note: replace `` with a valid attribute name. [v1.1.0]
Example:
```html
```
All available options for the config. object:
```javascript
{
currLang: 'en', // current language of the notice (must also be defined in the "languages" object below)
autoLang: false, // if enabled => use current client's browser language
// instead of currLang [OPTIONAL]
// callback fired when state changes (a new service is accepted/rejected)
onChange: ({changedServices, eventSource}) => {
// changedServices: string[]
// eventSource.type: 'api' | 'click'
// eventSource.service: string
// eventSource.action: 'accept' | 'reject'
},
services : {
myservice : {
embedUrl: 'https://',
// set valid url for automatic thumbnails [OPTIONAL]
thumbnailUrl: 'https://',
// global iframe settings (apply to all iframes relative to current service) [OPTIONAL]
iframe: {
allow: 'fullscreen', // iframe's allow attribute
params: 'mute=1&start=21', // iframe's url query parameters
// function run for each iframe configured with current service
onload: (dataId, setThumbnail) => {
console.log(`loaded iframe with data-id=${dataId}`);
}
},
// cookie is set if the current service is accepted
cookie: {
name: 'cc_youtube', // cookie name
path: '/', // cookie path [OPTIONAL]
samesite: 'lax', // cookie samesite [OPTIONAL]
domain: location.hostname // cookie domain [OPTIONAL]
},
languages: {
en: {
notice: 'Html notice message',
loadBtn: 'Load video', // Load only current iframe
loadAllBtn: "Don't ask again" // Load all iframes configured with this service + set cookie
}
}
},
anotherservice: {
// ...
}
}
}
```
Any other property specified inside the `iframe` object, will be set directly to the `iframe` element as attribute.
Example: add `frameborder` and `style` attributes:
```javascript
{
// ...
services: {
myservice: {
// ...
iframe: {
// ...
frameborder: '0',
style: 'border: 4px solid red;'
}
}
}
}
```
Note: `thumbnailUrl` can be static string, dynamic string or a function:
- `static string` : "https://path_to_image/image.png"
- `dynamic string` : "https://myservice_embed_url/{data-id}"
- `function` :
```javascript
thumbnailUrl: (dataId, setThumbnail) => {
// fetch thumbnail url here based on dataId of the current element ...
let url = 'fetched_url';
// pass obtained url to the setThumbnail function
setThumbnail(url);
}
```
## Custom Widgets
Some services (e.g. twitter) have their own markup and API to generate the iframe.
Note: this is an example with twitter's widget. Each widget/service will have a slightly different implementation.
1. Place the markup inside a special `data-placeholder` div. Remove any `script` tag that comes with the markup. Example:
```html
Sunsets don't get much better than this one over @GrandTetonNPS. #nature #sunset pic.twitter.com/YuKy2rcjyU
— US Department of the Interior (@Interior) May 5, 2014
```
2. Create a new service and dynamically load and initialize the widget inside the `onAccept` callback:
```javascript
im.run({
services: {
twitter: {
onAccept: async (div, setIframe) => {
// Using cookieconsent v3
await CookieConsent.loadScript('https://platform.twitter.com/widgets.js');
// Make sure the "window.twttr" property exists
await im.childExists({childProperty: 'twttr'}) && await twttr.widgets.load(div);
// Make sure the "iframe" element exists
await im.childExists({parent: div}) && setIframe(div.querySelector('iframe'));
},
onReject: (iframe) => {
iframe && iframe.parentElement.remove();
}
}
}
})
```
It is highly recommended to set a fixed `width` and `height` to the main `data-service` div, to avoid the (awful) content jump effect when the iframe is loaded.
## Placeholder for non-js browsers
You can set a placeholder visible only if javascript is disabled via a special div:
```html
```
Example:
```html
I'm visible only if js is disabled
```
## APIs
The plugin exposes the following methods:
- `.run()`
- `.acceptService()`
- `.rejectService()`
- `.getState()` [v1.2.0+]
- `.getConfig()` [v1.2.0+]
- `.reset()` [v1.3.0+]
Example usage:
```javascript
// accept specific service only
im.acceptService('youtube');
// accept all services (for example if user has given full consent to cookies)
im.acceptService('all');
// reject specific service
im.rejectService('youtube');
// reject all services (for example when user opts out of cookies)
im.rejectService('all');
// get entire config object
const config = im.getConfig();
// get current state (enabled/disabled services)
const state = im.getState();
// state.services: Map
// state.acceptedServices: string[]
// soft reset, removes internal event listeners
im.reset();
// hard reset, same as above, but also resets each div to its original state (for react frameworks)
im.reset(true);
```
Both `acceptService` and `rejectService` work the same way:
1. set/erase cookie
2. create/remove iframes
## Configuration examples
- Youtube
```javascript
im.run({
currLang: 'en',
services: {
youtube: {
embedUrl: 'https://www.youtube-nocookie.com/embed/{data-id}',
thumbnailUrl: 'https://i3.ytimg.com/vi/{data-id}/hqdefault.jpg',
iframe: {
allow: 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;',
},
languages: {
en: {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of youtube.com.',
loadBtn: 'Load video',
loadAllBtn: "Don't ask again"
}
}
}
}
});
```
Example:
```html
```
- Dailymotion
```javascript
im.run({
currLang: 'en',
services: {
dailymotion: {
embedUrl: 'https://www.dailymotion.com/embed/video/{data-id}',
thumbnailUrl: async (dataId, setThumbnail) => {
// Use dailymotion's API to fetch the thumbnail
const url = `https://api.dailymotion.com/video/${dataId}?fields=thumbnail_large_url`;
const response = await (await fetch(url)).json();
const thumbnailUlr = response?.thumbnail_large_url;
thumbnailUlr && setThumbnail(thumbnailUlr);
},
iframe: {
allow: 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;',
},
languages: {
en: {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of dailymotion.com.',
loadBtn: 'Load video',
loadAllBtn: "Don't ask again"
}
}
}
}
});
```
- Vimeo
```javascript
im.run({
currLang: 'en',
services: {
vimeo: {
embedUrl: 'https://player.vimeo.com/video/{data-id}',
iframe: {
allow : 'fullscreen; picture-in-picture, allowfullscreen;',
},
thumbnailUrl: async (dataId, setThumbnail) => {
const url = `https://vimeo.com/api/v2/video/${dataId}.json`;
const response = await (await fetch(url)).json();
const thumbnailUrl = response[0]?.thumbnail_large;
thumbnailUrl && setThumbnail(thumbnailUrl);
},
languages: {
en: {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of vimeo.com.',
loadBtn: 'Load video',
loadAllBtn: "Don't ask again"
}
}
}
}
});
```
- Twitch
```javascript
im.run({
currLang: 'en',
services: {
twitch: {
embedUrl: `https://player.twitch.tv/?{data-id}&parent=${location.hostname}`,
iframe: {
allow: 'accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen;',
},
languages: {
en: {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of twitch.com.',
loadBtn: 'Load stream',
loadAllBtn: "Don't ask again"
}
}
}
}
});
```
- Google Maps
- With API key
```javascript
im.run({
currLang: 'en',
services: {
googlemaps: {
embedUrl: 'https://www.google.com/maps/embed/v1/place?key=API_KEY&q={data-id}',
iframe: {
allow: 'picture-in-picture; fullscreen;'
},
languages: {
en: {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of Google Maps.',
loadBtn: 'Load map',
loadAllBtn: "Don't ask again"
}
}
}
}
});
```
Example:
```html
```
- Without API key
```javascript
im.run({
currLang: 'en',
services : {
googlemaps : {
embedUrl: 'https://www.google.com/maps/embed?pb={data-id}',
iframe: {
allow : 'picture-in-picture; fullscreen;'
},
languages : {
en : {
notice: 'This content is hosted by a third party. By showing the external content you accept the terms and conditions of Google Maps.',
loadBtn: 'Load map',
loadAllBtn: "Don't ask again"
}
}
}
}
});
```
Example usage:
```html
```
## Usage with CookieConsent [v1.2.0+]
You can use the `onChange` callback to detect when an iframe is loaded by the `loadAllBtn` button click event and notify CookieConsent to also update its state.
Example:
```javascript
im.run({
currLang: 'en',
onChange: ({changedServices, eventSource}) => {
if(eventSource.type === 'click') {
// Retrieve all accepted services:
// const allAcceptedServices = im.getState().acceptedServices;
/**
* Retrieve array of already accepted services
* and add the new service
*/
const servicesToAccept = [
...CookieConsent.getUserPreferences().acceptedServices['analytics'], //cookieconsent v3
...changedServices
];
CookieConsent.acceptService(servicesToAccept, 'analytics');
}
},
services: {
// ...
}
});
```
Note: the above example assumes that all services belong to the `analytics` category.
### Available `data-ratio`
Horizontal aspect ratio:
* `1:1`, `2:1`, `3:2`, `5:2`, `4:3`, `16:9`, `16:10`, `20:9`, `21:9`
Vertical aspect ratio:
* `9:16`, `9:20`
## License
Distributed under the MIT License. See [LICENSE](https://github.com/orestbida/iframemanager/blob/master/LICENSE) for more information.
#### Note
Not all services (example: twitch) allow automatic/easy thumbnail fetch.