I need to get a computed CSS position for an element, but when using auto instead of a number value I get inconsistent results across browsers.
For example, in the demo below when setting bottom: auto;
Chrome and Firefox reports auto, but Edge reports 0px
var el1 = document.querySelector('#one');
el1.innerText = getComputedStyle(el1).bottom;
var el2 = document.querySelector('#two');
el2.innerText = getComputedStyle(el2).bottom;
#one, #two {
position: sticky;
padding: 20px;
color: white;
}
#one {
bottom: 0px;
background: red;
}
#two {
bottom: auto;
background: blue;
}
<div id="one"></div>
<div id="two"></div>
Is there some other way to get the actual computed value of auto consistently across browsers?
In the Edge inspector you can see (screenshot below) the actual set value is auto but it shows the computed value as 0px
Screenshots
Chrome 74.0.3729.131
Edge 44.17763.1.0 | EdgeHTML 18.17763
There have been recent changes in this area.
Previously, getComputedStyle was returning the Computed Values of an element, now it should return its Resolved Values.
For the bottom property the rules to get this resolved value are:
A resolved value special case property like top
defined in another specification
If the property applies to a positioned element and the resolved value of the display property is not 'none' or 'contents', and the property is not over-constrained, then the resolved value is the used value. Otherwise the resolved value is the computed value.
It sounds like your browser treats your elements as a positioned element and thus uses the used value (0px) instead of the computed value (the keyword 'auto' or a computed <length-percentage> value).
I must admit I'm not quite clear as to why all the other browsers don't consider your sticky elements as positioned elements, I would have thought they were also, but they do agree that a relatively positioned element returns the resolve value '0px',
var el1 = document.querySelector('#one');
el1.innerText = getComputedStyle(el1).bottom;
var el2 = document.querySelector('#two');
el2.innerText = getComputedStyle(el2).bottom; // '0px' everywhere
#one, #two {
position: relative;
padding: 20px;
color: white;
}
#one {
bottom: 0px;
background: red;
}
#two {
bottom: auto;
background: blue;
}
<div id="one"></div>
<div id="two"></div>
While a non-positioned one returns the computed-value 'auto'.
var el1 = document.querySelector('#one');
el1.innerText = getComputedStyle(el1).bottom;
var el2 = document.querySelector('#two');
el2.innerText = getComputedStyle(el2).bottom; // 'auto' everywhere
#one, #two {
position: static;
padding: 20px;
color: white;
}
#one {
bottom: 0px;
background: red;
}
#two {
bottom: auto;
background: blue;
}
<div id="one"></div>
<div id="two"></div>
Unfortunately, I don't think there is a way to get consistent values across browsers that did implement these changes, the ones that didn't, and Edge, unless if you can avoid positioning your elements, then you should get 'auto' everywhere.
I've tried your code and reprodcued your issue on my side.
You could check from MDN and Can I Use that getComputedStyle() is normally supported by most modern browsers.
From my point of view, the different result of bottom value may be the browsers' self behaviour to show the bottom attribute.
Chrome read the bottom as auto but Edge read it as 0.
I've also test that if I set the width as auto, the result will be the same.
#two {
bottom: auto;
background: blue;
width:auto;
}
el2.innerText = getComputedStyle(el2).width;
Related
An intersection observer is set up on an element. When the element is scrolled past a certain point, the intersection observer handler is fired as expected. However, if a button is clicked to scroll the element past that same point, the handler is not fired.
Why is that? Is there a way to force the handler to be fired when using scrollTo/scrollIntoView?
const container = document.getElementById("container");
const hello = document.getElementById("hello");
const button = document.getElementById("button");
const options = {
rootMargin: "-100px 0px 0px 0px",
threshold: 1
}
const handleIntersect = entries => {
entries.forEach((entry) => {
console.log("handleIntersect")
});
};
const observer = new IntersectionObserver(handleIntersect, options);
observer.observe(hello);
button.addEventListener("click", () => {
container.scrollTo({
top: 120
});
})
body {
margin: 0;
}
#container {
background-color: #ddd;
height: 400px;
overflow-y: auto;
}
.inner-container {
height: 100px;
border-bottom: 1px solid #bbb;
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: right;
}
#button {
margin: 40px;
font-size: 20px;
}
#hello {
display: inline-block;
padding: 20px 40px;
background-color: blue;
color: white;
margin-top: 150px;
margin-bottom: 500px;
}
<div id="container">
<div class="inner-container">
<button id="button">Scroll</button>
</div>
<div id="hello">Hello</div>
</div>
remove rootMargin from options object and it will intersect, also you can decide percentage of visibility, if callback should be fired if even 50% is visible, you can provide inside options object
{ threshold: 0.5}
and so all...
I don't know if this solves your problem, But What I think is when we have scrollIntoView linked to a button we specify a value to the position of scrollbar if the button is clicked, for example when we click a button which has a scrollTo() function we expect the scrollbar to be at a specific place but that doesn't mean the scrollbar is sliding to the place which looks similar to the action that happens when we scroll the mouse.
In other words the intersection API fires an even when you cross a particular position or a point, however it does not fire an even if you just skip crossing the point and jump directly the desired position which happens when you use scrollIntoView,
In case you wonder that when you use scrollTo() to smooth scroll the webpage, you can visually see the scroll bar sliding to the particular point as if it passes the threshold point, however it is not the case, behind the scene the scrollbar just skip all the content and moves directly the specified position.
One way to counter the problem (not efficient) is to use looping, try looping from the current page offset value to your target value instead of hardcoding the value to the scrollIntoView() , it does gives you the desired output but the scrolling animation will be poor and it loses it's objective.
Question: Under certain circumstances .getComputedStyle() appears to return an incomplete/incorrect value for the height property of an element. Now I'm worried. Is it known to return unpredictable or incorrect values for other properties?
Backstory: I had been using .getComputedStyle() until I noticed that it was returning what appeared to be incorrect values for the heights of elements. So I tested it against several other methods.
In the simple test code below all of these returned 400 which is the correct height of the element that was tested.
.offsetHeight
.scrollHeight
.clientHeight
.getBoundingClientRect().height
However .getComputedStyle().height returned 300px which is the height of the element before its padding is applied.
'use strict';
window.addEventListener('load', measureDiv)
function measureDiv() {
console.log('offsetHeight = ' +document.querySelector('.container').offsetHeight);
console.log('scrollHeight = ' +document.querySelector('.container').scrollHeight);
console.log('clientHeight = ' +document.querySelector('.container').clientHeight);
console.log('getBoundingClientRect().height = ' +document.querySelector('.container').getBoundingClientRect().height);
console.log('getComputedStyle().height = ' +window.getComputedStyle(document.querySelector('.container')).height);
}
body {
margin: 0;
padding: 0;
}
.container {
margin: 120px;
padding: 50px;
background-color: green;
}
.box_1 {
padding: 20px;
background-color: blue;
}
.image_container {}
<div class="container">
<div class="box_1">
<div class="image_container"><img src="https://www.cis.rit.edu/~cnspci/courses/common/images/checkerboard-256x256.jpg"></div>
</div>
</div>
I have a plugin that creates and, on an interval, populates a <p> with the content of a <textarea>. The plugin positions the <p> underneath the <textarea>, and styles the elements so that their "boxes" are identical. Additionally, the background and text of the <textarea> are defined as transparent so that the content of the <p> can be seen.
Ideally, the elements and their contents will mirror one another at all times. And in most cases, they do. However, when both elements are made to be scrollable, the dynamic breaks; this is due to a difference in the scrollHeight of the two elements (the scrollHeight of <textArea> is larger than that of the <p>)
Here is the code:
var $shadowParagraphObj = $("#shadowParagraph");
var $contentTextAreaObj = $("#contentTextArea").scroll(scrollShadowParagraph);
function scrollShadowParagraph(event)
{
var textAreaScrollLeft = $contentTextAreaObj.scrollLeft();
var textAreaScrollTop = $contentTextAreaObj.scrollTop();
if($shadowParagraphObj.scrollLeft() != textAreaScrollLeft)
$shadowParagraphObj.scrollLeft(textAreaScrollLeft)
if($shadowParagraphObj.scrollTop() != textAreaScrollTop)
$shadowParagraphObj.scrollTop(textAreaScrollTop)
}
var intervalId = setInterval(function(){$shadowParagraphObj.html($contentTextAreaObj.val())}, 100);
#containerDiv {
position: relative;
left: 50%;
margin-left: -250px;
width: 510px;
height: 200px;
}
#shadowParagraph, #contentTextArea {
width: inherit;
height: inherit;
overflow: scroll !important;
padding: 4px;
border : none;
outline: none;
margin: 0px;
white-space: pre-wrap;
word-wrap: pre-wrap;
font: 1em Arial, sans-serif;
}
#shadowParagraph {
position: absolute;
z-index: 0;
background: white;
color: blue;
}
#contentTextArea {
position: relative;
z-index: 1;
background: transparent;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='containerDiv'>
<p id='shadowParagraph'></p>
<textarea id='contentTextArea'></textarea>
</div>
Overflowing the <textarea> should produce the issue (the text of the <textarea> has been given color to make the issue easy to see).
Have I forgot to declare some properties that are causing this discrepancy between scrollHeight values? If so, what are they and how should I declare them? If not, is there any way to ensure that the scrollHeight of the two elements is equal at all times?
Okay so using .replace(/\n\r?g, '<br />') to convert updated values, your line breaks will be converted into html line breaks. Additionally, html tends to ignore lone <br /> line breaks, so you will want to add an additional <br /> to the value to ensure the last line break is rendered.
Put together this would look something like:
var textAreaHTML = $myTextArea.val().replace(/\n\r?g, '<br />')+'<br />';
Additionally, I would recommend updating your textarea values AND scroll position on the .keyup() event, .keypress() event, or both events using .on('keyup keypress', function() {...}).
To see this in action check out this jsFiddle example
I'm trying to scroll an hidden element before I show it. This is the code i'm working with:
<div class="main">
<div class="bg">
</div>
</div>
.main {
display:none;
position:abolsute;
width:250px;height:250px;
overflow:scroll;
}
.bg {
background: blue url(http://defaulttester.com/img/bg-landing-mario.jpg);
width:1200px;
height:800px;
}
$(".main").scrollTop($(".bg").height()/2);
$(".main").scrollLeft($(".bg").width()/2);
IT works fine if its showing but if its display:hidden it will simple not work. Is there anyway to avoid this and make it work?
JSFiddle: http://jsfiddle.net/dpjzJ/
Use visibility: hidden; or some class like this instead:
.hide {
position: absolute !important;
top: -9999px !important;
left: -9999px !important;
}
Or this (from Boilerplate):
.visuallyhidden {
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px; width: 1px;
margin: -1px; padding: 0; border: 0;
}
Something with display: none; has no location on the page, as you probably knew.
Check out this article on the subject.
If your goal is to just set scrollLeft and scrollTop to 0(since that was my goal), one very hacky solution is to follow these steps:
Get a reference to the element you want to reset.
Get a reference to the element's parent.
Remove the element from the parent.
Append the element back to the parent.
scrollTop and scrollLeft will now be set back to 0 even though they are invisible.
function resetScroll(element) {
element.parentElement.replaceChild(element,element)
}
This will set both scrollTop and scrollLeft to 0 even if display none.
Example use:
resetScroll(document.getElementById("my_scroll"))
It's not ideal, but the way I solved this is to add a one-time IntersectionObserver that triggers the first time the element becomes visible. Here's a function to add a callback for when an element first becomes visible:
function onVisible(element, callback) {
new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if(entry.intersectionRatio > 0) {
callback(element);
observer.disconnect();
}
});
}).observe(element);
}
And use it like this:
let myElement = document.querySelector("#myElement");
// When the element first becomes visible, scroll it to 500px:
onVisible(myElement, el=>el.scrollTop=500);
Example: https://jsbin.com/gigumewemo/edit?html,output
I'm defining a javascript "object" via the following function:
function Window(vars) {
this.div = $("<div/>", {
id: vars.id,
class: vars.styles + " box text",
css: {
top: vars.top,
left: vars.left
}
});
this.div.appendTo( $("body") );
// more stuff happens..
As you can see, the Window has a div property, which is a jQuery object. In it's instantiation, I declare the CSS classes box and text. text is not important, it's just font stuff. Here's the CSS for box, however.
.box {
z-index: 1;
position: absolute;
background: #222222;
min-width: 10%;
text-align: center;
padding: 5px;
}
.nav-extension {
z-index: 3;
padding: 8px;
background: #000000;
position: absolute;
-moz-border-radius-bottomright: 5px;
-moz-border-radius-topright: 5px;
border-bottom-right-radius: 5px;
border-top-right-radius: 5px;
}
box is absolute at z-index 1, and another div with nav-extension is somewhere else on the page, also absolute and at z-index 3. However, when I add the Window object to my page, it appears above anything with nav-extension. All other CSS attribues, like background, still work.
I've tried altering the z-index where I instantiate the div in the "css" section I'm already using, but that didn't work either. What gives?
Edit
Also, I've inspected both the div with box and the one with nav-extension with Firefox, and the "Style" tab indicates they still have their intended z-index (not overridden).
#2: Changed vars.class to vars.styles.
Could you give us some DOM, please? It appears that that your box and the nav-extension-div are in different contexts. A non-static position sets up a new context, relatively to which all z-indexes inside are processed. A simple fiddle to demonstrate: http://jsfiddle.net/3KTyz/
<body>
<style>
.box { z-index:1; }
.nav-extension { z-index:3; }
</style>
...
<div id="context" style="position:relative"><!-- or absolute or fixed -->
...
<div class="nav-extension"><!--
will be positioned +3 relatively to other elements in #context
-->...</div>
</div>
<div class="box"><!--
will be above #context, which has (implicit) z-index:0
-->...</div>
</body>
To make nav-extension appear above the box, you will either
set #context (or one of its parents) to a z-index higher than the one of box or
move the nav-extension-div outside any context
class is a reserved word in JavaScript and cannot be used as a property or a variable name, maybe this is the culprit.