scrollTop & scrollLeft do not work on display:none elements - javascript

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

Related

Why intersection observer handler is not fired when using scrollTo or scrollIntoView?

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.

scrollTop (JS and jQuery version) won't scroll all the way to the bottom

I'm creating a chat app which when messages load (From Firebase), the div containing the messages scrolls to the bottom to display the most recent appended message div. scrollTop does somewhat work but it won't scroll all the way to the bottom, no matter what values I use for scrollTop. I've tried both the JS and the jQuery versions of scrollTop, but neither can get it to scroll to the bottom. Here's some of my code:
HTML
<div id="msgContainer">
<div id="msgFeed">
//Messages load here from a database
</div>
</div>
CSS
#msgContainer {
height: 165px;
overflow-x: hidden;
overflow-y: visible;
}
#msgFeed {
display: block;
background-color: white;
position: relative;
margin: 0;
overflow: hidden;
}
JS
function scrollToBottom (id) {
var div = document.getElementById(id);
div.scrollTop = div.scrollHeight - div.clientHeight;
}
or...
$('#scroll').scrollTop(1000000);
Doesn't seem to matter which version or what values I use, it just refuses to scroll that last approximately 5% of the way to the bottom. Any ideas what I might be doing wrong??
I dealt with a similar issue. I was receiving a value from a web socket to put into a chat box. Whenever I used scrollTop/Height, it always scrolled to the NEXT to last message (off just a bit). Even if I put in the max or a very high value, it would not scroll all the way.
This occurs b/c the dimensions of the container (with the added item) are not yet what we expect. A simple timeout will solve this problem:
setTimeout(() => {
el.scrollTop = el.scrollHeight;
}, 500);
If you're using Vue.js (probably something analagous in other reactive frameworks), you can also do the following ('this' is the Vue instance):
this.$nextTick(
() => (this.$refs.chat.scrollTop = this.$refs.chat.scrollHeight)
);
'nextTick' seems optimal, but not everyone will be using Vue. Hope this all helps someone solve this simple yet not so evident problem.
EDIT: nextTick doesn't always seem to work. setTimeout should always work.
I don't know what element you were referring to with #scroll since I don't see it in your html, but try this and let me know if it still falls ~5% short.
$(document).ready(function(){
function scrollToBottom (id) {
var div = document.getElementById(id);
/*TRY*/
div.scrollTop = div.scrollHeight - div.clientHeight;
/*OR*/
$('#'+id).scrollTop(div.scrollHeight - div.clientHeight);
}
scrollToBottom('msgContainer');
});
#msgContainer {
height: 165px;
overflow-x: hidden;
overflow-y: visible;
border: 3px solid red
}
#msgFeed {
display: block;
background-color: white;
position: relative;
margin: 0;
overflow: hidden;
border: 1px solid blue
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="msgContainer">
<div id="msgFeed">
<br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br><br><br><br>
//Messages load here from a database
</div>
</div>

Sticky element inside a div with absolute position on scroll

I have a div with position: absolute and overflow: auto. Inside this div I have a div that should act sticky and should be fixed(top: 0, bottom: 0, overflow: auto) when I scroll.
I can fix this div, but I can't return it to original position because I can't attached the scroll event when this div is fixed.
$('.right').scroll(function() {
if ($('.scroll').offset().top <= 0) {
$('.scroll').css({
'position': 'fixed',
'top': 0,
'left': '20px',
'right': '0',
'overflow': 'auto'
})
}
})
Please check my JSFiddle for more info - JSFIDDLE
Thank you.
Here's how I would do it. This doesn't position it fixed but it has the same appearance. Once scrollTop is equal to or greater than where the top of the fixed content "should be" then we set the top absolutely to that of scrollTop, if you scroll upwards once the scrollTop reaches the point where the fixed content used to be, it will drop it again.
$(document).ready(function() {
oldOffset = $('.scroll').offset().top;
$('.right').scroll(function() {
if ($('.right').scrollTop() > oldOffset) {
$('.scroll').css({
'position': 'absolute',
'top': $('.right').scrollTop(),
'left': '20px',
'right': '0',
'overflow': 'auto'
});
}
});
});
(Demo)
Set the outside div to
position: relative;
Set the inside div to
position: absolute;
top: 15px;
right: 15px;
This will put the top right corner of the inside div at the designated location within the parent container. When setting position absolute, the image is set relative to the first parent container with position defined to anything other than default, I believe. If there is no DOM element assigned a position, the absolute element will be positioned relative to the viewport.
It is very strange task you want to accomplish :)
But anyway there is the problem:
When you set you inner div to position: fixed you positioned this div above your div.right and it is prevents scrolling event from fire.
So what you need is to set pointer-events: none to the div.scroll to allow your div.right listen scroll events without any problems.
But when you do that you will face another problem - when you set your div.scroll to position: fixed it will lose its place inside the div.right and div.right jumps to the top of the scroll automatically. To prevent that you need to create clone of the div.scroll and set his height to 0 initially, and to auto when your inner element is fixed.
Note pointer-events: none - disable all mouse events including the text selection.
There is the code:
JS
$(document).ready(function() {
var cnt = $('.right');
var scrollEl = $('.scroll');
var scrollClone = scrollEl.clone().addClass('clone');
scrollEl.before(scrollClone);
cnt.scroll(function() {
var expression = scrollClone.offset().top <= 0;
scrollEl.toggleClass('stick', expression);
scrollClone.toggleClass('stick-clone', expression);
})
})
CSS
.scroll {
background: yellow;
pointer-events: none;
overflow: hidden; /* Remove top offset from h1*/
}
.scroll.stick {
position: fixed;
left: 20px;
right: 0;
top: 0;
}
.scroll.clone {
height: 0;
overflow: hidden;
}
.scroll.clone.stick-clone {
height: auto;
}
JSFiddle
You can try the following example:
Firstly, instead of adding the css as inline styles, create a css class that you can add and remove from the .scroll element.
CSS
.fixed-top {
position:fixed;
top:0;
left:20px;
right:20px;
}
Wrap your .scroll element with another div which will be used in the javascript to keep track of the original height of your .scroll div.
HTML
<div class="scroll-wrapper">
<div class="scroll"></div>
</div>
Lastly, store the scrollTop value in a variable when the fixed position is applied for the first time. You can then use that value to determine when to remove the fixed styles from the .scroll div. Also set the height of the .scroll-wrapper element equal to the height of your .scroll element to make sure the content is scrollable.
Javascript
var startScrollTop = 0;
$('.right').scroll(function () {
var $scroll = $('.scroll');
if ($scroll.offset().top <= 0 && $('.right').scrollTop() > startScrollTop) {
if (startScrollTop === 0) {
startScrollTop = $('.right').scrollTop();
}
$('.scroll-wrapper').css("height", $('.scroll').height() + 300);
$scroll.addClass("fixed-top");
} else {
$scroll.removeClass("fixed-top");
}
})
Take a look at this fiddle.
http://jsfiddle.net/a924dcge/25/
Hope that helps!

