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

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/
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.

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:
Where as in Firefox:
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>

Here you go: https://jsfiddle.net/Lcox1ecc/423/
You just need to add -webkit-transform: translateZ(0); to the .on-top class.
Happy Coding!

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):
... but not at scale(1.228):
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.
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>

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/

I made this workaround by puttingz-index:-1; on video.
https://jsfiddle.net/Lcox1ecc/312/

I really like the answer from Salman A.
The only thing that comes to mind, would be rewriting with position: relative.
But I don't know if that is an option.

I stumbled across something similar to this last week with positioning absolute elements and transforms...
I dunno if this will help you out but here is a link.
CSS transform: translate moves postion:fixed inner Div
In the end I fixed it by using a transform: translateX(none) vs translateX(0).
Super strange behavior for sure, but the link gives some more links to help make things more clear - as in its behaving per spec.

It is happening because of overflow is hidden.Here is working link
https://jsfiddle.net/Lcox1ecc/322/
.content {
overflow:visible;
}

It might be late but just posting in case somebody finds it helpful.
Add an empty div under the parent container element with transform animation and nothing will disappear anymore. The animation does not do anything but it forces the browser to render all the elements using hardware acceleration.
<div class="emptydiv"></div>
.emptydiv{
transform:scale(1);
animation:fix 3s infinite;
}
#keyframes fix{
50%{
transform:scale(1);
}
}

Related

How to Swap Two Divs With Animation

I have a project where I want a div to appear as a large box and three more to appear underneath as smaller boxes and when you click a smaller box, it switches sizes and places with the large box using css transitions to make the movement and size change smooth. Right now I'm attempting to use jQuery and the positioning is not working at all. Here's an example of what I have so far:
https://jsfiddle.net/v3pmhawj/1/
$(function () {
let { left: x1, top: y1 } = $('.full-size-card').offset()
$('.inactive-sheets .card').on('click', function() {
let { left: x2, top: y2 } = $(this).offset()
let curr = $('.full-size-card')
let diffX = x2 - x1
let diffY = y2 - y1
$(this).css({
left: -diffX,
top: -diffY
})
$(this).addClass('full-size-card')
curr.css({
left: diffX,
top: diffY
})
curr.removeClass('full-size-card')
})
})
If anyone has suggestions on ways that involve other libraries or other techniques, I'm all ears. I'd like to be able to move the divs around in the DOM as well but as far as I can tell, you can't css-transition them if you do that since the only way (I know of) is to delete and re-add a copy of the element where you want it in the DOM.
You can create animation effect using transitions only. To achieve this you will have to define width and height of your containers as well as top and left position of bottom elements.
On click, you just have to exchange classes of element that will become small and of element that will become large.
Here is fiddle of an example:
https://jsfiddle.net/fkd3ybwx/210/
HTML
<div class="card-container">
<div class="card large">A</div>
<div class="card small">B</div>
<div class="card small">C</div>
<div class="card small">D</div>
</div>
CSS
.card-container {
position: relative;
}
.card {
transition: all ease 1s;
position: absolute;
font-size: 24px;
border: white 4px solid;
box-sizing: border-box;
cursor: pointer;
}
.small {
width: 100px;
height: 100px;
background: blue;
left: 0;
top: 300px;
}
.small ~ .small {
left: 100px;
background: green;
}
.small ~ .small ~ .small {
left: 200px;
background: yellow;
}
.large {
width: 300px;
height: 300px;
background: red;
left: 0px;
top: 0px;
}
JavaScript
const smallCards = document.querySelectorAll('.card');
smallCards.forEach((smallCard) => {
smallCard.addEventListener('click', (event) => {
const largeCard = document.querySelector('.large');
largeCard.className = "card small";
event.target.className = "card large";
});
});

How to shrink an iframe inside a div to match its width?

