Demo | Features | Installation
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 with iframe management.
- Key features
- Installation
- Configuration options & API
- Configuration examples
- Usage with CookieConsent
- License
- Lightweight
- Complies with GDPR
- Multilanguage support
- Automatic/custom thumbnail support *
- Allows to integrate any service which uses iframes
- Improves website performance:
- lazy-load thumbnails
- lazy-load iframes
- Can be integrated with any consent solution
-
Download the latest release or use via CDN/NPM:
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
:npm i @orestbida/iframemanager
-
<html> <head> ... <link rel="stylesheet" href="iframemanager.css"> </head> <body> ... <script defer src="iframemanager.js"></script> <body> </html>
-
-
As external script
-
Create a .js file (e.g.
app.js
) and import it in your html markup:<body> ... <script defer src="iframemanager.js"></script> <script defer src="app.js"></script> <body>
-
Configure iframemanager inside
app.js
:(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" } } } } }); })();
-
-
As inline script
<body> ... <script defer src="iframemanager.js"></script> <!-- Inline script --> <script> 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" } } } } }); }); </script> <body>
-
-
<div data-service="youtube" data-id="<video-id>"></div>
All available options for the <div>
element:
<div
data-service="<service-name>"
data-id="<resource-id>"
data-params="<iframe-query-parameters>"
data-thumbnail="<path-to-image>"
data-autoscale
data-ratio="<x:y>">
</div>
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 titledata-params
: [String] iframe query parametersdata-thumbnail
: [String] path to custom thumbnaildata-ratio
: [String] custom aspect ratio (Available values.)[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]
You can set any attribute by using the following syntax:
data-iframe-<attribute>
[String] note: replace<attribute>
with a valid attribute name. [v1.1.0]
Example:
<div
data-service="youtube"
data-id="5b35haQV7tU"
data-autoscale
data-iframe-id="myYoutubeEmbed"
data-iframe-loading="lazy"
data-iframe-frameborder="0">
</div>
All available options for the config. object:
{
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://<myservice_embed_url>',
// set valid url for automatic thumbnails [OPTIONAL]
thumbnailUrl: 'https://<myservice_embed_thumbnail_url>',
// 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 <b>notice</b> 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:
{
// ...
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
: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); }
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.
-
Place the markup inside a special
data-placeholder
div. Remove anyscript
tag that comes with the markup. Example:<div data-service="twitter" data-widget style="width: 300px; height: 501px" > <div data-placeholder> <blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sunsets don't get much better than this one over <a href="https://twitter.com/GrandTetonNPS?ref_src=twsrc%5Etfw">@GrandTetonNPS</a>. <a href="https://twitter.com/hashtag/nature?src=hash&ref_src=twsrc%5Etfw">#nature</a> <a href="https://twitter.com/hashtag/sunset?src=hash&ref_src=twsrc%5Etfw">#sunset</a> <a href="http://t.co/YuKy2rcjyU">pic.twitter.com/YuKy2rcjyU</a></p>— US Department of the Interior (@Interior) <a href="https://twitter.com/Interior/status/463440424141459456?ref_src=twsrc%5Etfw">May 5, 2014</a></blockquote> </div> </div>
-
Create a new service and dynamically load and initialize the widget inside the
onAccept
callback: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.
You can set a placeholder visible only if javascript is disabled via a special div:
<div data-placeholder data-visible></div>
Example:
<div
data-service="youtube"
data-id="5b35haQV7tU"
data-autoscale>
<div data-placeholder data-visible>
<p>I'm visible only if js is disabled</p>
</div>
</div>
The plugin exposes the following methods:
.run(<config_object>)
.acceptService(<service_name>)
.rejectService(<service_name>)
.getState()
[v1.2.0+].getConfig()
[v1.2.0+].reset(<hard_reset>)
[v1.3.0+]
Example usage:
// 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<string, boolean>
// 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:
- set/erase cookie
- create/remove iframes
-
Youtube
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" } } } } });
Example:
<!-- https://www.youtube.com/watch?v=5b35haQV7tU --> <div data-service="youtube" data-id="5b35haQV7tU" ></div>
-
Dailymotion
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 <a rel="noreferrer noopener" href="https://www.dailymotion.com/legal/privacy?localization=en" target="_blank">terms and conditions</a> of dailymotion.com.', loadBtn: 'Load video', loadAllBtn: "Don't ask again" } } } } });
-
Vimeo
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 <a rel="noreferrer noopener" href="https://vimeo.com/terms" target="_blank">terms and conditions</a> of vimeo.com.', loadBtn: 'Load video', loadAllBtn: "Don't ask again" } } } } });
-
Twitch
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 <a rel="noreferrer noopener" href="https://www.twitch.tv/p/en/legal/terms-of-service/" target="_blank">terms and conditions</a> of twitch.com.', loadBtn: 'Load stream', loadAllBtn: "Don't ask again" } } } } });
-
Google Maps
-
With API key
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 <a rel="noreferrer noopener" href="https://cloud.google.com/maps-platform/terms" target="_blank">terms and conditions</a> of Google Maps.', loadBtn: 'Load map', loadAllBtn: "Don't ask again" } } } } });
Example:
<div data-service="GoogleMaps" data-id="Space+Needle,Seattle+WA" data-autoscale ></div>
-
Without API key
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 <a rel="noreferrer noopener" href="https://cloud.google.com/maps-platform/terms" target="_blank">terms and conditions</a> of Google Maps.', loadBtn: 'Load map', loadAllBtn: "Don't ask again" } } } } });
Example usage:
<div data-service="googlemaps" data-id="!1m18!1m12!1m3!1d2659.4482749804133!2d11.644969316034478!3d48.19798087922823!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x479e7499e2d4c67f%3A0x32f7f02c5e77043a!2sM%C3%BCnchner+Str.+123%2C+85774+Unterf%C3%B6hring%2C+Germany!5e0!3m2!1sen!2sin!4v1565347252768!5m2!1sen!2sin" data-autoscale ></div>
-
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:
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.
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
Distributed under the MIT License. See LICENSE for more information.
Not all services (example: twitch) allow automatic/easy thumbnail fetch.