Search code examples
javascriptjquerycssfirefoxfullscreen

Prevent Firefox from moving after `.exitFullscreen()`


Firefox is exhibiting this behavior (bug?) that occurs after exiting a full screened <img> where the user ends up at the element that sits above the <img> the user had just viewed in fullscreen. In short my question is:

How can I prevent Firefox from scrolling up after exiting fullscreen mode?

The MCVE posted as a Snippet doesn't function due to SO's strict security measures so I have provided a plunker. All the details are commented in the Snippet and Plunker. In addition I have added a simple interface to not only reproduce the issue but to change the layout to test different combinations as well. Thank you for your valuable time.

SNIPPET (doesn't function--review this plunker instead)

/* Several attempts to use the .focus() method
|| and focus events did not work for me, neither
|| has tabindex and anchors. If it appears that
|| my implementation is wrong (a strong possibility)
|| please inform me.
*/
$('a').click(function(e) {
  var tgt = $(this).prev();
  fs(tgt[0]);
  $(this).focus();
});
/* This function is for the MCVE 
|| It enables the ~select~ to remove and re-insert
|| the test elements. By doing so, we can see
|| how the test elements behave in different
|| combinations. What I found out about FF is
|| that when exiting a full screened ~img~ that's
|| positioned last is that it will lose focus 
|| and the viewport is scrolled up to the element 
|| above it.
*/
$('#sel1').on('change', function(e) {
  var V = $(this).val();
  var first = $('#' + V).find(':first').attr('id');
  if ($('#' + V).hasClass('media')) {
    $('#' + V).fadeOut('#' + first);
  } else {
    $('#' + V).fadeIn('#' + first);
  }
  $('#' + V).toggleClass('media');
});

/* These 2 functions are responsible for 
|| full screen. Please inform me if there's a 
|| better way, or if anything is outdated. I
|| have researched the Fullscreen API and I
|| haven't found any updates of any use. I've
|| used these functions for the last 3 years
|| so maybe I might've missed something 
|| critical.
*/ // There's no ms prefixes because I'm not concerned about IE.

var isFullScreen = function() {
  return !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
}

function fs(target) {

  if (!isFullScreen()) {
    if (target.requestFullscreen) {
      target.requestFullscreen();
    } else if (target.webkitRequestFullscreen) {
      target.webkitRequestFullscreen();
    } else if (target.mozRequestFullScreen) {
      target.mozRequestFullScreen();
    }
  } else {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    }
  }
}
/* These styles are here for the demo itself 
|| and are not a cause of the problem at hand
*/

* {
  margin: 0;
  padding: 0
}

body {
  font: 400 16px/1.3 Consolas;
  height: 100%;
  width: 100%;
  background: #333;
  color: #fed
}

a {
  margin: 0 auto 50px;
  display: block;
  width: 48px;
  height: 48px;
  text-align: center;
  cursor: pointer;
  background-size: contain;
}

.vid,
.img,
.gif,
.svg {
  display: block;
  margin: 20px auto;
}