Pass click through to fixed element

I have a static element that is above a fixed element. When the upper static element is clicked, I want to pass this click through to the fixed element. How can I do this? Here is a working example (I wasn't able to get this to work in JSFiddle).
<body>
<div class="lower" onclick="lowerClick()"></div>
<div class="upper" onclick="upperClick()"></div>
</body>
<script>
function lowerClick() {
alert("LOWER CLICK");
};
function upperClick() {
alert("UPPER CLICK");
};
</script>
//CSS file
.lower {
height: 100px;
width: 100%;
z-index: -1;
position: fixed;
background-color:blue;
}
.upper {
height: 50px;
width: 100%;
z-index: 1;
position: static;
background-color:red;
pointer-events: none;
}
I thought that adding pointer-events:none to my upper element would make this work, but when I click the top element, nothing happens.
In fact pointer-events:none works expectedly. But what prevents you from clicking on the lower is the body element. Because of setting z-index:-1 on your lower, it will be sent to behind of its parent (which is the body). So you can set the z-index of the body to a more negative number, of course the postion should be some value other than static (default):
body {
z-index:-1000;
position:relative;
}
Demo
You can use the following css for the upper div
.upper {
pointer-events:none;
}
...also avoid negative z-index, and instead use 1 for lower and 2 for upper. Also why put static and not absolute element at the top?

Firefox like inspecting element

I am building my website with inspecting elements option to inspect each elements separately like firebug. I like to built the styles like newer version of Firefox which will blur all the elements except the selected element. Any idea on how to do this? The example of the needed output is given below.
EDIT : Please note that, here the element i need to select may have lower DOM hierarchy than the other elements. For eg. i may need to gray out the body container and if i select some internal elements which should not have the grey effect.
Something like this:
http://jsfiddle.net/lollero/T7PyK/
Clicking any element will show overlay and isolate the element.
Clicking overlay will undo that.
JS:
$('*').on("click", function( e ) {
e.stopPropagation();
var self = $(this),
overlay = $('#overlay');
if ( !self.hasClass('active') ) {
if ( self.is(':not(#overlay)') ) {
self.addClass('active');
}
overlay.fadeTo(400, 0.7);
}
if ( self.hasClass('active') ) {
overlay.on("click", function() {
overlay.fadeOut(400, function() {
self.removeClass('active');
});
});
}
});
​
CSS:
#overlay {
display: none;
background: #000;
position: fixed;
z-index: 100;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.active {
position: relative !important;
z-index: 101 !important;
background: #fff;
box-shadow: 0px 0px 50px #111;
}
​
HTML:
<div id="overlay"></div>
Put a div with a background color of black, 100% width/height, absolute positioning and left/top of 0, and an opacity of somewhere between 0 and 1 (eg. 0.5). That gives you the "gray out the page effect".
Then, put the text that you don't want grayed out in a separate div that's higher in the DOM hierarchy (or at the same level but with a higher z-index), so that it won't get covered up by your graying-out div.
You can use jQuery overlay effect along with some CSS..
hit this link: http://tympanus.net/codrops/2009/12/03/css-and-jquery-tutorial-overlay-with-slide-out-box/

Categories

Resources