Preventing the browser from reading the YouTube code until the image was clicked


#21

Forgive me if I’m wrong, but I have the feeling it’s the “transferring, loading” being displayed that is the main concern.

With 9 videos, that is likely to add up to a lot of weight, and they will need to download at some point. Either on initial page load or on demand. AFAIK, some kind of deferred lazy-load could work for sequentially viewed videos, but with 9 to choose from I don’t see how that could work.


#22

I have one that uses only 1 player behind an image, maybe it will work on that one.
https://jsfiddle.net/zb6mkug3/573/

I just found this:


#23

How would I convert this to be able to be used with const?

For this single player code?
https://jsfiddle.net/zb6mkug3/573/

(function() {
        var youtube = document.querySelectorAll(".youtube");
        for (var i = 0; i < youtube.length; i++) {
            var source = "https://img.youtube.com/vi/" + youtube[i].dataset.embed + "/sddefault.jpg";
            var image = new Image();
            image.src = source;
            image.addEventListener("load", function() {
                youtube[i].appendChild(image);
            }(i));
            youtube[i].addEventListener("click", function() {
                var iframe = document.createElement("iframe");
                iframe.setAttribute("frameborder", "0");
                iframe.setAttribute("allowfullscreen", "");
                iframe.setAttribute("src", "https://www.youtube.com/embed/" + this.dataset.embed + "?rel=0&showinfo=0&autoplay=1");
                this.innerHTML = "";
                this.appendChild(iframe);
            });

Const:


(function() {
        const youtube = document.querySelectorAll(".youtube");
        for (const i = 0; i < youtube.length; i++) {
            const source = "https://img.youtube.com/vi/" + youtube[i].dataset.embed + "/sddefault.jpg";
            const image = new Image();
            image.src = source;
            image.addEventListener("load", function() {
                youtube[i].appendChild(image);
            }(i));
            youtube[i].addEventListener("click", function() {
                const iframe = document.createElement("iframe");
                iframe.setAttribute("frameborder", "0");
                iframe.setAttribute("allowfullscreen", "");
                iframe.setAttribute("src", "https://www.youtube.com/embed/" + this.dataset.embed + "?rel=0&showinfo=0&autoplay=1");
                this.innerHTML = "";
                this.appendChild(iframe);
            });

#24

I would be using everything except for the image part of it I think.

That would just be this I think:

<div class="js-player" data-embed="M7lc1UVf-VE">

        const youtube = document.querySelectorAll(".js-player");
        for (const i = 0; i < youtube.length; i++)

  youtube[i].addEventListener("click", function() {
    const iframe = document.createElement("iframe");
    iframe.setAttribute("frameborder", "0");
    iframe.setAttribute("allowfullscreen", "");
    iframe.setAttribute("src", "https://www.youtube.com/embed/" + this.dataset.embed + "?rel=0&showinfo=0&autoplay=1");
    this.innerHTML = "";
    this.appendChild(iframe);
  });

The directions on how to set it up is here, but I can’t get it to work:
https://jsfiddle.net/zb6mkug3/573/


#25

I got it working here:
https://jsfiddle.net/zb6mkug3/594/

(function iife() {
  "use strict";

let youtube = document.querySelector('.youtube');

let thumbnail = `https://i.imgur.com/AJDZEOX.jpg`;

let image = new Image();
image.src = thumbnail;

image.addEventListener('load', function() {
  youtube.appendChild(image);
});


youtube.addEventListener('click', function() {
  let iframe = document.createElement('iframe');
  iframe.setAttribute('frameborder', '0');
  iframe.setAttribute('src', `https://www.youtube-nocookie.com/embed/M7lc1UVf-VE?rel=0&amp;autoplay=1&amp;controls=1&amp;iv_load_policy=3&amp;cc_load_policy=0&amp;fs=0&disablekb=1&amp;enablejsapi=1`);
  this.innerHTML = '';
  this.appendChild(iframe);
});
}());


  <div class='container'>
    <div class="youtube" data-embed="dQw4w9WgXcQ">
      <svg class="play" width="600" height="338" viewbox="-3 -0.3 30 24.655">
    <path d="M16.036 11.58l-6-3.82a.5.5 0 0 0-.77.42v7.64a.498.498 0 0 0 .77.419l6-3.817c.145-.092.23-.25.23-.422s-.085-.33-.23-.42z"></path>
    <path d="M12 22.75C6.072 22.75 1.25 17.928 1.25 12S6.072 1.25 12 1.25 22.75 6.072 22.75 12 17.928 22.75 12 22.75zm0-20C6.9 2.75 2.75 6.9 2.75 12S6.9 21.25 12 21.25s9.25-4.15 9.25-9.25S17.1 2.75 12 2.75z"></path>
    </svg>

      <div class="lines"></div>


    </div>
  </div>

.container {
  position: relative;
  width: 606px;
  height: 344px;
  cursor: pointer;
  margin-top: 45px;
  border-radius: 25px;
  border: 3px solid #0059dd;
  box-sizing: border-box;
  overflow: hidden;
  background: #000;
}

.container .lines::before,
.container .lines::after {
  content: "";
  position: absolute;
  top: 0;
  left: 198px;
  width: 3px;
  height: 100%;
  background: #0059dd;
}

.container .lines::after {
  left: 399px;
}

.youtube iframe {
  position: absolute;
  top: -3px;
  left: -3px;
  width: 606px;
  height: 344px;
  border-radius: 25px;
}

.youtube .play {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  margin: auto;
  fill: #fa33fc;
}

I don’t believe this can work with that code:


(function videoPlayer() {
  "use strict";

  const tag = document.createElement("script");
  tag.src = "https://www.youtube.com/player_api";
  const firstScriptTag = document.getElementsByTagName("script")[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

  function onPlayerReady(event) {
    const youtubePlayer = event.target;
    youtubePlayer.setVolume(50); // percent
  }
  window.onYouTubePlayerAPIReady = function() {
    new YT.Player(document.querySelector(".js-player"), {
      events: {
        "onReady": onPlayerReady
      }
    });
  };
}());

And this code would no-longer be necessary:


(function iife() {
  "use strict";
  const show = (el) => el.classList.remove("hide");
  const hide = (el) => el.classList.add("hide");

  function coverClickHandler(evt) {
    const cover = evt.currentTarget;
    const thewrap = cover.parentNode.querySelector(".wrapg");
    hide(cover);
    show(thewrap);
  }
  const cover = document.querySelector(".jacketc");
  cover.addEventListener("click", coverClickHandler);
}());

#26

Is there an easier way to add lazy load to this code keeping the javascript that’s already in here without taking it all apart?

https://jsfiddle.net/zb6mkug3/598/


(function iife() {
  "use strict";
  const show = (el) => el.classList.remove("hide");
  const hide = (el) => el.classList.add("hide");

  function coverClickHandler(evt) {
    const cover = evt.currentTarget;
    const thewrap = cover.parentNode.querySelector(".wrapg");
    hide(cover);
    show(thewrap);
  }
  const cover = document.querySelector(".jacketc");
  cover.addEventListener("click", coverClickHandler);
}());

(function videoPlayer() {
  "use strict";

  const tag = document.createElement("script");
  tag.src = "https://www.youtube.com/player_api";
  const firstScriptTag = document.getElementsByTagName("script")[0];
  firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

  function onPlayerReady(event) {
    const youtubePlayer = event.target;
    youtubePlayer.setVolume(50); // percent
  }
  window.onYouTubePlayerAPIReady = function() {
    new YT.Player(document.querySelector(".js-player"), {
      events: {
        "onReady": onPlayerReady
      }
    });
  };
}());

#27

I guess that I should demonstrate then that it can and does work.

First, we can start with a clean slate, and add in pieces as and when we need them.

Here is a button, and a placeholder for the videoplayer.

<button id="loadplayer">Load Player</button>
<div class="js-player"></div>

Here’s the videoplayer code, using an init function to trigger the loadPlayer() function.

const videoPlayer = (function videoPlayer() {
  "use strict";

  function loadPlayer() {
    const tag = document.createElement("script");
    tag.src = "https://www.youtube.com/player_api";
    const firstScriptTag = document.getElementsByTagName("script")[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  }
  function onPlayerReady(event) {
    const youtubePlayer = event.target;
    youtubePlayer.setVolume(50); // percent
  }
  window.onYouTubePlayerAPIReady = function() {
    new YT.Player(document.querySelector(".js-player"), {
      events: {
        "onReady": onPlayerReady
      }
    });
  };
  function init() {
    loadPlayer();
  }
  return {
    init
  };
}());

And here is the button telling the video player to load.

document.querySelector("#loadplayer").addEventListener("click", function () {
  videoPlayer.init();
});

That alone, gives us a video player that is delayed until we decide to activate it, by clicking the button.
In actual practice it is clicking the cover that will do what the button does.

https://jsfiddle.net/pmw57/3f4tnajh/2/

We need to next tell that videoplayer what to load.


#28

Yes, that works.


#29

Where would the video id go?
https://jsfiddle.net/zb6mkug3/600/

I got it:
https://jsfiddle.net/zb6mkug3/602/


#30

How do I get the video to appear after it’s clicked?
https://jsfiddle.net/zb6mkug3/602/

It takes 2 clicks for it to load:
https://jsfiddle.net/zb6mkug3/604/

I don’t have it connected properly.

After I click on the image nothing appears, I click again, then the video appears.


#31

I was working through taking things further, and learned that the window.onYouTubePlayerAPIReady event can only be run once.

As a result, I’m going to use a different option where each video has the videoId associated with it, and loading the video players initializes all of them at once.

<p><button id="loadplayers">Load Players</button></p>
<div data-id="M7lc1UVf-VE" class="video"></div>
<div data-id="ylLzyHk54Z0" class="video"></div>

I can now loop through each of the videos, and get each different data-id value to initialize those videos.

  window.onYouTubePlayerAPIReady = function() {
    const videos = document.querySelectorAll(".video");
    videos.forEach(function (video) {
      const videoId = video.getAttribute("data-id");
      new YT.Player(video, {
        width: 200,
        height: 200,
        videoId: videoId,
        events: {
          "onReady": onPlayerReady
        }
      });
    });
  }

The one button click is now allowing all of the appropriate videos to load.
https://jsfiddle.net/pmw57/3f4tnajh/41/

I’ll take things further tomorrow.


#32

I just added that to this one:
https://jsfiddle.net/zb6mkug3/616/

But haven’t been able to figure out how to have it show after it’s clicked.

This is what I see after it’s clicked once:


#33

I now want to load the videos separately, which means waiting until the youtube API is ready before adding them.
It’ll help to move the code for adding a video out to a separate function:

  function addVideo(video) {
    const videoId = video.getAttribute("data-id");
    new YT.Player(video, {
      ...
    });
  }

I can now load the player from the init code, and use setInterval to check if the youtube API is available.
As soon as it is available, I can then use that you add the video.

  function init(video) {
    loadPlayer();
    let timer = setInterval(function checkAPIReady() {
      if (apiIsReady) {
        timer = null;
        addVideo(video);
      }
    }, 100);
  }

With a couple of test buttons, and a couple of videos, they now load separately.

<p><button id="loadplayer1">Load Player 1</button></p>
<div data-id="M7lc1UVf-VE" id="video1"></div>
<p><button id="loadplayer2">Load Player 2</button></p>
<div data-id="ylLzyHk54Z0" id="video2"></div>
document.querySelector("#loadplayer1").addEventListener("click", function () {
  videoPlayer.init(document.querySelector("#video1"));
});
document.querySelector("#loadplayer2").addEventListener("click", function () {
  videoPlayer.init(document.querySelector("#video2"));
});

https://jsfiddle.net/pmw57/3f4tnajh/50/


#34

I have questions about the single player version:
If there is only 1 on the page.
https://jsfiddle.net/zb6mkug3/638/

My first question is:

After I click the image, the player should be there, it’s not.
How do I do that?

I can’t figure this out.

Update:

I changed loadplayer to a class and added it to the jacket.

Did I do this right?
https://jsfiddle.net/zb6mkug3/647/

<div class="jacketc loadplayer">

(function iife() {
  "use strict";
  document.querySelector(".loadplayer").addEventListener("click", function() {
    videoPlayer.init();
  });
}());

#35

Using separate names for the videos is a bad approach to take.

As soon as you get tempted to use namea, nameb, namec as classnames, you’re using class names incorrectly. Classnames are supposed to let you apply the same classname to multiple items. Unique identifiers are what you use instead when only one of the name is allowed.

However, unique identifiers are frequently the wrong approach, and are a good indicator that there’s normally a better way to do things.

Remove video id’s

As a result, I’m going to remove the unique identifiers from the video sections:

<p><button id="loadplayer1">Load Player 1</button></p>
<!--<div data-id="M7lc1UVf-VE" id="video1"></div>-->
<div data-id="M7lc1UVf-VE"></div>
<p><button id="loadplayer2">Load Player 2</button></p>
<!--<div data-id="ylLzyHk54Z0" id="video2"></div>-->
<div data-id="ylLzyHk54Z0"></div>

Instead of using a unique identifier, we can get the next element after the button instead.

// document.querySelector("#loadplayer1").addEventListener("click", function () {
document.querySelector("#loadplayer1").addEventListener("click", function (evt) {
  const wrapper = evt.target.parentNode;
  videoPlayer.init(wrapper.nextElementSibling);
});});
// document.querySelector("#loadplayer2").addEventListener("click", function () {
document.querySelector("#loadplayer2").addEventListener("click", function (evt) {
  const wrapper = evt.target.parentNode;
  videoPlayer.init(wrapper.nextElementSibling);
});

Remove the button id’s

Instead of using an identifier on the button elements, which forces us to duplicate our code, we can use a class name and handle all of the button elements at the same time.

<!--<p><button id="loadplayer1">Load Player 1</button></p>-->
<p><button class="loadplayer">Load Player 1</button></p>
...
<!--<p><button id="loadplayer2">Load Player 2</button></p>-->
<p><button class="loadplayer">Load Player 1</button></p>
...
// document.querySelector("#loadplayer1").addEventListener("click", function (evt) {
//   const wrapper = evt.target.parentNode;
//   videoPlayer.init(wrapper.nextElementSibling);
// });
// document.querySelector("#loadplayer2").addEventListener("click", function () {
//   videoPlayer.init(document.querySelector("#video2"));
// });
document.querySelectorAll(".loadplayer").forEach(function (button) {
  button.addEventListener("click", function (evt) {
    const wrapper = evt.target.parentNode;
    videoPlayer.init(wrapper.nextElementSibling);
  });
});

And now, we can individually load those youtube videos, without needing unique classes or unique identifiers.
A single classname for the button is all we need.

https://jsfiddle.net/pmw57/3f4tnajh/56/

We’re on the right track here. We should have all that we now need to apply this technique to the grid of videos.


#36

I’m working on the single player version of the code.

That’s the one I have a few questions for you about.


#37

I find it’s best to focus on only one thing at a time.


#38

Carrying on from the code at https://jsfiddle.net/zb6mkug3/570/ I’m going to remove all of the videoplayer code, so that there’s just the cover code there.

That gives us cover code that still shows the video, even though the videoplayer code isn’t there. https://jsfiddle.net/pmw57/xe62o9us/
That’s because the iframe was loading the youtube videos.

I replace the iframe with a basic div, in preparation for the videoplayer code to convert that to an iframe.

    <!--<iframe class="js-playera .playinga" src="https://www.youtube-nocookie.com/embed/M7lc1UVf-VE?rel=0&amp;autoplay=0&amp;controls=1&amp;iv_load_policy=3&amp;cc_load_policy=0&amp;fs=0&disablekb=1&amp;enablejsapi=1" frameborder="0" allow="encrypted-media"></iframe>-->
    <div class="video" data-id="M7lc1UVf-VE">Video content not yet present</div>

When clicking each play section, the “not yet present” video information shows, helping to confirm that the cover part of the code still continues to work. I’ve also tidied up the HTML code so that there’s just a single whitespace gap between each of the players. https://jsfiddle.net/pmw57/xe62o9us/9/

I can now put in the videoplayer code from https://jsfiddle.net/pmw57/3f4tnajh/56/ and use that from the coverclickhandler code to load the video. We need to easily get from the SVG to the video, which is as easy as getting the next element sibling and then it’s .video' child.

The evt.target though could be the path element or the svg element. We can instead use evt.currentTarget to always reliably get the svg element that we attached the event listener to.

  function coverClickHandler(evt) {
    const wrapper = evt.currentTarget.nextElementSibling;
    show(wrapper);
    videoPlayer.init(wrapper.querySelector(".video"));
  }

The videos are now loading when you click on them, but the CSS is having trouble showing them in the correct place, so the CSS is the next thing to work on from here. https://jsfiddle.net/pmw57/xe62o9us/10/


#39

This just gave me some ideas on how I can improve the single player version, which now I have a few more questions on.


#40

I’ve just found that the SVG arrows are showing on the wrong locations. The B arrow is showing on A, for example. I’ll be taking some time delving more deeply into that to investigate how to fix that.