3

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?

Anna_B
  • 820
  • 1
  • 4
  • 23
  • Is this what you're asking? https://stackoverflow.com/questions/10082155/remove-scrollbar-from-iframe – sleepwalk Jul 23 '23 at 01:13
  • No, it doesn't change anything. – Anna_B Jul 23 '23 at 04:11
  • 1
    what a funny question – Furious Mountain Aug 01 '23 at 07:29
  • 1
    Hello potatooo :) – Anna_B Aug 02 '23 at 21:32
  • It's kind of twisted problem, since we don't have the power to control the events on iframe that is cross-origin. If there would be a concrete video provider used, we could overwrite the presentation layer of the video with custom solution to play, mute, etc. In terms of vimeo, this package would be useful: https://github.com/vimeo/player.js. The other solution would be to create additional layer, covering the play bar, which would listen to pointer-events only in this place. However the position of this layer of course could vary depending on video provider – Adam Zabrocki Aug 07 '23 at 11:13

4 Answers4

2

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/big-buck-bunny-1080p@0.0.6/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.

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
  • Interesting. It makes sense. I still feel it doesn't work as well as it should. If I start scrolling, then it doesn't directly work well with the video. But it works then after a while of scrolling seamless. Do you have an idea how to improve it so it always directly works? – Anna_B Jul 24 '23 at 00:05
0

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>
Wakil Ahmed
  • 1,053
  • 1
  • 8
  • 16
0

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>
Hugo Fang
  • 101
  • 5
  • Hm, the idea is interesting. But it doesn't work well then to use the buttons of the video. – Anna_B Aug 02 '23 at 21:36
  • I thought the user could move the mouse before clicking the buttons in video. If the user moves the mouse then he can click buttons. But 100ms after moving mouse user can't click the buttons cause there is a hidden layer above the iframe. To remove the hidden layer for 100ms the user should move mouse. There doesn't seem to be a good way unless you update the iframe source website. – Hugo Fang Aug 02 '23 at 21:49
0

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.