Lazy Loading YouTube (external content)- Vanilla JS

Hier möchte ich Euch nun eine Variante vorstellen um YouTube Videos auf Eurer Website einzubinden.
Gerade wenn man mehrer YouTube-Videos auf einer Seite zur Verfügung stellen möchte erkennt man schnell wieviel Daten von YouTube nachgeladen werden und das ohne auch nur ein Video gestartet zu haben. Einige von Euch möchten auch gerne den Besucher der Website darauf aufmerksam machen, dass externer Inhalt abgespielt wird und dadurch die Abrufdaten an YouTube gehen.

In dieser Variante habe ich das YouTube-IFrame was in den meisten Implementierungen verwendet wird durch ein DIV ersetzt, dieses bekommt das YouTube Thumbnail-/Poster-Bild als Hintergrund-Bild, welches durch das JavaScript von YouTube geladen wird oder noch besser welches man zuvor lokal auf den Server ablegt und in dem Attribut data-poster des DIV übergibt.

Passende Formate sind leicht durch einmaligen Download zu erhalten, folgende Möglichkeiten stehen zur Verfügung:

  • Hohe Qualität: https://img.youtube.com/vi/[youtube-video-id]/hqdefault.jpg
  • Medium Qualität: https://img.youtube.com/vi/[youtube-video-id]/mqdefault.jpg
  • Standard Qualität: https://img.youtube.com/vi/[youtube-video-id]/sddefault.jpg
  • Maximale Auflösung: https://img.youtube.com/vi/[youtube-video-id]/maxresdefault.jpg
  • andere Formate wie webp existieren auch, werden vom IE-11 allerdings nicht unterstützt
    standard quality: https://i.ytimg.com/vi_webp/[youtube-video-id]/sddefault.webp

Im data-src Attribute muss die YouTube IFrame src komplett oder auch nur die YouTube Video-Id angegeben werden, über JavaScript wird sowieso nur die Id verwendet, egal ob für das Video als auch für das Thumbnail-/Poster-Bild.

Des weiteren wird ein zentrierter Play-Button erzeugt der wie der Play-Button im YouTube Player aussieht, zu dem kann durch das titel-Attribut der Video-Titel eingeblendet werden und mit dem Attribute data-external ein Hinweistext eingeblendet werden das externe Resourcen von YouTube nachgeladen werden.

Durch das Setzen des Attributes data-target="popup" ist es auch möglich das Video in einem simplen Popup anzuzeigen in der auf die Browser-Fenster (in meinem Beispiel der Demo-Bereich) Abmessungen angepassten Qualität. Dieses Popup startet das Video unmittelbar nach der Einblendung, nach Beendigung des Videos schließt sich das Popup automatisch.

Die YouTube Player API wird durch diese Implementierung erst beim Start eines Videos geladen, wie auch das Video, wird ein zweites mal ein Video gestartet wird die API nicht erneut geladen.

