I have a structure like
<div id="parent">
<div>...</div>
<div>...</div>
<div>...</div>
<div>...</div>
</div>
Where the children of parent are scrollable.
If I take on of the elements from inside parent after scrolling.
const child = parent.children[3];
I have a scroll position
child.scrollTop; // Maybe 269px
And when I add it back
parent.prepend(child);
The child looses its scroll position
child.scrollTop; // 0px
My question here is, why does the scroll position get lost in this case?
Full demonstration of what is happening.
Because when you prepend(element) it's actually first removed from the DOM then reinserted at the new position. While it's removed from the DOM, it doesn't have a CSS box anymore, no defined size, no scrolling box. When appended again all the scrolling info is reset.
const elem = document.querySelector("ul");
elem.scrollTop = elem.scrollHeight;
console.log("before", elem.scrollTop);
elem.remove();
console.log("after", elem.scrollTop);
console.log("scrollHeight", elem.scrollHeight);
console.log("height", elem.getBoundingClientRect().height);
ul { max-height: 50px; overflow: scroll }
<ul>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
<li>item</li>
</ul>
Note that there is an active discussion to maybe add a "move" operation to the DOM APIs, but it's not quite sure yet what this would look like, nor if it would solve this use case.
If you wish to maintain the scrolling position, you'd have to store it before calling .prepend() and set it back after.
Without the full demonstration I would have never understood what you were talking about.
You're moving an child. In other words you first remove it and then you insert it. Once an element is removed, scrollTop makes no sense and is reset.
A solution is to scroll the element down again after it has been inserted:
app.prepend(lastChild);
lastChild.scrollTop = lastChild.scrollHeight;
That seems to work fine.
Related
I've got a page using gsap to animate scrolling.
To navigate, the menu is setup with a simple "scroll to ID" approach which is intercepted by gsap to do the scrolling.
This works as expected to scroll down the page, but not up the page. However if you've scrolled down the page, selecting the element above from the menu doesn't scroll up. But selecting an element 2 up from the current one, then scrolls up 1 element.
An example of the code;
<nav>
<ul>
<li>Section 1</li>
<li>Section 2</li>
</ul>
</nav>
<section id="section1" class="panel">
</section>
gsap.utils.toArray("nav a").forEach(function(a) {
a.addEventListener("click", function(e) {
e.preventDefault();
gsap.to(window, {duration: 1, scrollTo: e.target.getAttribute("href")});
});
});
A demo is here on codepen
This is a logical issue. Once you've scrolled past a section, the element has been moved down by 100vh. So when you navigate to its y offset, it's 100vh below where it was at the start.
There are different ways to fix it. The easiest may be to keep an array of values of the original offset and scrollTo those values instead. Demo.
Sides notes:
You can just use the selector strings as your targets in GSAP when you don't need it scoped further.
You should use a double colon (::) for pseudo-elements in CSS.
If you're going to use ES6 features like const some places, you might as well use them throughout your code.
You're more likely to get help even quicker in the GreenSock forums.
I am trying to make a drop down list and I have made it somewhat work. When I put the mouse over the area, a div in the shape of the drop down becomes visible. Then when you put your mouse over anything in the div, it disappears. That is obviously not meant to happen. Here is my code. Any solution is greatly appreciated.
HTML:
<li><a onMouseOver="showServersDropDown()" onClick="showServersDropDown()" class="three-d">
Servers
<span aria-hidden="true" class="three-d-box">
<span class="front">Servers</span>
<span class="back">Servers</span>
</span>
</a></li>
<div onMouseOut="hideServersDropDown()" id="serversDropDown">
<p>Live Map</p>
</div> <!--This ends the Server List Drop Down Div-->
JS:
function showServersDropDown() {
document.getElementById("serversDropDown").style.display="block";
}
function hideServersDropDown() {
document.getElementById("serversDropDown").style.display="none";
}
I wasn't able to reproduce this exact issue, but it sounds like the problem is caused by hovering over the child elements of the div firing the onmouseout event of the parent div. I found this answer that should help you with that: prevent onmouseout when hovering child element of the parent absolute div.
I also noticed that you are changing display to none. Once the display is set to none, the div won't render at all on the browser, which will prevent mouse events from firing on it, so hovering in that area will not cause it to reappear. I found another answer here about hovering over a hidden element to reveal it: Hover over a hidden element to show it.
Also, it seems like you are missing an onmouseover event to reveal the drop down list when you hover over it, although I may be mistaken in what you are trying to accomplish.
So in all, with two modifications to your Javascript and a small modification to your HTML, I think you can achieve your intended result with this:
<div onmouseout="hideServersDropDown(event)" onmouseover="showServersDropDown(event)" id="serversDropDown">
<p>Live Map</p>
</div> <!--This ends the Server List Drop Down Div-->
function showServersDropDown(event) {
document.getElementById("serversDropDown").style.opacity="1";
}
function hideServersDropDown(event) {
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
return;
}
document.getElementById("serversDropDown").style.opacity="0";
}
I only put the event blocking code in hideServersDropDown since you would want the onmouseover event to fire and show whether you are hovering over a parent or a child in the div. I hope this helps!
It's usually because the mouse is leaving the original div, the key is to make the submenu a child of the main div:
<ul class="menu">
<li>
<a>nav title</a>
<ul>
<li><a>sub link</a></li>
</ul>
</li>
</ul>
Then in pure css you can handle this:
.menu ul { display: none }
.menu li:hover ul { display: block }
I've been trying to sort through this for a while now, can't seem to get it working 100%. The current code reveals the .content area without any collapse. It also shifts the page down to center on the content (which in this case goes under the fold if you don't).
$(document).ready(function() {
$('.vl-option .vl-toggle-link').click(function(e) {
e.preventDefault();
$(this).closest('li').find('.content').not(':animated').slideToggle();
$(this).closest('li').toggleClass('active');
});
$('.vl-option').bind('click',function(){
var self = this;
setTimeout(function() {
theOffset = $(self).offset();
$('body,html').animate({ scrollTop: theOffset.top - 20 });
}, 310);
});
});
I've attempted a few approaches to no avail. I'll list a few below (concept was to find any siblings and collapse):
$(this).siblings('li').find('.content').slideToggle();
which actually breaks the original functionality. Then I went with the following (to try an make anything without class="active" collapse):
if ( $(this).siblings('li').not('.active').find('.content').slideToggle(); ) //also tried .hide()
which doesn't seem to have any affect on anything.
The HTML is simple
<ul>
<li class="lv-option active"><!-- when toggled, "active" class is applied... -->
Yada
<div class="content"></div>
</li>
<li class="lv-option"><!-- ...when untoggled, "active" class removed -->
Yada yada
<div class="content"></div>
</li>
</ul>
.active is only applied for stylistic reasons. it has no effect on the functionality of anything. Just needed to be able to target the :before / :after when something was selected.
I just can't seem to wrap my head around jquery... argh.
Only one content at a time should be opened in an accordion right, so applying .slideToggle() to all <li> contents will break this rule. I think it's okay with your markup since you only have two <li>, so they just slides alternately. But if you have more I think the active <li> only should be .slideToggle() other should be .slideUp() only.
You can just chain them all:
$('.vl-toggle-link').click(function(e) {
e.preventDefault();
//find the sibling content apply slideToggle, then move to their parent and add active
$(this).siblings('.content').slideToggle().parent('li').addClass('active')
// go through all the siblings of their parent and remove active, then slideUp their child content
.siblings('li').removeClass('active').children('.content').slideUp();
});
I also find your .animate({scrollTop}) not working properly because it's obtaining the old offset().top value. I think you should get the offset().top after the slideToggle() has finished. See this jsfiddle.
Or you can also calculate scrollTop: jsfiddle.
I have a <ul> element. It's CSS overflow property is scroll.
I have several list elements in the list, such that there is a scrollbar.
<ul style="overflow: scroll; height: 100px;">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
...
<li></li> // how can I judge if this element is in the viewport?
</ul>
How can I determine if a specific list item is visible in that list?
Also, if it's not currently visible, what property can I use to make it scroll into view?
PS: No libraries, please (jQuery, MooTools, etc).
This is a function I just came up with.
I did some testing on the jsFiddle link at the end of this answer, and it seems consistent.
function elementInViewOfParent(elem) {
var container = elem.parentNode;
return (container.scrollTop + container.offsetHeight) >= elem.offsetTop &&
(container.scrollTop - elem.offsetHeight) <= elem.offsetTop;
}
jsFiddle example - Just scroll it wherever you want, and click the button.
It checks for the red LI's visiblity, in this example.
If you are okay with using jQuery, this will scroll so that elem is visible and at the top.
function scrollTo(elem) {
var offset = $(elem).offset();
$(window).scrollTop(offset.top);
}
(You could even animate the scroll: jQuery scroll to element).
Another solution would be to use <a target="foo"></a>, and the change the URL fragment to scroll to a particular element, but you specifically asked to be able to tell from JavaScript, which this does not allow you to do.
I have a div which contains several elements:
<div class="menuItem designMenu" id="dMenu">
<ul class="menuList menu">
<li class="menuHeader">Design</li>
<li class="projectHeader">Mother</li>
<li class="projectBody">Some text here</li>
<li class="more">More</li>
</ul>
</div>
I need to get the height of the dMenu items that I can animate it upwards, including all the content inside. My Javascript currently:
var designHeight = $("#dMenu").height();
Returns nothing.
I've tried offsetHeight, scrollHeight, and everything else Google turns up. I'm running the jQuery at the end of the body, inside a document ready function.
The reason to get the height to animate, instead of doing it manually, is that a: I'd like to learn how and b: I don't yet know how many items will be in the div.
Are the lis within the ul floated? If so, and the parent does not have overflow: hidden the parent collapses and therefor has no height. Try (if possible) adding overflow: hidden to the parent element, or hard code a height in your CSS.
I'm using scrollHeight to get at what I want. I also, embarrassingly, noticed as I was fiddling with things I was loading my JS before loading JQuery. So ashamed I didn't spot that.