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.
Table of Contents
Features
- 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
Installation
-
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
-
Import script + stylesheet:
<html>
<head>
...
<link rel="stylesheet" href="iframemanager.css">
</head>
<body>
...
<script defer src="iframemanager.js"></script>
<body>
</html>
-
Configure and run:
-
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();
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>
<script>
window.addEventListener('load', function(){
const im = iframemanager();
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>
-
Create a div with data-service
and data-id
attributes:
<div data-service="youtube" data-id="<video-id>"></div>
Configuration options
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]
How to set attributes on the iframe
element
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',
autoLang: false,
onChange: ({changedServices, eventSource}) => {
},
services : {
myservice : {
embedUrl: 'https://<myservice_embed_url>',
thumbnailUrl: 'https://<myservice_embed_thumbnail_url>',
iframe: {
allow: 'fullscreen',
params: 'mute=1&start=21',
onload: (dataId, setThumbnail) => {
console.log(`loaded iframe with data-id=${dataId}`);
}
},
cookie: {
name: 'cc_youtube',
path: '/',
samesite: 'lax',
domain: location.hostname
},
languages: {
en: {
notice: 'Html <b>notice</b> message',
loadBtn: 'Load video',
loadAllBtn: "Don't ask again"
}
}
},
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:
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.
-
Place the markup inside a special data-placeholder
div. Remove any script
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) => {
await CookieConsent.loadScript('https://platform.twitter.com/widgets.js');
await im.childExists({childProperty: 'twttr'}) && await twttr.widgets.load(div);
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:
<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>
APIs
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:
im.acceptService('youtube');
im.acceptService('all');
im.rejectService('youtube');
im.rejectService('all');
const config = im.getConfig();
const state = im.getState();
im.reset();
im.reset(true);
Both acceptService
and rejectService
work the same way:
- set/erase cookie
- create/remove iframes
Configuration examples
-
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:
<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) => {
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>
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:
im.run({
currLang: 'en',
onChange: ({changedServices, eventSource}) => {
if(eventSource.type === 'click') {
const servicesToAccept = [
...CookieConsent.getUserPreferences().acceptedServices['analytics'],
...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:
License
Distributed under the MIT License. See LICENSE for more information.
Note
Not all services (example: twitch) allow automatic/easy thumbnail fetch.