How to swipe YouTube embedded clip in Slick gallery slider on mobile

Hello there

Recently, I have run into the problem that I couldn’t swipe the slides with YouTube embedded clips in the Slick gallery. Instead of swiping, nothing was happening. In other words, when I tried to touch and swipe the slide to the left or the right on a mobile device, the slide and the slider didn’t react in any way. Nevertheless, when I just touched the YouTube embedded clip, it just started playing normaly. I tried to play with the settings of the Slick slider, and then I concluded that it was impossible to fix it that way.

I searched for the solution, and I didn’t find anything that could help me. So, I thought that it would be a very interesting task to solve.

First of all, as I understood, the problem was in the embedded objects that caught the focus of the main window. When a user tried to swipe and touch the slide, the touch event didn’t pass to the window context, and the Slick slider handler couldn’t receive the data of the event. An empty div tag, stretched above the embedded object, could resolve this problem, but in that case the user would not be able to start a Youtube clip. When the user would have tried to touch the play button, he would touch the empty div tag, not the Youtube embedded object.

After a couple of hours of googling, I found the solution. YouTube provides the API for their embedded clips, and you can control almost everything in the embedded object. You can programmatically change the volume, the timecode, the state – play/stop, etc. By the link, you can find the description of this API.

Secondly, I joint the idea of an empty div tag and the ability to control an embedded object programmatically with JavaScript. It means when the user touches the empty div tag, you can programmatically handle the event, and based on the state of the YouTube embedded clip you can start to play or stop playing the clip. So, I have made a simple example with the solution.

Explaining of the solution:

1. You need to make a simple markup, like this:

<div class="video-review-slick-slider">
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video1"></div>
          <div class="player-init" id="video1" data-yt-id="f9O3yURTBCM"></div>
        </div>
        
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video2"></div>
          <div class="player-init" id="video2" data-yt-id="nhm8aal5F1c"></div>
        </div>
        
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video3"></div>
          <div class="player-init" id="video3" data-yt-id="JkI2wJQ5YcM"></div>
        </div>
        
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video4"></div>
          <div class="player-init" id="video4" data-yt-id="ZA4lISqqUXU"></div>
        </div>
           
        <div class="slide">
          <div class="gap" data-action-type="play" data-target-id="video5"></div>
          <div class="player-init" id="video5" data-yt-id="NIjMeUDurrk"></div>
        </div>
</div> 

The div tag with class video-review-slick-slider is a root container for the slider. It contains the slides with divs. The first of these divs is a gap or an empty div tag. This div has attributes – data-action-type that has the state of the embedded YouTube object. Also, this div has data-target-id that holds the id of the second div that used to make a YouTube embedded object. The second div has attributes like id and data-yt-id, which means the id for data-target-id of the previous div and the id of the YouTube video clip.

2. Next step, you need to initialize the YouTube API as it specified in the documentation:

var tag = document.createElement('script');
tag.id = 'iframe-demo';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

3. After that, you need to write a simple function that contains logic with the initialization of the YouTube embedded clips and attaches the handle for the empty div tag with that class gap. The function is below.

var players = {};

function init_players()
{
  $('.player-init').each(function() {
    
    if(!$(this).data('initialized'))
    {
    
      let player_cont_id = $(this).attr('id');
      let yt_video_id = $(this).data('yt-id');
      
      if(player_cont_id && yt_video_id)
      {
        
        players[player_cont_id] = new YT.Player(player_cont_id, {
          videoId: yt_video_id,
          height: '380',
          width: '100%'
        });
        
      } else {
        
        if(player_cont_id == '')
        {
          player_cont_id = 'video_'+Math.random(999,9999);
          $(this).attr('id',player_cont_id);
          $(this).prev().data('target-id',player_cont_id);
          
          players[player_cont_id] = new YT.Player(player_cont_id, {
            videoId: yt_video_id,
            height: '380',
            width: '100%'
          });
        }
      }
      
      
      $(this).data('initialized',true);
    }
  });

  $('.video-review-slick-slider .slide .gap').off('click.video');
  $('.video-review-slick-slider .slide .gap').on('click.video',function() {
    
    var target_id = $(this).data('target-id');
    var action_type = $(this).data('action-type');
    
    if(typeof(players[target_id]) == 'object')
    {
        if(action_type == 'play')
        {
          players[target_id].playVideo();
          $(this).data('action-type','pause');
        }
        
        if(action_type == 'pause')
        {
          players[target_id].pauseVideo();
          $(this).data('action-type','play');
        }
    }
    
  });
}

