Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 254
iafiawik Avatar asked Apr 04 '18 14:04

iafiawik


4 Answers

It looks like a bug in Chrome. Notice that when you scale the image, the element inspector keeps telling you that the size of #scaled is 1024x768:

Chrome Scale

Where as in Firefox:

Firefox Scale

Now, apparently, Chrome uses the wrong size to conclude that .on-top is completely outside .content and hides it because of hidden overflow (it should not be doing this but apparently it is trying to optimize away any element that displays above a video). Examples:

Scale: 1.225
Parent width: 1254.40
Child left: 1254.40 - (100 + 90) * 1.225 = 1021.65
Result: less than 1024 (partially inside)

Scale: 1.230
Parent width: 1259.52
Child left: 1259.52 - (100 + 90) * 1.230 = 1025.82
Result: greater than 1024 (completely outside)

Unfortunately I could not find an elegant solution. Ideally you should revise your HTML markup and CSS, perhaps align the top element with left edge. As a last resort, you can move the elements more towards left using transparent border:

var goalScale = 140;
var startScale = 100;
var currentScale = 100;
var shouldScaleUp = true;
var container = document.getElementById("scaled");
var scaleInfo = document.getElementById("scale-info");

function step() {
  container.style.transform = "scale(" + (currentScale / 100) + ")";
  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);
.scale-info {
  position: fixed;
  left: 0;
  top: 0;
}

#scaled {
  background: #cccccc;
  width: 1024px;
  height: 768px;
  position: fixed;
  left: 200px;
  top: 200px;
}

.content {
  height: 100%;
  overflow: hidden;
  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: 100px;
  top: 150px;
  background: pink;
  z-index: 5;
  padding: 20px;
  /* a 200px border moves the element towards left */
  border-left: 200px solid transparent;
  background-clip: padding-box;
}

.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="scale-info"></div>

<div id="scaled">
  <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>
like image 174
Salman A Avatar answered Oct 17 '22 15:10

Salman A


Here you go: https://jsfiddle.net/Lcox1ecc/423/

You just need to add -webkit-transform: translateZ(0); to the .on-top class.

Happy Coding!

like image 30
jmcgriz Avatar answered Oct 17 '22 16:10

jmcgriz


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>
like image 4
iafiawik Avatar answered Oct 17 '22 15:10

iafiawik


One approach, if you can modify a bit your html, is wrap your problematic elements in a container that is the same size as the video and container, with the proper z-index. That way you would have clear layers of the same size and positions, into which you can position more complex elements. Like this for example:

   <div id="top-container">
     <div class="on-top">
      <h1>
        I should always be on top.<br /> I have z-index 5
      </h1>
    </div>
   </div>

  #top-container {
    width: 100%;
    height: 100%;
    position: absolute;
    z-index: 5;
  }

https://jsfiddle.net/06oykj8o/4/

like image 2
Julien Grégoire Avatar answered Oct 17 '22 14:10

Julien Grégoire