What I am trying to do should be simple. But somehow I am not able to find an answer to it.
Here is my codepen:
https://codepen.io/mvsimple/pen/wvgbvgQ
HTML:
<div class="parent">
<iframe class="child" src="https://reesgargi.com/"></iframe>
</div>
CSS
.parent {
position: relative;
overflow: hidden;
width: 100%;
padding-top: 62.5%;
}
.child {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
}
I want the iframe to fit inside the div (so that its width = parent div width)
But iframe loads the page zoomed in, from the center.
I have tried using CSS (Flexbox, Table display, and W3S trick
But I am helpless. I tried the iframe resizer library but it had its own issues. (Dragging)
Please advise my fellow programmers.
Finally found a solution!
All I had to was to scale down the iFrame and set its width equal to the original size (source of that iframe).
.child{
width: 1280px;
/* Magic */
transform-origin: 0 0;
transform: scale(0.703);
}

Question in the "input" type range, and JavaScript to design the video player

Is it possible to control in the period and change its color while updating the movement of the cursor specified in the picture
What is the name of that period in the CSS and how will the code in Java Script be in that case?
var video = document.querySelector('.video');
var btn = document.getElementById('play-pause');
function intializePlayer() {
btn = document.getElementById("play-pause");
seekslider = document.getElementById("seekslider");
// Add event listeners
seekslider.addEventListener("change", vidSeek, false);
video.addEventListener("timeupdate", seektimeupdate, false);
}
window.onload = intializePlayer;
function vidSeek() {
var seekto = video.duration * (seekslider.value / 100);
video.currentTime = seekto;
}
function seektimeupdate() {
var nt = video.currentTime * (100 / video.duration);
seekslider.value = nt;
if (video.ended) {
btn.className = "play";
}
}
#seekslider {
height: 10px;
top: 0;
left: 0;
width: 100%;
background: #000;
}
input[type='range'] {
-webkit-appearance: none !important;
background: #000;
border: #666 1px solid;
height: 4px;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none !important;
background: #FFF;
height: 15px;
width: 15px;
border-radius: 100%;
}
<input id="seekslider" type="range" min="0" max="100" value="0" step="1">
As mentioned in the comments only Internet Explorer allows you to natively design the elapsed and remaining parts of the slider, at least that's what I got from this well-written post about styling range sliders.
There are alternatives, though, that you might use. One idea I tried is to put the range slider into a DIV that holds elements to display the progress. The background element would be (kind of) the width of the slider while the foreground element (the "progress indicator") would expand according to the value of the slider. Please note that I did not change the appearance of the slider itself but if you want to do so, read the above article, it's worth the time.
Let's move on to the relevant code.
HTML code
<div class="slider-wrapper" style="width: 500px;">
<div class="slider-underlay"></div>
<input class="seekslider" type="range" min="30" max="60" value="0" step="1">
<div class="slider-progress"></div>
</div>
The slider sits in the middle of the background element that defines the background color and the foreground element that will indicate the progress.
CSS code
.slider-wrapper {
display: inline-block;
position: relative;
height: 30px;
width: 310px;
}
.slider-underlay {
background-color: black;
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
background: black;
border-right: 1px solid white;
z-index: 1;
}
.slider-progress {
position: absolute;
height: 100%;
width: 0;
left: 0;
top: 0;
background: #999;
border-right: 1px solid white;
z-index: 1;
}
.seekslider {
width: 98%;
z-index: 2;
position: absolute;
left: 0;
top: 3px;
}
JS code
$(".seekslider").on('change, input', function() {
const ratio = (this.value - this.min) / (this.max - this.min);
const counterRatio = (0.5 - ratio) * 1.2;
const counterWidth = counterRatio * 10 + 2;
const width= $(this).width();
$(this).next('.slider-progress').width(ratio * width + counterWidth);
});
Due to internal padding inside the slider element itself (the thumb slider will only be touching the left or right side of the countrol's outer width) we need to apply a bit of adjustment. When the slider is on the left, we need to make the progress indicator's width to be a little wider but as soon as we progress to the right, we need to reduce the extra padding, otherwise the progress would be exceeded the thumb slider on the right. The values above have been configured through experimenting and might need adjustment and there is a good chance that there are way better implementations available on the Net.
Codepen demo.
Aside from the wrapper all elements scale in percents so you may play around with the width of the slider.
Here are some demo images taken from the Codepen:
Firefox
Edge
Chrome
Please let me know about what you think and if this might be a solution for you. Thanks.

Detect When Mouse Enters Specific Area of Document (Not a Div Element)

I'm trying to figure out how Medium made their bottom action / menu bar slide up when your mouse enters the bottom of the document. The slide up effect is not triggered by moving the mouse over the invisible div (it slides up & down via transform translateY).
Besides, the menu bar is only 44px in height, but its is-visible class gets triggered way before your mouse is near it — but by what? When using Inspect Element, I can't see any hidden divs that could be triggering it..
I've searched for countless of ways, e.g. "show element when mouse enters specific part of document" but all search results involve when the mouse enters or moves over a div element, which is not the solution I'm looking for.
Obviously, you can solve this problem by putting the slide up menu inside a hidden container like I've done here, and then you get the desired result:
(function() {
var actionBar = document.querySelector('.action-bar');
var actionBarWrapper = document.querySelector('.action-bar-detection');
function showDiv() {
actionBar.classList.add('js-is-visible')
}
function hideDiv() {
actionBar.classList.remove('js-is-visible')
}
actionBarWrapper.onmouseover = showDiv;
actionBarWrapper.onmouseout = hideDiv;
})();
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.5;
}
body {
height: 100%;
}
.wrapper {
width: 90%;
max-width: 600px;
margin: 5% auto;
}
.action-bar {
position: absolute;
left: 0;
bottom: 0;
border: 1px solid #252321;
background: #fff;
padding: 16px;
width: 100%;
min-height: 50px;
opacity: 0;
transform: translateY(100%);
transition: all .5s;
z-index: 99;
}
.action-bar-detection {
height: 150px;
width: 100%;
opacity: 1;
position: fixed;
bottom: 0;
left: 0;
z-index: 1;
}
.js-is-visible {
opacity: 1;
transform: translateY(0%);
}
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<div class="wrapper">
<p>When mouse enters the hidden action bar element, slides up.</p>
<p>But it's only happening because the action-bar is inside an invisible detection layer class (action-bar-detection) with a height of 150px.</p>
</div>
<div class="action-bar-detection">
<div class="action-bar">
Bottom Menu
</div>
</div>
</body>
</html>
However, this doesn't seem to be what Medium have done, and if this can be done without adding more HTML & CSS, I want to learn how! :-)
I think I'm not phrasing the problem correctly, since I can't find any solutions even remotely close (I've searched A LOT).
Any advice? What should I read up on? :-)
Get height of viewport, track onmousemove, and compare clientY from the mouse event to the viewport height:
(function() {
var actionBar = document.querySelector('.action-bar');
var viewHeight = window.innerHeight - 150;
function toggleDiv(e) {
if (e.clientY >= viewHeight) {
actionBar.classList.add('js-is-visible');
} else {
actionBar.classList.remove('js-is-visible');
}
}
window.onmousemove = toggleDiv;
})();
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.5;
}
body {
height: 100%;
}
.wrapper {
width: 90%;
max-width: 600px;
margin: 5% auto;
}
.action-bar {
position: absolute;
left: 0;
bottom: 0;
border: 1px solid #252321;
background: #fff;
padding: 16px;
width: 100%;
min-height: 50px;
opacity: 0;
transform: translateY(100%);
transition: all .5s;
z-index: 99;
}
.action-bar-detection {
height: 150px;
width: 100%;
opacity: 1;
position: fixed;
bottom: 0;
left: 0;
z-index: 1;
}
.js-is-visible {
opacity: 1;
transform: translateY(0%);
}
<div class="wrapper">
<p>When mouse comes within 150px of the bottom part of the screen, the bar slides up.</p>
<p>When the mouse leaves this defined area of the screen, the bar slides down.</p>
</div>
<div class="action-bar-detection">
<div class="action-bar">
Bottom Menu
</div>
</div>
You could do this by listening to the mousemove event on the document, you will want to invest effort into making this performant as it will be triggered frequently. The most common way to regulate events like this is through throttling.
Once you are hooked into the mousemove event you will need to get the Y coordinate of the cursor and compare that to the height of the window, if it is within a threshold then you can reveal your panel, once it moves out you can proceed to hide it again.
Here is an example showing a basic implementation jsFiddle
// Using underscore for the throttle function though you can implement your own if you wish
document.addEventListener('mousemove', _.throttle(mouseMoveEventAction, 200));
function mouseMoveEventAction(e) {
doPanelStuff(isInsideThreshold(e.clientY));
}
function doPanelStuff(isActive) {
var panelElement = document.querySelector('.panel');
if (isActive) {
panelElement.style.background = 'red';
} else {
panelElement.style.removeProperty('background');
}
}
function isInsideThreshold(cursorY) {
var threshold = 200;
var clientHeight = document.documentElement.clientHeight;
return cursorY > (clientHeight - threshold);
}
html, body {
height: 100%;
}
.container, .content {
height: 100%;
width: 100%;
}
.panel {
height: 50px;
position: absolute;
bottom: 0;
width: 100%;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<div class="container">
<div class="content"></div>
<div class="panel"></div>
</div>

Position Fixed and Backface Visibility

I have three sectioning areas and within these I have a header element and a child item that is position fixed.
As the user scrolls I want the next section to go over the previous section including its fixed child.
I have this working in Chrome by using backface visibility:
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
backface-visibility: hidden;
But in FF, the fixed items are no longer fixed. Take a look at the my jsfiddle http://jsfiddle.net/7KjXm/5/
Is this expected behaviour? Is there a cross browser solution? Or is JS the way to go?
Thanks....
I managed to solve the effect you were looking for. Unfortunately, it does not seem possible to do with only css (yet).
Here is my solution that uses jquery and modified css of the original page. I switched to numbers instead of colored elements and changed the sizes.
My javascript for fake floating elements (allows for them to be hidden when the view moves away):
$(function(){
elem = $('.fixeditem');
win = $(window);
wrap = $('<div>').css({
width: elem.width(),
height: elem.height()
});
elem.wrap(wrap);
win.scroll(function() {
elem.each(function(index, element){
element = $(element);
var offset = element.parent().offset().top;
element.css('top', win.scrollTop() + 40 - offset);
});
});
});
My custom css for this specific example:
html, body{
height: 100%;
}
.item {
min-height:100%;
background-color: white;
position: relative;
z-index: 1;
overflow: hidden;
}
.header {
position: relative;
background-color: green;
padding: 5px;
z-index: 2;
}
.fixeditem {
position: relative;
top: 10%;
left: 50%;
z-index: 0;
}
Colored update of code: http://jsfiddle.net/8F2Zc/4/
Hope this helps!

Categories

Resources