Search code examples
javascriptcssgoogle-chromez-indexscaletransform

Video and z-index inside scaled element: some divs disappear


I have a somewhat strange behaviour in Chrome and Safari. I have a scaled (transform: scale()) container with a video and other elements inside of it. At some scalings the absolute positioned elements with a high z-index disappears and does not come back again.

How can I fix this? Note that I cannot give the video element a negative z-index and I need to use overflow: hidden;.

Example

I have made an example that scales the outermost container up and down. At a specifik scale value the element with class .on-top (and text "I should always be on top.") disappears. When scaling down again it suddenly appears.

Link to exmaple: https://jsfiddle.net/iafiawik/Lcox1ecc/

enter image description here

Conclusions

  • It seems like the size of the element matters. The larger I make it, the larger is the scale value before it disappears.
  • I have also tested to set transform: scale(1.4) with CSS directly on the element and the behaviour is the same.

The issue does not exist if I:

  • Replace the video tag with a div
  • Remove position: absolute; from siblings to .on-top (that is, .below)
  • Remove overflow: hidden; from .content
  • If I move .on-top so it is placed after the video tag in the document flow

(But of course none of these workarounds work for me in reality because of project specific reasons. I also cannot give the video element a negative z-index and I need to use overflow: hidden;.)

Suggested workarounds from the community (thanks!)

  • Give the video tag a negative z-index (can't do this because I sometimes have elements placed behind the video)
  • Remove overflow: hidden; (I can't remove overflow: hidden;)

Browsers

I have seen this issue in Chrome (Mac) and Safari (Mac).


Update 1

Seems like this bug report pretty much covers my problem. However, it does not provide a fix for it.

Update 2

I've answered my own question by providing my solution to this problem.

Update 3

There are a lot of answers coming in that either modify the z-index of the video or adds translateZ to the .on-top element. Demos have shown that both of those approaches do fix the issue. However, since my HTML structure is the output from a visual HTML editor (long story ...), I do not know what elements will be there or if they should be in front, below or next to a video. Therefore I am looking for a solution that does not require changes to individual elements that are inside the scaled element.


Solution

  • After spending a lot of time researching this problem and trying a lot of different approaches I've come to the conclusion that no solution fixes my problem. There are solutions that fix the problem if you are able to control the z-indexes of the elements that disappear, but I am unable to do so since the structure of the HTML is not known to be (it is the output of the HTML editor). I was looking for a solution that would not require changes to individual children to the scaled parent, but I have not found any so far.

    This bug report pretty much covers my problem but it does not provide a fix for it.

    I can confirm that this happens because the element is outside of the scaled containers original width and height:

    The element is visible at scale(1.227) (red border indicates the original size of #scaled):

    enter image description here

    ... but not at scale(1.228):

    enter image description here

    My solution is therefore to add another wrapping element outside the scaled element that is not scaled, but get its width and height properties updated according to its first child scale values. This element has overflow: hidden; and prevents elements from being visible.

    enter image description here

    This is not a perfect solution as one might experience a small gap between the scaled element and the outermost wrapping element (rounding issues), but it is the best I can do given the circumstances.

    var goalScale = 140;
    var startScale = 100;
    var currentScale = 100;
    var shouldScaleUp = true;
    var container = document.getElementById("scaled");
    var scaledContainer = document.getElementById("resized-container");
    var scaleInfo = document.getElementById("scale-info");
    
    function step() {
      var contentWidth = 1024;
      var contentHeight = 768;
      container.style.transform = "scale(" + (currentScale / 100) + ")";
    
      scaledContainer.style.width = contentWidth * ((currentScale / 100)) + "px";
      scaledContainer.style.height = contentHeight * ((currentScale / 100)) + "px";
    
      scaleInfo.innerText = "Scale: " + (currentScale / 100);
    
      if (currentScale === goalScale) {
        shouldScaleUp = false;
      }
      if (currentScale === startScale) {
        shouldScaleUp = true;
      }
    
      if (shouldScaleUp) {
        currentScale += 0.5;
      } else {
        currentScale -= 0.5;
      }
    
      window.requestAnimationFrame(step);
    }
    
    window.requestAnimationFrame(step);
    #resized-container {
      position: fixed;
      width: 1024px;
      height: 768px;
      overflow: hidden;
      border: 10px solid red;
      top: 200px;
      left: 200px;
    }
    
    #scaled {
      background: #cccccc;
      width: 1024px;
      height: 768px;
      position: absolute;
      transform-origin: left top;
    }
    
    .content {
      height: 100%;
      position: relative;
      margin-left: auto;
      margin-right: auto;
      background: rgba(34, 34, 56, 0.2);
    }
    
    .below {
      position: absolute;
      width: 300px;
      height: 300px;
      right: 0px;
      top: 100px;
      background: purple;
      z-index: 1;
      opacity: 0.8;
    }
    
    .below-2 {
      z-index: 3;
      right: 100px;
    }
    
    .below-3 {
      z-index: 4;
      right: 400px;
    }
    
    .on-top {
      position: absolute;
      width: 50px;
      right: -30px;
      top: 150px;
      background: pink;
      z-index: 5;
      padding: 20px;
    }
    
    .on-top h1 {
      font-size: 20px;
    }
    
    #video {
      position: absolute;
      z-index: 4;
      width: 1024px;
      height: 768px;
      background: rgba(0, 0, 0, 0.4);
    }
    <div id="resized-container">
      <div id="scaled">
        <div id="scale-info">
    
        </div>
        <div class="content">
          <h2 class="below below-1">
            I have z-index 1
          </h2>
    
          <div class="on-top">
            <h1>
              I should always be on top.<br /> I have z-index 5
            </h1>
          </div>
    
          <h2 class="below below-2">
            I have z-index 3
          </h2>
          <video id="video" src="https://www.w3schools.com/html/mov_bbb.mp4"></video>
          <h2 class="below below-3">
            I have z-index 4
          </h2>
        </div>
      </div>
    </div>