The Dilemma

I recently had a client who wanted to feature an image on their site that, when clicked, would reveal a YouTube video over top of it. Both the placeholder image and the video needed to be full-width and responsive, and they also wanted a play button and a close button to appear when appropriate. I searched for a solution to this, but was unable to find anything that suited all of the client's needs, so I decided to try writing this from scratch. Check out the pen and my code below (and be sure to check out the full page too):

The HTML

I wanted to keep this code as clean as possible, so the HTML markup is pretty straightforward. It simply creates a wrapper that contains the placeholder image, the play/close button, and, later, the iframe for the video and the close button.

  <section class="video">
  <img src="#" data-video="#" title="Play Video" class="video__placeholder" />
  <button class="video__button"></button>
</section>

Because the client needed to be able to easily swap out the image and video paths, I used an <img> tag, where the source points to the placeholder image file, and data-video points to the video embed URL. It's important to note here that the data-video attribute links to the embed URL and not the actual video URL. For example:

  <!-- Correct method -->
<img src="#" data-video="http://www.youtube.com/embed/q3Dt6kI-ajA" />

<!-- Incorrect method -->
<img src="#" data-video="http://youtu.be/q3Dt6kI-ajA" />

You can find the correct URL inside the embed code, and you can add ?autoplay=1 to the end of the data-video attribute to autoplay the video when it loads.

The CSS (more like SCSS)

The following code is written in SCSS. To view the compiled CSS, go here.

Nothing too fancy with the CSS for this one. I first defined the selectors for the wrapper and the placeholder image. The wrapper needs to have a relative position so that nested elements can be positioned absolutely within it. Without this, the button's positioning would be based off the document, or page, dimensions. Setting the image to have a minimum width of 100% is what allows it to stretch across the entire page and shrink as the user scales the page down. Because there are no anchor tags being used, it's a good idea to set the cursor to pointer, so that when the user moves their mouse over the image, they know it can be clicked.

  .video {
  height: $video-height;
  position: relative;
  overflow: hidden;
  cursor: pointer;
}
.video__placeholder { min-width: 100%; display: block; }

I used a variable to define the height so that I could easily reference this for both the video and the iframe. Viewport units work well here (most instances of this I've done 100vh), but whatever you include, you'll need to make sure it works responsively.

The rest of the CSS simply positions the buttons and the iframe within the wrapper. I used one button for both the play and close buttons, since they're mostly the same, but you could separate these.

  .video__button {
  background: transparent;
  width: 100px;
  height: 100px;
  top: 50%;
  left: 50%;
  position: absolute;
  display: block;
  border: 3px solid #232439;
  border-radius: 50%;
  outline: none;
  cursor: pointer;
  transition: all 0.3s ease-in-out;
  transform: translateX(-50%) translateY(-50%);

  &:before,
  &:after {
    content: "";
    position: absolute;
    display: block;
    background-color: #232439;
    width: 35px;
    height: 3px;
    top: 50%;
    left: 35%;
    transition: background-color 0.3s;
  }

  &:before { transform: translateY(-11px) rotate(45deg); }
  &:after { transform: translateY(11px) rotate(-45deg); }

  &.is-playing {
    top: 1rem;
    right: 1rem;
    left: auto;
    transform: none;
  }

  &.is-playing:before,
  &.is-playing:after { left: 32%; }

  &.is-playing:before { transform: translateY(0) rotate(45deg); }
  &.is-playing:after { transform: translateY(0) rotate(-45deg); }
}
.video:hover .video__button {
  border-color: #6ddce5;

  &:before,
  &:after {
    background-color: #6ddce5;
  }
}

#video-player {
  width: 100%;
  height: $video-height;
  top: 0;
  left: 0;
  position: absolute;
  display: block;
}

The jQuery

Be sure to include the most recent version of the jQuery library into your code. Without it, the following code won't work.

jQuery is the key to making everything work, and this particular code is powered by an onclick function. You can see there are two selectors for the onclick: the image and the play button. This simply allows the user to click either the image or the play button to show the video, which is more user friendly than having one or the other.

From there, the function triggers the following:

  1. Checks to see if the iframe already exists
  2. Defines a video variable that contains the code for the iframe
  3. Inserts the iframe code after the placeholder image tag
  4. Adds a class to the play button to transition it to be the close button
  5. If the iframe already exists, remove it and transition the close button back to the play button
  $('.video__placeholder, .video__button').on('click', function() {
  if ( !$('#video-player').length ) {
    var video = '<iframe id="video-player" src="' + $('.video__placeholder').attr('data-video') + '" frameborder="0" allowfullscreen wmode="opaque"></iframe>';
    $(video).insertAfter( $('.video__placeholder') );
    $('.video__button').addClass('is-playing');
  } else {
    $('.video__button').removeClass('is-playing');
    $('#video-player').remove();
  }
});

And that's all there is to it! What I thought would be a daunting task turned out to be easier than expected. Thanks for reading, and I hope this can be of some help!

Notes

  • I'm not sure how accessible this method is, so if you have any insight or improvements, let me know!
  • This method involves injecting an iframe manually. If you're looking for more control (play/pause features) with the video itself, refer to the YouTube IFrame Player API. You would need to remove the iframe injection and replace it with the API code, which requires an empty div to target.