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 mehrere YouTube-Videos auf einer Seite zur Verfügung stellen möchte, erkennt man schnell wie viel 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 title-Attribut der Video-Titel eingeblendet werden und mit dem Attribut 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 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 Meta-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 weiterzureichen (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,74 kByte) 30.03.2022 20:20
// 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ädt 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.className = 'overlay';
      document.body.appendChild(overlay);
      var content = document.createElement('div');
      content.className = 'popup-content';
      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 content.id;
    }
    // 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;
            }
          }
        }
        // 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
        play.removeEventListener('click', load);
      }
    }
    play.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,28 kByte) 13.07.2021 11:07

StyleSheet:
youtube.css StyleSheet (1,93 kByte) 30.03.2022 20:04
/* 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;
}

Kontakt

Udo Schmal
Udo Schmal

Udo Schmal
Softwareentwickler
Ellerndiek 26
24837 Schleswig
Schleswig-Holstein
Germany




+49 4621 9785538
+49 1575 0663676
+49 4621 9785539
SMS
WhatsApp

Google Maps Profile
Instagram Profile
vCard 2.1, vCard 3.0, vCard 4.0

Service Infos

CMS Info

Product Name:
UDOs Webserver
Version:
0.5.1.200
Description:
All in one Webserver
Copyright:
Udo Schmal
Compilation:
Wed, 25. Sep 2024 20:55:53

Development Info

Compiler:
Free Pascal FPC 3.3.1
compiled for:
OS:Linux, CPU:x86_64

System Info

OS:
Ubuntu 22.04.5 LTS (Jammy Jellyfish)

Hardware Info

Model:
Hewlett-Packard HP Pavilion dm4 Notebook PC
CPU Name:
Intel(R) Core(TM) i5-2430M CPU @ 2.40GHz
CPU Type:
x86_64, 1 physical CPU(s), 2 Core(s), 4 logical CPU(s),  MHz