Natürlich werden die Videos von YouTube ohne Cookies eingebunden (https://www.youtube-nocookie.com) und durch setzen des Mata-Tags referrer mit dem Wert no-referrer wird die Weitergabe des Referrers unterbunden.

Bei allen versuchen die Daten der Website-Besucher zu schützen sorgen die YouTube Scripte natürlich dafür die Information in welcher Seite das Video eingebettet ist an YouTube weiter zu reichen (https://www.youtube-nocookie.com/embed/ScMzIvxBSi4?enablejsapi=1&origin=https://www.gocher.me&widgetid=1).


HTML:
youtube.htm HTML (1,21 kByte) 21.04.2021 22:44
<!DOCTYPE html >
<html lang="de">
  <head>
    <meta charset="utf-8" />
    <title>External click YouTube</title>
    <link rel="stylesheet" href="youtube.css" />
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' https://fonts.gstatic.com https://www.youtube-nocookie.com/; script-src 'self' https://www.youtube.com/; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://img.youtube.com https://i.ytimg.com/ googlevideo.com" />
  </head>
  <body>
    <div class="youtube" title="YouTube video player" data-external="true" data-poster="/media/youtube_poster/bHQqvYy5KYo.jpg" data-src="https://www.youtube.com/embed/bHQqvYy5KYo?origin=https%3A%2F%2Fdevelopers.google.com&autohide=1&showinfo=0&video-id=bHQqvYy5KYo&enablejsapi=1&widgetid=1"></div>
    <div class="youtube" title="YouTube placeholder video" data-external="true" data-poster="/media/youtube_poster/ScMzIvxBSi4.jpg" data-src="ScMzIvxBSi4"></div>
    <div class="youtube" title="YouTube placeholder video POPUP" data-external="true" data-target="popup" data-poster="/media/youtube_poster/ScMzIvxBSi4.jpg" data-src="ScMzIvxBSi4"></div>
    <script src="youtube.js"></script>
  </body>
</html>
JavaScript:
youtube.js JavaScript (5,71 kByte) 08.04.2021 22:05
// coding: utf-8
/** Created by: Udo Schmal | https://www.gocher.me/ */
(function () {
  'use strict';
  function youtube(el) {
    var player, id;
    // don't send referrer infos to external services
    if (!document.querySelector('meta[name="referrer"]')) {
      let meta = document.createElement('meta');
      meta.name = 'referrer';
      meta.content = 'no-referrer';
      document.head.appendChild(meta);
    }
    // get YouTube video id
    if (el.dataset.src && el.dataset.src.length === 11) {
      id = el.dataset.src;
    } else {
      let src = el.dataset.src;
      let regExp = /^.*((youtube\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
      let match = src.match(regExp);
      id = (match && match[7].length == 11) ? match[7] : src;
    }
    el.id = id;
    // show in popup, other defined target or replace el with player
    var target = el.dataset.target || id;

    // create placeholder background image
    if (el.dataset.poster) {
      el.style.backgroundImage = "url('" + el.dataset.poster + "')";
    } else {
      // high quality: https://img.youtube.com/vi/<insert-youtube-video-id-here>/hqdefault.jpg
      // medium quality: https://img.youtube.com/vi/<insert-youtube-video-id-here>/mqdefault.jpg
      // standard quality: https://img.youtube.com/vi/<insert-youtube-video-id-here>/sddefault.jpg
      // maximum resolution: https://img.youtube.com/vi/<insert-youtube-video-id-here>/maxresdefault.jpg
      //el.style.backgroundImage = "url('https://i.ytimg.com/vi_webp/" + id + "/sddefault.webp')"; // not supported by IE11
      el.style.backgroundImage = "url('https://img.youtube.com/vi/" + id + "/sddefault.jpg')";
    }
    // show placeholder title
    if (el.title) {
      let title = document.createElement('div');
      title.className = 'title';
      title.appendChild(document.createTextNode(el.title));
      el.appendChild(title);
    }
    // show placeholder external message
    if (el.dataset.external) {
      let external = document.createElement('div');
      external.className = 'external';
      let language = window.navigator.userLanguage || window.navigator.language;
      if (!language) {
        language = 'en';
      }
      let txt;
      switch (language.substr(0,2)) {
        case 'de': txt = 'Läd externe Inhalte von YouTube'; break;
        case 'fr': txt = 'Charger du contenu externe à partir de YouTube'; break;
        case 'it': txt = 'Carica contenuti esterni da YouTube'; break;
        case 'es': txt = 'Cargar contenido externo de YouTube'; break;
        default: txt = 'Load external Content from YouTube'; break;
      }
      external.appendChild(document.createTextNode(txt));
      el.appendChild(external);
    }
    // show placeholder youtube like play button
    var play = document.createElement('div');
    play.className = 'play';
    el.appendChild(play);

    // create popup
    function createPopup() {
      var overlay = document.createElement('div');
      overlay.id = 'overlay';
      document.body.appendChild(overlay);
      var content = document.createElement('div');
      content.id = 'popup-content';
      overlay.appendChild(content);
      var close = document.createElement('span');
      close.className = 'popup-close';
      overlay.appendChild(close);
      close.addEventListener('click', function (event) {
        player.stopVideo();
        document.body.removeChild(overlay);
      });
      return 'popup-content';
    }
    // youtube player API
    function onPlayerReady(event) {
      event.target.playVideo();
    }
    function onPlayerStateChange(event) {
      if (event.data == window.YT.PlayerState.ENDED) {
        player.stopVideo();
        document.body.removeChild(overlay);
      }
    }
    // create youtube player
    function createPlayer() {
      window.YT.ready(function() {
        var dest, obj = {
          videoId: id,
          host: 'https://www.youtube-nocookie.com',
          events: {
            'onReady': onPlayerReady
          }
        };
        var sizes = [
          { width: '426', height: '240' },
          { width: '640', height: '360' },
          { width: '854', height: '480' },
          { width: '1280', height: '720' },
          { width: '1920', height: '1080' }
        ];
        if (target == 'popup') {
          dest = createPopup();
          obj.events['onStateChange'] = onPlayerStateChange;
          for(var i = 0; i < sizes.length; i++) {
            if (sizes[i].width <= window.innerWidth) {
              obj.height = sizes[i].height;
              obj.width = sizes[i].width;
            } else {
              break;
            }
          }
        } else {
          dest = target;
          for(var i = 0; i < sizes.length; i++) {
            if (sizes[i].width >= el.offsetWidth) {
              obj.height = sizes[i].height;
              obj.width = sizes[i].width;
              break;
            }
          }
        }
        console.log(obj);
        // create YouTube player
        player = new window.YT.Player(dest, obj);
      });
    }
    // load youtube external content
    function load() {
      // init once
      if (!document.getElementById('YouTubeApi')) {
        // load YouTube API
        var script = document.createElement('script');
        script.id = 'YouTubeApi';
        script.src = 'https://www.youtube.com/iframe_api';
        document.head.appendChild(script);
        script.addEventListener('load', createPlayer);
      } else {
        createPlayer();
      }
      if (target !== 'popup') {
        // remove placeholder click event
        el.removeEventListener('click', load);
      }
    }
    el.addEventListener('click', load);
  }

  var media = document.querySelectorAll('.youtube[data-src]');
  for (let i=0; i < media.length; i++) {
    new youtube(media[i]);
  }
})();

Sind viele Videos auf der Seite kann auch lazy loading Sinn machen, kombiniert würde lazy loading und youtube in diesem Fall natürlich noch effektiver sein youtube_lazy.js. (9,12 kByte) 08.04.2021 22:06

StyleSheet:
youtube.css StyleSheet (1,93 kByte) 21.04.2021 22:38
/* coding: utf-8 */
div.youtube {
  position: relative;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  width: 100%;
  height: 0;
  padding-top: 56.25%;
  margin: 5px;
  float: left;
}
div.youtube .title, div.youtube .external {
  font-family: Arial, Helvetiva, sans-serif;
  text-align: center;
}
div.youtube .title {
  position: absolute;
  top: 15px;
  width: 100%;
  color: #fff;
  text-shadow: 1px 1px 2px black, 0 0 1em black, 0 0 0.2em black;
}
div.youtube .external {
  position: absolute;
  top: 30%;
  width: 100%;
  color: red;
  text-shadow: 1px 1px 2px white, 0 0 1em white, 0 0 0.2em white;
}
div.youtube .play {
  background: #333;
  border-radius: 50% / 10%;
  color: #fff;
  font-size: 2em;
  height: 1.5em;
  width: 2em;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  cursor: pointer;
}
div.youtube .play:hover {
  background: red;
}
div.youtube .play::before {
  background: inherit;
  border-radius: 5% / 50%;
  bottom: 9%;
  content: "";
  left: -5%;
  position: absolute;
  right: -5%;
  top: 9%;
}
div.youtube .play::after {
  border-style: solid;
  border-width: 1em 0 1em 1.732em;
  border-color: transparent transparent transparent rgba(255, 255, 255, 0.75);
  content: " ";
  font-size: 0.35em;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 0;
}
#overlay {
  z-index: 100;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0,0,0,.8);
}
#popup-content {
  position: absolute;
  top: 50%;
  left: 50%;
  max-width: 100%;
  transform: translate(-50%, -50%);
}
.popup-close {
  position: absolute;
  top: 30px;
  right: 30px;
  cursor: pointer;
}
.popup-close::before {
  display: inline-block;
  content: "\00d7";
  color: #fff;
  font-size: 42px;
  font-weight: bold;
  text-shadow: 1px 1px 2px black, 0 0 1em black, 0 0 0.2em black;
  cursor: pointer;
}

Author: , published: , last modified:

Kontakt

Udo Schmal

Udo Schmal
Softwareentwickler
Olvengraben 41
47608 Geldern
Nordrhein-Westfalen
Germany





+49 2831 9776557
+49 1575 0663676
+49 2831 1328709
SMS
WhatsApp

Instagram Profile
vCard 3.0

Service Infos

CMS Info Product Name:
UDOs Webserver
Version:
0.5.0.58
Description:
All in one Webserver
Copyright:
Udo Schmal
Compilation:
Sat, 17. Apr 2021 17:15:14
Development Info Compiler:
Free Pascal FPC 3.3.1
compiled for:
OS:Linux, CPU:x86_64
System Info OS:
Ubuntu 20.04.2 LTS (Focal Fossa)
Hardware Info Model:
Hewlett-Packard HP Pavilion dv7 Notebook PC
CPU Name:
Intel(R) Core(TM) i5-2450M CPU @ 2.50GHz
CPU Type:
x86_64, 1 physical CPU(s), 2 Core(s), 4 logical CPU(s), 2893.370 MHz