.expand {
  background: url(http://imgh.us/expand_2.svg)no-repeat;
}

header {
  padding: 15px 10px;
  margin: 15px auto;
}

fieldset {
  border: 10px solid tomato;
  width: 20ch
}

legend {
  font-size: 1.2em;
}

dt {
  text-decoration: underline;
  font: 1.1em;
}

dd {
  margin-left: 20px
}

.note,
dt {
  color: #ffcc33
}

.demo {
  width: 450px;
  padding: 10px;
  counter-reset: step;
}

.demo li::before {
  counter-increment: step;
  content: "» " counter(step) ". ";
  text-indent: -150px;
  margin-left: 30px;
  color: cyan;
}

.fs:-webkit-full-screen {
  max-width: 100%;
  height: auto;
}
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
  <title>Prevent Firefox from Moving After .exitFullscreen()</title>
  <style>

  </style>
</head>

<body>
  <header>
    <dl>
      <dt>Objective</dt>
      <dd>Prevent Firefox from Moving After .exitFullscreen()</dd>
      <dt>Behavior</dt>
      <dd><b class='note'>Expected: </b>When exiting fullscreen mode, we should be at the same position that we were at before</dd>
      <dd><b class='note'>Experienced: </b>In FF, when exiting fullscreen mode, we are scrolled up as if the element above has a higher tab priority or more than likely is that tab index and focus are being ignored by FF.</dd>
      <dt>Question</dt>
      <dd><b><mark>How can I prevent Firefox from scrolling up after exiting fullscreen mode?</mark></b></dd>
    </dl>
  </header>
  <section>
    <ol class='demo'>
      <li>To reproduce issue, use the &lt;select&gt; to remove items C, D, E, and F.</li>
      <li>Next, fullscreen item B by clicking the icon below it.</li>
      <li>Then exit full screen mode by hitting <kbd>ESC</kbd>.</li>
      <li>Notice we have jumped up the page.</li>
    </ol>
  </section>
  <!--This ~select~ is for the MCVE - details are
commented below in the ~script~ block-->

  <section>
    <fieldset>
      <legend>Remove and Re-insert Elements</legend>
      <select id='sel1'>
         <option value="">----</option>
         <option value='A'>&lt;video&gt; src=MP4</option>
         <option value='B'>&lt;img&gt; src=PNG</option>
         <option value='C'>&lt;video&gt; poster=GIF</option>
         <option value='D'>&lt;img&gt; src=SVG</option>
         <option value='E'>&lt;div&gt; &amp;nbsp;</option>
         <option value='F'>&lt;iframe&gt; srcdoc="&lt;DIV&gt;&lt;div&gt;"</option>
      </select>
    </fieldset>

    <!--I tried using the ~a~nchors, -id-, -name-, and -tabindex- 
FF was ignoring my attempts to keep or get focus. Using named or 
id ~a~nchors failed since the distance between desired spot
and the spot FF ends up at is short.-->
    <div id='A' class='media'>
      <video id="vid1" class="vid fs" src="http://html5demos.com/assets/dizzy.mp4" controls></video>
      <a href='#/' class='expand' tab-index='1'></a>
    </div>
    <div id='B' class='media'>
      <img id='img1' class='img fs' src='http://imgh.us/Lenna.png'>
      <a href='#/' class='expand' tab-index='1'></a>
    </div>
    <div id='C' class='media'>
      <video id='gif1' class='gif fs' poster='http://imgh.us/gir_zim.gif' width='300' height='300'></video>
      <a href='#/' class='expand' tab-index='1'></a>
    </div>
    <div id='D' class='media'>
      <img id='svg1' class='svg fs' src='http://www.clker.com/cliparts/j/g/8/S/V/O/test.svg' width='auto' height='500'>
      <a href='#/' class='expand' tab-index='1'></a>
    </div>

    <!--Subjects E and F were added to see if a "dummy"
element were to be the last element so that FF
would exit fullscreen on the last ~img~ correctly.
I got mixed results.-->
    <div id='E' class='media'>
      <div id='div1' class='fs'>&nbsp;</div>
      <a href='#/' class='expand' tab-index='1' height='1' width='1'></a>
    </div>
    <div id='F' class='media'>
      <iframe id='ifm1' class='fs' srcdoc="<div style='color:lime'>iframe srcdoc</div><div style='color:cyan'>2 divs</div>" allowfullscreen></iframe>
      <a href='#/' class='expand' tab-index='1' height='1' width='1'></a>
    </div>
    <footer class='bottom'> </footer>
  </section>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
  <script>
  </script>
</body>

</html>


Solution

  • This answer is fully credited to @Kaido and I'll readily replace this answer if and when Kaido posts an answer.

    My attempts at using the scroll methods didn't work is because I was listening to click events when I should've been listening for the onmozfullscreenchange

    Plunker

    Demo

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
    
      <title>Prevent Firefox from Moving After .exitFullscreen()</title>
      <style>
        button {
          display: block;
          padding: 0;
          width: 32px;
          height: 32px;
          text-align: center;
          cursor: pointer;
          background-size: contain;
        }
        
        .expand {
          background: url(http://imgh.us/expand_2.svg)no-repeat;
        }
      </style>
    </head>
    
    <body>
    
      <div id='A' class='media'>A
        <video id="vid1" class="vid fs" src="http://html5demos.com/assets/dizzy.mp4" controls></video>
    
      </div>
      <div id='B' class='media'>B
        <img id='img1' class='img fs' src='http://imgh.us/Lenna.png'>
        <button class='expand'></button>
      </div>
    
    
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
      <script>
        $('button').click(function(e) {
          e.preventDefault();
          var tgt = $(this).prev();
          fs(tgt[0]);
    
        });
        /* This function is for the MCVE 
        || It enables the ~select~ to remove and re-insert
        || the test elements. By doing so, we can see
        || how the test elements behave in different
        || combinations. What I found out about FF is
        || that when exiting a full screened ~img~ that's
        || positioned last is that it will lose focus 
        || and the viewport is scrolled up to the element 
        || above it.
        */
    
        $('#sel1').on('change', function(e) {
          var V = $(this).val();
          var first = $('#' + V).find(':first').attr('id');
          if ($('#' + V).hasClass('media')) {
            $('#' + V).fadeOut('#' + first);
          } else {
            $('#' + V).fadeIn('#' + first);
          }
          $('#' + V).toggleClass('media');
        });
    
        /* These 2 functions are responsible for 
        || full screen. 
        */ // There's no ms prefixes because I'm not concerned about IE.
    
        var isFullScreen = function() {
          return !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement);
        }
        // SOLUTION XXXXXXXXXX]START[XXXXXXXXXXXXXXX
        var yOffset;
        document.onmozfullscreenchange = function() {
          if (!isFullScreen()) {
            window.scrollTo(0, yOffset);
          }
    
        };
        // SOLUTION XXXXXXXXXXX]END[XXXXXXXXXXXXXXXX
        function fs(target) {
    
          if (!isFullScreen()) {
            yOffset = pageYOffset;
            if (target.requestFullscreen) {
              target.requestFullscreen();
            } else if (target.webkitRequestFullscreen) {
              target.webkitRequestFullscreen();
            } else if (target.mozRequestFullScreen) {
              target.mozRequestFullScreen();
            }
          } else {
            if (document.exitFullscreen) {
              document.exitFullscreen();
            } else if (document.webkitExitFullscreen) {
              document.webkitExitFullscreen();
            } else if (document.mozCancelFullScreen) {
              document.mozCancelFullScreen();
            }
    
          }
        }
      </script>
    </body>
    
    </html>