JSPM

@gothassos/iframemanager

1.2.6
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 52
  • Score
    100M100P100Q77830F
  • License MIT

GDPR friendly iframe manager written in vanilla js

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

IframeManager Logo

Demo   |   Features   |   Installation   

License: MIT Size Stable version

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

  1. Download the latest release or use via CDN/NPM:

    https://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
  2. Import script + stylesheet:

    <html>
      <head>
        ...
        <link rel="stylesheet" href="iframemanager.css">
      </head>
      <body>
        ...
        <script defer src="iframemanager.js"></script>
      <body>
    </html>
  3. 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>

  • 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 title
    • data-params : [String] iframe query parameters
    • data-thumbnail : [String] path to custom thumbnail
    • data-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:

    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:

      <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&#39;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&amp;ref_src=twsrc%5Etfw">#nature</a> <a href="https://twitter.com/hashtag/sunset?src=hash&amp;ref_src=twsrc%5Etfw">#sunset</a> <a href="http://t.co/YuKy2rcjyU">pic.twitter.com/YuKy2rcjyU</a></p>&mdash; 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>
    2. 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:

    1. set/erase cookie
    2. 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:
      <!-- 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.