Anna_B
Anna_B

Reputation: 890

How can I make an iframe behave like other media elements in a horizontal scrolling area?

This is my code:

$(document).ready(function() {
  $('.scrollable-area').on('wheel', function(e) {
    var scrollLeft = $(this).scrollLeft();
    var width = $(this).get(0).scrollWidth - $(this).width();
    var deltaY = e.originalEvent.deltaY;
    var deltaX = e.originalEvent.deltaX;
    var newScrollLeft = scrollLeft + deltaY + deltaX;
    if ((deltaY > 0 && newScrollLeft < width) ||
      (deltaY < 0 && newScrollLeft > 0)) {
      e.preventDefault();
    }
    if (newScrollLeft <= 0) {
      $(this).scrollLeft(0);
    } else if (newScrollLeft >= width) {
      $(this).scrollLeft(width);
    } else {
      $(this).scrollLeft(newScrollLeft);
    }
  });
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.gallery-container {
  position: relative;
  width: 100%;
  height: 90vh;
  background-color: yellow;
  overflow: hidden;
}

.scrollable-area {
  width: 100%;
  height: 100%;
  overflow-x: auto;
}

.gallery-items {
  display: flex;
  min-width: 100%;
  height: 100%;
}

.gallery-item {
  flex: 0 0 auto;
  height: 100%;
  display: flex;
}

.gallery-item img {
  max-width: 100%;
  height: auto;
  object-fit: contain;
}

.gallery-item iframe {
  background-color: blue;
  width: auto;
  width: 800px;
}
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>

<div class="gallery-container">
  <div class="scrollable-area">
    <div class="gallery-items">
      <div class="gallery-item">
        <img src="https://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast%2C_Dhaka%2C_Bangladesh.JPG">
      </div>
      <div class="gallery-item">
        <img src="https://upload.wikimedia.org/wikipedia/commons/d/da/Sky_landscape.jpg">
      </div>
      <div class="gallery-item">
        <iframe src="https://player.vimeo.com/video/584985260" frameborder="0" allow="fullscreen"></iframe>
      </div>
      <div class="gallery-item">
        <img src="https://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast%2C_Dhaka%2C_Bangladesh.JPG">
      </div>
    </div>
  </div>
</div>

If you have the cursor on the media gallery and scroll down normally, you will first scroll horizontally through the gallery. That's on purpose. However, it stops when an iframe is included. I've already tried to prevent the pointer events during scrolling, but it leads to some other issues like making it impossible to click the play button of a video.

Also, it would be cool to find a way to always resize the iframe to that of the video and to fit the overall height of the horizontal gallery. Does anyone know how this works?

Upvotes: 3

Views: 1046

Answers (4)

ChipChop Gizmo
ChipChop Gizmo

Reputation: 401

There is one convoluted way that I've used in the past. It's an event listener injection into the iFrame and then a return back to the parent page.

I've just done a very quick test and it kinda works with your example.

$(document).ready(function() {
   
   ....your code here.....
   
   ...and just add this...
    //this will inject event listeners to any iframe in your page, you can do the selector any way you want, this is just an example
    let iframes = document.getElementsByTagName('iframe')
    for(let iframe of iframes){
        iframe.contentWindow.addEventListener("wheel", function(e){

           //IMPORTANT: you need to call a function outside this scope as this is actually being executed inside the iframe, not here. We are basically telling the iframe to fire a function "someFunction()" inside it's parent which is this window where we are.

            window.parent.someFunction(e) //someFunction() is further below
            
        })
    }
 
    
});

// this needs to exists otherwise you will get a script error
  function someFunction(e){
    //and we should get the wheel event re-broadcast here, thank you mister iFrame :0)

      console.log(e) // on my comp don't need to do anything with this, the gallery keeps scrolling on its own or you may need to just pass this event to your main scrolling code

  }

Now, this will most likely not work if the page that you are loading in the iFrame implements strict CORS (your video example is not fussy and seems to work fine)

Also, it may require some tweaking if the iFrame background is exposed it may not to react to the wheel but that may be solved by attaching another onwheel event to the iframe itself.

Hope that helps, if not, heck it's a bit of iFrame trivia.

Upvotes: 0

Hugo Fang
Hugo Fang

Reputation: 101

I think you can use setTimeout to implement this. I know this is not a good way. If you can update the iframe source website, no problem.

$(document).ready(function () {
        $(".scrollable-area").on("wheel", function (e) {
          var scrollLeft = $(this).scrollLeft()
          var width = $(this).get(0).scrollWidth - $(this).width()
          var deltaY = e.originalEvent.deltaY
          var deltaX = e.originalEvent.deltaX
          var newScrollLeft = scrollLeft + deltaY + deltaX
          if (
            (deltaY > 0 && newScrollLeft < width) ||
            (deltaY < 0 && newScrollLeft > 0)
          ) {
            e.preventDefault()
          }
          if (newScrollLeft <= 0) {
            $(this).scrollLeft(0)
          } else if (newScrollLeft >= width) {
            $(this).scrollLeft(width)
          } else {
            $(this).scrollLeft(newScrollLeft)
          }
        })

        let frameBack = document.querySelector(".background")
        frameBack.addEventListener("mousemove", (e) => {
          frameBack.classList.remove("z-1")
          frameBack.classList.add("-z-1")
          setTimeout(() => {
            frameBack.classList.remove("-z-1")
            frameBack.classList.add("z-1")
          }, 100)
        })
      })
* {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      .gallery-container {
        position: relative;
        width: 100%;
        height: 90vh;
        background-color: yellow;
        overflow: hidden;
      }

      .scrollable-area {
        width: 100%;
        height: 100%;
        overflow-x: auto;
      }

      .gallery-items {
        display: flex;
        min-width: 100%;
        height: 100%;
      }

      .gallery-item {
        flex: 0 0 auto;
        height: 100%;
        display: flex;
      }

      .gallery-item img {
        max-width: 100%;
        height: auto;
        object-fit: contain;
      }

      .gallery-item iframe {
        background-color: blue;
        width: auto;
        width: 800px;
      }

      .relative {
        position: relative;
      }

      .background {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
      }

      .z-1 {
        z-index: 1;
      }

      .-z-1 {
        z-index: -1;
      }
 <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>

    <div class="gallery-container inactive">
      <div class="scrollable-area">
        <div class="gallery-items">
          <div class="gallery-item">
            <img
              src="https://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast%2C_Dhaka%2C_Bangladesh.JPG"
            />
          </div>
          <div class="gallery-item">
            <img
              src="https://upload.wikimedia.org/wikipedia/commons/d/da/Sky_landscape.jpg"
            />
          </div>
          <div class="gallery-item relative">
            <iframe
              src="https://player.vimeo.com/video/584985260"
              frameborder="0"
              allow="fullscreen"
            ></iframe>
            <div class="background z-1"></div>
          </div>
          <div class="gallery-item">
            <img
              src="https://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast%2C_Dhaka%2C_Bangladesh.JPG"
            />
          </div>
        </div>
      </div>
    </div>

Upvotes: 0

herrstrietzel
herrstrietzel

Reputation: 17195

You might add an overlay to your scrolling div to disable iframe pointer events and re-enable pointer events on mouse movements.

Based on trincot's answer "How to detect when mousemove has stopped"
we could use a custom mousestop event.

  1. we disable iframe pointer events by appending an overlay.
  2. we're toggling/discarding the overlay according to mouse movements:
    if mouse moves were stopped (using a setTimeout delay) we add/activate the overlay to block pointer events within the iframe content – enabling wheel scroll events When moving over the slider wrap – we can access iframe/slide buttons.
  3. iframe dimensions are simply set via aspect-ratio e.g. 16/9

let galleryWrap = document.querySelector('.gallery-container');


(function(mouseStopDelay) {
  var timeout;
  document.addEventListener('mousemove', function(e) {
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      var event = new CustomEvent("mousestop", {
        detail: {
          clientX: e.clientX,
          clientY: e.clientY
        },
        bubbles: true,
        cancelable: true
      });
      e.target.dispatchEvent(event);
    }, mouseStopDelay);
  });
}(1000));

// Example use
galleryWrap.addEventListener('mousestop', function(e) {
  galleryWrap.classList.add('inactive')
});

galleryWrap.addEventListener('mousemove', function(e) {
  galleryWrap.classList.remove('inactive')
});

galleryWrap.addEventListener('click', function(e) {
  galleryWrap.classList.remove('inactive')
});


$(document).ready(function() {
  let galleryWrap = $('.gallery-container')
  let scrollArea = $('.scrollable-area')
  galleryWrap.on('wheel', function(e) {
    scrolling = true;
    var scrollLeft = scrollArea.scrollLeft();
    var width = scrollArea.get(0).scrollWidth - scrollArea.width();
    var deltaY = e.originalEvent.deltaY;
    var deltaX = e.originalEvent.deltaX;
    var newScrollLeft = scrollLeft + deltaY + deltaX;
    if ((deltaY > 0 && newScrollLeft < width) ||
      (deltaY < 0 && newScrollLeft > 0)) {
      e.preventDefault();
    }
    if (newScrollLeft <= 0) {
      scrollArea.scrollLeft(0);
    } else if (newScrollLeft >= width) {
      scrollArea.scrollLeft(width);
    } else {
      scrollArea.scrollLeft(newScrollLeft);
    }
  });
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.gallery-container {
  position: relative;
  width: 100%;
  height: 250px;
  background-color: yellow;
  overflow: hidden;
}

.scrollable-area {
  width: 100%;
  height: 100%;
  overflow-x: auto;
}

.gallery-items {
  display: flex;
  min-width: 100%;
  height: 100%;
}

.gallery-item {
  flex: 0 0 auto;
  height: 100%;
  display: flex;
}

.gallery-item img {
  max-width: 100%;
  height: auto;
  object-fit: contain;
}

.gallery-item iframe {
  width: 100%;
  aspect-ratio: 16/9;
  display: block;
}

.scrollable-area {
  position: relative;
}

.gallery-container.inactive:after {
  content: "";
  position: absolute;
  display: block;
  left: 0;
  top: 0;
  right: 0;
  width: 100%;
  height: 100%;
  background: red;
  opacity: 0.1;
  z-index: 100;
}
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>

<div class="gallery-container inactive">
  <div class="scrollable-area">
    <div class="gallery-items">

      <div class="gallery-item">
        <iframe frameborder="0" scrolling="no" srcdoc='<html><body style="margin:0; padding:0;"><video style="margin:0; padding:0; width:100%; aspect-ratio:16/9" controls muted playsinline controls preload="meta"><source src="https://cdn.jsdelivr.net/npm/[email protected]/video.mp4#t0" type="video/mp4"></video></html></body>'></iframe>
      </div>

      <div class="gallery-item">
        <img src="https://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast%2C_Dhaka%2C_Bangladesh.JPG">
      </div>
      <div class="gallery-item">
        <img src="https://upload.wikimedia.org/wikipedia/commons/d/da/Sky_landscape.jpg">
      </div>

      <div class="gallery-item">
        <img src="https://upload.wikimedia.org/wikipedia/commons/1/16/Appearance_of_sky_for_weather_forecast%2C_Dhaka%2C_Bangladesh.JPG">
      </div>
    </div>
  </div>
</div>

I've added a semitransparent reddish tint to illustrate the overlay toggling.

Upvotes: 2

Wakil Ahmed
Wakil Ahmed

Reputation: 1393

Modify your jQuery code to handle scrolling events for both images and iframes and you may also dynamically resize the iframes to fit their content and adjust the height of the horizontal gallery accordingly.

<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script>
  $(document).ready(function() {
    $('.scrollable-area').on('wheel', function(e) {
      var scrollLeft = $(this).scrollLeft();
      var width = $(this).get(0).scrollWidth - $(this).width();
      var deltaY = e.originalEvent.deltaY;
      var deltaX = e.originalEvent.deltaX;
      var newScrollLeft = scrollLeft + deltaY + deltaX;
      if ((deltaY > 0 && newScrollLeft < width) ||
        (deltaY < 0 && newScrollLeft > 0)) {
        e.preventDefault();
      }
      if (newScrollLeft <= 0) {
        $(this).scrollLeft(0);
      } else if (newScrollLeft >= width) {
        $(this).scrollLeft(width);
      } else {
        $(this).scrollLeft(newScrollLeft);
      }
    });

    // Resize iframes to fit their content
    function resizeIframes() {
      $('.gallery-item iframe').each(function() {
        this.height = this.contentWindow.document.body.scrollHeight + 'px';
      });
    }

    // Call the function on initial load and on window resize
    resizeIframes();
    $(window).resize(function() {
      resizeIframes();
    });
  });
</script>

Upvotes: 0

Related Questions