Package Exports
- @gothassos/iframemanager
- @gothassos/iframemanager/dist/iframemanager.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@gothassos/iframemanager) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
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.
Table of Contents
- Key features
- Installation
- Configuration options & API
- Configuration examples
- Usage with CookieConsent
- License
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
latest release or use via CDN/NPM:
Download thehttps://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.2.5/dist/iframemanager.js https://cdn.jsdelivr.net/gh/orestbida/iframemanager@1.2.5/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(); // 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>
data-service
and data-id
attributes:
Create a div with <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', // 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); }
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 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.
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+]
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[]
Both acceptService
and rejectService
work the same way:
- set/erase cookie
- create/remove iframes
Configuration examples
Youtube
Example: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" } } } } });
<!-- 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>
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') {
// 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 for more information.
Note
Not all services (example: twitch) allow automatic/easy thumbnail fetch.