In this function:

  • Each div element with class player-init is being checked if it is initialized, and if it is not initialized then initialization starts. Then two variables are filled with the values of the id container for the embedded YouTube player and the id that is the YouTube clip id. However, when the Slick slider runs, it clones the slides for the infinity swiping. So, when the Slick slider does this, it is checking the id attribute on uniqueness. If the id already exists, then it fills the attribute with an empty value, so in the function above there is check for this case. The id attribute with an empty value is being filled with an automatically generated value and also being assigned to the gap element attribute data-target-id.
  • The two variables are used to create a new YouTube (YT) object that is added to the global variable players.
  • The click handler event is being attached to the gap elements. This handler contains a simple check based on the state of the YouTube embedded object using a data-action-type attribute./li>

4. Having defined the main function for the YouTube objects, it’s time to define the function that will be called when the YouTube API is loaded on the page. The function name is onYouTubeIframeAPIReady, and it is described in the documentation to the API.

function onYouTubeIframeAPIReady() {
  init_players();
}

5. The final step is writing the definition for the Slick slider initialization.

$('.video-review-slick-slider').slick({
  slidesToShow: 1,
  slidesToScroll: 1,
  dots: false,
  arrows:true,
  centerMode: false,
  swipe: true,
  focusOnSelect: true,
  centerPadding: '0px',
  adaptiveHeight: false,
  responsive: [{
      breakpoint: 768,
      settings: {
        arrows: false,
        centerMode: true,
        centerPadding: '40px',
        slidesToShow: 1,
        dots: true
      }
    },
    {
      breakpoint: 480,
      settings: {
        arrows: false,
        centerMode: true,
        centerPadding: '40px',
        slidesToShow: 1,
        dots: true
      }
    }
  ]
});

6. But in one row with the scripts, it is needed to add css styles for the slides and the gap elements. They are below:

.video-review-slick-slider .slide
{
  position: relative;
}

.video-review-slick-slider .slide iframe
{
  position: relative;
  z-index: 2;
  padding: 0 10px;
  margin: 0 10px;
  max-width: 95%;
}

.video-review-slick-slider .slide .gap
{
  position: absolute;
  top: 50px;
  left: 0px;
  width: 100%;
  height: 65%;
  z-index: 10;
}

In the style definition for the gap element there are important properties like height and top. In this case, they allow the user to use the control elements of the YouTube embedded objects like – play button, pause button, settings button, volume bar, timecode bar, etc.; because the gap element provides only the play or stop functionality.

7. But when you run this code on your page, you will notice that the YouTube videos do not stop playing after a swiping gesture. It means there will be a lot of YouTube clips playing simultaneously. To prevent this behaviour, you can just add a simple handler to the swipe event of the Slick slider before its initialization; the function handler is below:

$('.video-review-slick-slider').on('swipe', function(event, slick, direction) {
  
  $('.video-review-slick-slider .gap').data('action-type','play');
  for(object in players)
  {
    let player = players[object];
    player.pauseVideo();
  }

});

In this handler, after a swiping gesture all the YouTube clips are stopped.

The working example of this solution is available by the link.


Jade nested mixins

Hello everyone!

If you are a frontend developer and you are using gulp/grunt/webpack with the template engine like Jade,  it is very a likely that you face with a problem like Рhow to create mixin with mixin inside.

In other words, you need to set mixin with your random name and display it inside your parent mixin in the particular place. It seems like a javascript object with methods which would be called in loop by their names.

You can do it in Jade with the code like this:

//- global object for nested mixins

- blocks = {}

//- mixin for set other mixin into global object for nested mixins

mixin set(key)

- blocks[key] = this.block

mixin container-for-content

.container

//- It needs for declaring place where nested mixis will displayed.

block

.blocks
  //- Runs loop for iterates each block in global blocks object and runs "render" function
  each element, i in blocks
  = blocks[i]()

Now you can use this construction like this:

+container-for-content
 +set('first-block')
   .first-row
     | First row
 +set('second-block')
    .second-row
     | Second row

And as a result you should get output like this:

<div class="first-row">First row</div>
<div class="second-row">Second row</div>