I'm trying to make a collapsible submenu that, when there isn't enough space to fit all items, will throw overflowing items into a Bootstrap-style dropdown menu.
I'm doing this by checking if the submenu is greater than a certain height. If it's higher than this set height, it's considered overflowing and thus need to run it's function. However, the project I'm working on can't really have set heights on elements. These might change over time, and it will be a maintenance nightmare to go through all the code if something changes.
So how do I check if an element's height is higher than its own (I guess 'initial') height?
Setting if(menuHeight >= menuHeight) will cause a Maximum call stack size exceeded-error and crash the tab in Chrome.
Example:
var CollapseMenu = {
init: function(parent) {
var menu = parent;
var menuHeight = menu.height(); // Using jQuery here; vanilla JS could also work
if(menuHeight >= 64) // This is the set height I want to avoid
// ...
} else {
// ...
}
}
}
$(function() {
var submenu = $('.submenu');
if(submenu.length) {
CollapseMenu.init($('.submenu ul'));
}
$(window).resize(function() {
if(submenu.length) {
CollapseMenu.init($('.submenu ul'));
}
});
});
Related
So I have a script that adds a slight shadow to table edge where you can scroll, depending on the location of the scrollbar, but it sometimes doesn't work.
This is one part of it:
$('table').on('scrollstart scrollstop', function(){
if($(this).parent().hasClass('table-wrap')){
var elem = $(this),
elemBody = elem.find('tbody'),
elemParent = elem.parent('.table-wrap');
var scrolled = (elemBody.outerWidth() - elemParent.outerWidth() - elem.scrollLeft());
if(scrolled === 0){
elemParent.addClass('left_active');
elemParent.removeClass('right_active');
} else if(elem.scrollLeft() === 0) {
elemParent.removeClass('left_active');
elemParent.addClass('right_active');
} else {
elemParent.addClass('left_active');
elemParent.addClass('right_active');
}
}
});
This part sometimes I have to add "+1" to "elem.scrollLeft() --here---); to make it work.
var scrolled = (elemBody.outerWidth() - elemParent.outerWidth() -
elem.scrollLeft());
But then I noticed, some tables it helps and on others, it stops working. Meaning when I scroll to right the 'right_active' class will not disappear.
Any suggestions?
Have you tried including scrollbar width in your calculation for var scrolled?
var scrolled = (elemBody.outerWidth() - elemParent.outerWidth() - elem.scrollLeft());
I think you are on the right track but the width is probably not precise since .outerWidth() doesn't include scrollbar width. Hence, the maximum scroll width is always greater than the actual element width.
I am trying to replicate the scrolling effect from here: http://www.altisliferpg.com/
I have a feeling that they are using a heavily modified version of Bootstrap Navbar, which I have taken from here: http://www.enjin.com/forums/page/1/m/10826/viewthread/8514993-boot-strap-30-navbar-full-module and have changed it to fit into my specific case.
How would I make it so when you scroll down the page, the bar on the top gets "smaller" and scrolls along with the page as you scroll? Thanks
You can use css transitions for the height, font size and whatever else you want changed. Then simply set a scroll listener, which adds a class to the header so the size changes. Quick (and very ugly) example. jsFiddle
$(window).scroll(function () {
if ($(this).scrollTop()) {
$('#header').addClass('small');
}
else {
$('#header').removeClass('small');
}
});
Maybe you should detect the scroll event of the window, after that, set the position of the navbar to fixed and then, perform the animation. Here's an example of the javascript part and a link see it in action:
$(function(){
var performingDownAnimation = false,
performingUpAnimation = false;
var performScroll = function(){
if($("body").scrollTop() > 0) {
if(performingUpAnimation) {
$('#logo').stop();
performingUpAnimation = false;
}
if(!performingDownAnimation){
$('#navbar').addClass('navbar-fixed');
$('#logo').animate({ 'font-size': "12px" }, 1000, function(){
performingDownAnimation = false;
});
performingDownAnimation = true;
}
}else if($("body").scrollTop() == 0){
if(performingDownAnimation) {
$('#logo').stop();
performingDownAnimation = false;
}
if(!performingUpAnimation){
$('#navbar').removeClass('navbar-fixed');
$('#logo').animate({ 'font-size': "48px" }, 1000, function(){
performingUpAnimation = false;
});
performingUpAnimation = true;
}
}
}
$(document).on('scroll', performScroll);
});
On scroll event and position fixed
I edited my response for adding support for the "up" direction too. About using bootstrap for the animation, I have no idea how to do it, and I think it can't be done, because bootstrap is based mainly on applying CSS classes to different elements. CSS classes are discrete, but you are asking for animating something numerical, as the font-size property is. As much, you could create an animation that looks "staggered".
I've started with the below which works perfect, but I only need this to run and execute when my nav element .mainNav is scrolled to within a certain point within browser height.
#media screen and (max-height: 660px) {
.mainNav {
margin-top:-130px !important;
}
}
So detecting vertical browser height WITH scroll position within then chain .css with jQuery to. (margin-top:-100px)
Basically how to combine above parameter with below parameter. Below detects scroll position...
var $document = $(document),
$element = $('#some-element'),
className = 'hasScrolled';
$document.scroll(function() {
if ($document.scrollTop() >= 50) {
// user scrolled 50 pixels or more;
// do stuff
$element.addClass(className);
} else {
$element.removeClass(className);
}
});
You could do this by using the clientBoundingRect of the element. For example (untested, just theory);
var mainNav = document.getElementsByClassName('.mainNav')[0];
window.onscroll(function(e){
if(mainNav.clientBoundingRect().top <= 100 && window.height <= 660){
mainNav.style.marginTop = "-130px";
// or you could add a class here, as per the suggestion above, such as
// mainNav.setAttribute('class', 'mainNav locked');
}else{
mainNav.style.marginTop = "0";
// and remove it here
// mainNav.setAttribute('class', 'mainNav');
}
})
There is no visbility pseudo-selector in CSS, so you're looking at implementing this using JS handing of the visibilitychange event on your element, and then toggling the right (sequence of) class(es) through the element.classList interface, probably with transition rules in the CSS itself for the properties you want to be dynamic.
I am having this problem where i have a set of 6 UL's having a common class x.Each of them consist of a specific section of the page.Now i have 6 menus that are related to each of the section.What i have to do is highlight the menu when its related section is in users view.
For this i thought that may be jQuery position(); or offset(); could have helped but they give the top and left of the element.I also tried using jQuery viewport plugin but apparently view port is big it can show more than one UL at a time hence i cant apply element specific logic here.I am not familliar to this but does anything changes of an element on scrolling?If yes then how to access it?
Please share your views.
Regards
Himanshu Sharma.
Is very easy to do it using jQuery and a dummy fixed HTML block that helps you find the current position of the viewport.
$(window).on("scroll load",function(){
var once = true;
$(".title").each(function(ele, index){
if($(this).offset().top > $("#viewport_helper").offset().top && once){
var index = $(this).index(".title");
$(".current").removeClass('current')
$("#menu li").eq(index).addClass('current')
once = false;
}
});
})
Check out a working example: http://jsfiddle.net/6c8Az/1/
You could also do something similar with the jQuery plugin, together with the :first selector:
$(window).on("scroll load",function(){
$(".title:in-viewport:first").each(function(){
var index = $(this).index(".title");
$(".current").removeClass('current')
$("#menu li").eq(index).addClass('current')
});
})
You can get the viewport's width and height via $(document).width() and $(document).height()
You can get how many pixels user scrolls via $(document).scrollTop() and $(document).scrollLeft
Combining 1 and 2, you can calculate where the viewport rectangle is
You can get the rectangle of an element using $(element).offset(), $(element).width() and $(element).height()
So the only thing left to you is to determine whether the viewport's rectangle contains (or interacts) the elements's rectangle
So the whole code may look like:
/**
* Check wether outer contains inner
* You can change this logic to matches what you need
*/
function rectContains(outer, inner) {
return outer.top <= inner.top &&
outer.bottom >= inner.bottom &&
outer.left <= inner.left &&
outer.right >= inner.right;
}
/**
* Use this function to find the menu related to <ul> element
*/
function findRelatedMenu(element) {
return $('#menu-' + element.attr('id'));
}
function whenScroll() {
var doc = $(document);
var elem = $(element);
var viewportRect = {
top: doc.scrollTop(),
left: doc.scrollLeft(),
width: doc.width(),
height: doc.height()
};
viewportRect.bottom = viewportRect.top + viewportRect.height;
viewportRect.right = viewportRect.left + viewportRect.width;
var elements = $('ul.your-class');
for (var i = 0; i < elements.length; i++) {
var elem = $(elements[i]);
var elementRect = {
top: elem.offset().top,
left: elem.offset().left,
width: elem.width(),
height: elem.height()
};
elementRect.bottom = elementRect.top + elementRect.height;
elementRect.right = elementRect.left + elementRect.width;
if (rectContains(viewportRect, elementRect)) {
findRelatedMenu(elem).addClass('highlight');
}
}
}
$(window).on('scroll', whenScroll);
Let's see if i understood well. You have a page long enough to scroll, and there is an element that when it appears in the viewport, you wanna do something with it. So the only event that's is triggered for sure on the time the element gets in the viewport is the 'scroll'. So if the element is on the page and the scroll is on the viewport, what you need to do is bind an action to the scroll event to check if the element is in the view each time the event is trigger. Pretty much like this:
$(window).scroll(function() {
check_element_position();
});
Now, in order for you to know if the element is in the viewport, you need 3 things. The offset top of that element, the size of the viewport and the scroll top of the window. Should pretty much look like this:
function check_element_position() {
var win = $(window);
var window_height = win.height();
var element = $(your_element);
var elem_offset_top = element.offset().top;
var elem_height = element.height();
var win_scroll = win.scrollTop();
var pseudo_offset = (elem_offset_top - win_scroll);
if (pseudo_offset < window_height && pseudo_offset >= 0) {
// element in view
}
else {
// elem not in view
}
}
Here, (elem_offset_top - win_scroll) represent the element position if there was no scroll. Like this, you just have to check if the element offset top is higher then the window viewport to see if it's in view or not.
Finally, you could be more precise on you calculations by adding the element height (variable already in there) because the code i just did will fire the event even if the element is visible by only 1 pixels.
Note: I just did that in five minutes so you might have to fix some of this, but this gives you a pretty darn good idea of what's going on ;)
Feel free to comment and ask questions
The current plugin, shown below, scrolls the top-most div in a series of divs with the same class upwards, then removes it from the container, and appends it to the bottom of the series (within the container). This gives the illusion of a vertical slideshow.
$.fn.rotateEach = function ( opts ) {
var $this = this,
defaults = {
delay: 5000
},
settings = $.extend(defaults, opts),
rotator = function ($elems) {
$elems.eq(0).slideUp(500, function(){
var $eq0 = $elems.eq(0).detach();
$elems.parent().append($eq0);
$eq0.fadeIn();
setTimeout(function(){ rotator( $($elems.selector) ); },
settings.delay);
});
};
setTimeout(function(){ rotator( $this ); }, settings.delay);
};
$('.dynPanelContent').rotateEach();
However, if there are a large number of elements to scroll through, this would make for a VERY long page. As such, I am attempting to re-write this script so that it accepts a parameter which will determine how many elements to display. Any elements exceeding this number will be hidden until they are in the top 'x' number of elements. Here is an example of what I have attempted to implement.
$.fn.rotateEach = function (opts) {
var $this = this,
defaults = {
delay: 5000,
//Add a parameter named elementsShown, pass in a default value of 3
elementsShown: 3
},
settings = $.extend(defaults, opts),
rotator = function ($elems) {
//Hide the elements that are past the number to be shown
for (i = settings.elementsShown; i <= $elems.eq; i++) {
$elems.eq(i).hide();
}
$elems.eq(0).slideUp(500, function () {
var $eq0 = $elems.eq(0).detach();
var $eqN = $elems.eq(settings.elementsShown) - 1;
//Check & Show the element that is now within the show range
if ($elems.eq() == $eqN) {
$elems.eq($eqN).show('slow');
}
$elems.parent().append($eq0);
$eq0.fadeIn();
setTimeout(function () { rotator($($elems.selector)); },
settings.delay);
});
};
You can use simple CSS for this, mate.
If your elements are all of the same height (which your problem has to assume: if you are rotating a whole bunch of things dynamically, you won't want your page to change height), then you don't really need to use JavaScript for this at all. Just set the height of the container to what you want and hide the overflow. Then when you remove and append, everything appears to work. This won't take care of your dynamic configuration, though.
Improved plug-in: http://jsfiddle.net/morrison/tTJaM/
Notes:
Added support for showing X elements.
Added support for rotating only certain elements.
Added support for stopping the rotations:
Stop after X milliseconds.
Stop after X rotations.
overflow-y:hidden is added to container dynamically.
Simplified your detaching/attaching.
Known Issues:
Displaying X elements doesn't check for a maximum.