Screen width detection with Javascript / jQuery - Sidebar to Tabbed Pullout - javascript

I have a responsive design, with selectable sidebar data drawn from a database. At a screen width less than 751 pixels, that sidebar becomes a pull-out tab on the left of the screen. I cannot feasibly reload the data on state change (sidebar to tab or vice versa) as the amount of data is extensive. So, the solution seems to be using the tabbed state (using MB Extruder - a "hidden" tab utility) as the sidebar, also, and just changing the state of the div. However, that cannot be done without javascript as, in the sidebar state, Extruder needs to be open, whereas it needs to be closed when in the tab state.
So, I am doing the following to set the sidebar/tab:
$(document).ready(function()
{
CheckScreen();
},
$(window).resize(function()
{
CheckScreen();
}));
function CheckScreen()
{
var ww=$(window).width();
if(ww < 751)
{
$('#extruderLeft').closeMbExtruder();
$('.extruder.left .flap').css('display', 'block'); // The tab
$('.site_wrapper').css('padding-left', '30px');
}
else
{
$('#extruderLeft').openMbExtruder(true);
$('.extruder.left .flap').css('display', 'none');
$('.site_wrapper').css('padding-left', '0px');
}
}
This changes the state from a sidebar column to a hidden state with a small tab on the left side of the screen when the screen width is less that 751 pixels. This will work fine at any size screen on document.ready. It will adjust fine when dragging the side of a browser from larger to smaller. However, when dragging back out to a larger width, the div will, rather randomly, switch from one state to another.
Perhaps there is a better way to do this altogether. If worse came to worse, I could have two separate entities (sidebar and tabbed state) holding the same data, and just use CSS, but that would be ridiculously redundant.

The problem seemed to be faulty conditions in the if-clauses (see comments under the question).
This should do the trick:
$(window).load(function() {
$(window).resize(function() {
if ($(window).width() < 751) {
if ($('.extruder.left .flap').css('display') != 'block') {
$('#extruderLeft').closeMbExtruder();
$('.extruder.left .flap').css('display','block'); // The tab
$('.site_wrapper').css('padding-left','30px');
}
} else if ($('.extruder.left .flap').css('display') != 'none') {
$('#extruderLeft').openMbExtruder(true);
$('.extruder.left .flap').css('display','none');
$('.site_wrapper').css('padding-left','0');
}
}).resize();
});
Notice the extra if-clauses checking the display-state:
if ($('.extruder.left .flap').css('display') != 'block') {
and
} else if ($('.extruder.left .flap').css('display') != 'none') {
This makes sure the sidebar/tab-switch only occurs on the break point of the specified screen-width, and the if-clauses aren't unnecessarily executed.
I also changed your script a bit to make more efficient use of jQuery. This way you don't have to create a named function AND call it twice. (I always put window.resize inside window.load instead of document.ready because if you need to scale things that only works properly after load anyway, but for your purpose both will work.)

Related

Detect when the end of a div is visible

I need to know if the end of a div element is currently visible in the users' browser.
I tried something I saw on the web, but scrollTop() always gave me zero in my Browser. I read something about an issue in Chrome, but I didn't understand quite well.
jQuery(
function($) {
$('#flux').bind('scroll', function() {
if ($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
alert('end reached');
}
})
}
);
My idea is the following:
1- User loads page and sees a Bar (sticky div at bottom visible page) with some information.
2- After scrolling a bit, and reaching the end of a div element, this bar will position there, after the div. This is the bar's original position
I wasn't really able to know when I was at the end of the div element. Eventually I found this code:
if ($(window).scrollTop() >= $('#block-homepagegrid').offset().top + $('#block-homepagegrid').outerHeight() - window.innerHeight) {
$('.hero-special-message').removeClass('hero-special-messege-scrolling');
} else {
$('.hero-special-message').addClass('hero-special-messege-scrolling');
}
});
I see that it's working, but I'm having a bit of trouble understanding what it does.
I know the following:
1. $(window).scrollTop();
this gives me the amount of pixels the user has scrolled, pretty self explanatory.
2. $('#block-homepagegrid').offset().top;
I THINK this is the distance between the start of the page and the start of the div. I know it's the current coordinates, but what is top exactly here?
3. $('#block-homepagegrid').outerHeight();
this gives the height of the element, I know there are 3, like
height(), innerHeight() and outerHeight(), if you want to take into
account border, margin, padding, which is the better to use?
4. window.innerHeight;
I understand this is what the user sees, but I'm having troubles understanding why does it matter for my situation.
Thanks!
You may be interested in the native JavaScript IntersectionObserver API. It automatically figures out what percentage of a given element is visible in the window and triggers callbacks based on that. So then you can do this:
function visibleHandler(entries) {
if (entries[0].intersectionRatio >= 1.0) {
// The whole element is visible!
} else {
// Part of it is scrolled offscreen!
}
}
const observer = new IntersectionObserver(visibleHandler, {threshold: 1.0});
observer.observe(document.getElementById('flux'));
Now, whenever the element with ID flux is 100% in view, it will trigger the visibleHandler. It will also trigger again if it's scrolled back out of view; that's why the function checks the ratio of visibility to see if it just hit 100% or just got reduced from 100%. You could be more fancy and use the observer entry's insersectionRect, which gives you the rectangle containing the visible portion of the element, and use that to determine top/bottom visibility.

change content on resize without page refresh?

I'm using ShieldUI's "Tabs" and there is a property for the tabs position. I want when the screen width is >= 768px to display the tabs at "left" and while <= 768px to display them on the "top". I came to here:
var $window = $(window);
var $pane = $('#tabsDiv');
function checkWidth() {
var windowsize = $window.width();
if (windowsize < 768) {
$pane.shieldTabs({
position: "top"
});
} else {
$pane.shieldTabs({
position: "left"
});
}
}
checkWidth();
$(window).resize(checkWidth);
But when i'm full width and i go "mobile" i have to refresh the page to get what i want, is there a way to do that without page refresh?
We kind of have some issues with your code, along with the Shield UI limitations. Let's see some of them:
First of all, for what I've checked at the official docs, the framework does support component refreshing, but only for new options you'd like to add to your widgets. Therefore, your code won't work when you try to update specific features of the tab (like the position). This means that it's good to have a global function (initTabs, in our case) for destroying and recreating the whole tab structure;
Second, once you do that, don't declare the resizing function inside of this global new one, in order to evict recursiveness problems;
It's also a good idea creating a global variable to store the state of the tabs position, since we have now local/global functions to handle.
Check out the final working code: https://jsfiddle.net/ro0a5bhv/4/
Note: See that I had to do a workaround regarding the CSS property min-height, which is always set by the framework when switching tab views.

How to force a drop-down to move down on IE 11?

I have the following drop-down :
<select>
<option></option>
<option>Closed</option>
<option>Open</option>
</select>
with the associated style:
select {
font-family: Cursive;
width:200px;
position: relative;
z-index: 100;
padding-right: 25px;
}
My problem is that the drop-down is moving upward on IE 11:
Where as on chrome it is working fine.
Any idea ?
Like mentioned in the comments, select menus are very browser specific and hard to style. Some even send the options into the twilight zone where they are seemingly not even a part of the window and any events will return null. It might not be worth trying to get this to look the same across browsers, also because of the mobile implementations, but I happened to be making something like this for no apparent reason. As it coincides with your question I might as well post it.
It's not the prettiest thing when it comes to HTML and CSS because it requires four additional elements - one wrapper (commonly used for styling select boxes with overflow hidden but I took a slightly different approach because I thought it looked better) and three absolutely placed elements. One is a styled button, another will hide the scrollbar that appears and the third is a minor hack.
Most important thing is that the user will not be able to click the select menu itself. When this happens, most is lost because after that it's limbo. For that the third element will be used. It will be put on top of the select box. Then when it's clicked, instead of directly opening the menu it will be faked by changing the size of the select element. The div covering the right side also serves another purpose. It's initially placed at the bottom and by getting it's offset we'll know the height of the box. This will be used to resize the button and set the correct size for the overlaying div.
Looks to be behaving quite predicatbly on all major Windows desktop browsers. For the mobile implications this script uses a touch support feature test and reverts to normal layout if that is the case. Could probably be tweaked (with a screen size check) to not exclude trackpad users.
Demo
Not too much relevant CSS. But important to style body background the same as the bar on the right. Transparency is used so the actual menu isn't visible before the image for the button loads.
$(function() {
var hub = $('#box'), list = $('select'),
impel = $('#open'), commit = $('#mark'), tract = $('#refer'),
zenith = tract.position().top,
extent = list.children().length, active;
if (touch()) {
impel.add(commit).add(tract).remove();
hub.fadeTo(0,1);
return;
}
impel.add(commit).height(zenith);
tract.addClass('bar');
hub.fadeTo(0,1).on('mouseup click', function(e) {
e.stopPropagation();
});
commit.mouseup(function() {
flip();
show();
active = true;
});
list.add(impel).click(function() {
flip();
active = !active;
if (active) show();
else hide();
});
$(window).click(function() {
if (active) {
flip();
hide();
active = false;
}
});
function show() {list.attr('size', extent)}
function hide() {list.removeAttr('size')}
function flip() {commit.toggle()}
function touch() {
return 'ontouchstart' in window
|| navigator.maxTouchPoints > 0
|| navigator.msMaxTouchPoints > 0;
}
});

Maintaining page view on window resize in a responsive website

Situation:
Suppose we are reading the content somewhere down the page that is built to be responsive. Suppose also that we resize the browser window to a smaller size and that some content above get extended down due to the thinner width, hence making the whole page longer. Then as we resize, whatever content we are looking at will get pushed down the page accordingly.
Example:
Suppose we were to look at the Helper classes section in this page. Then shrinking/expanding the window a sufficient amount moves the bit we were reading down/up the current view.
Prompt:
Is there any way we can fix this? I.e. maintain our current view of the page regardless of what happens to the contents above it when we resize the window.
Thoughts:
I am thinking that we could at least start with javascript and put an event on window resize. Then automatically scroll the page to the top-most element that was in our view on event fire. I don't know how this will affect the performance, however, especially in bigger pages.
There's also the problem of refering to the top-most element in current view. The top of our current view might be cutting off the top portion of some elements, not to mention that there's usually more than 1 element layered on top of one another at any point within the page. The notion of top-most element I've mentioned is not very well-defined :(
Also rather than a problem of responsive design in general, instead it seems to me like this is a problem with the default scrolling behaviour of web browsers? Or perhaps I am missing some circumstances where the current behaviour is desirable.
Edit 2 4
Updated fiddle (see fullscreen result) based on Rick Hitchcock's solution's solution.
With jQuery:
//onresize:
var scrollAmount;
if (topNode.getBoundingClientRect().top >= 0) {
scrollAmount = $(topNode).offset().top - topNode.getBoundingClientRect().top;
} else {
scrollAmount = $(topNode.offset().bottom - topNode.getBoundingClientRect().bottom;
}
$(window).scrollTop(scrollAmount);
The fiddle is acting a bit weird even in the same browsers, I've uploaded the same script using a free hosting here.
Still need to incorporate the IE, Opera and Safari fix for elementFromPoint.
Edit 3
Thanks for all the help, Rick Hitchcock. Welcome to stackoverflow, by the way :)
The discussion is turning into cross-browser compatibility issues so I've accepted your answer since we've pretty much got the answer to the original question. I'll still be fixing up my implementation though. The focus being cross-browser issues, topNode criteria, and topNode cut-off handling.
An edge case
While playing around with it, I noticed that when we were at the bottom of the page in a small viewport, then switch to a larger viewport (let us assume now that some more elements that were originally above the element we saw now came into view due to shorter container from wider viewport) the window cannot always lock the topNode to the top of the viewport in such a case since we've reached the scroll bottom. But then switching back to the small viewport now uses a new topNode that got into the viewport during the switch.
Although this should be expected from the behaviour being implemented, it is still a weird side-effect on scroll bottom.
I will also be looking into this in due course. Initially, I am thinking of simply adding a check for scroll bottom before we update topNode. I.e. to keep the old topNode when we've reached scroll bottom until we've scrolled up again. Not sure how this will turn out yet. I'll make sure to see how Opera handle this as well.
Here's what I've come up with:
(function(){
var topNode;
window.onscroll=function() {
var timer;
(function(){
clearTimeout(timer);
timer= setTimeout(
function() {
var testNode;
topNode= null;
for(var x = 0 ; x < document.body.offsetWidth ; x++) {
testNode= document.elementFromPoint(x,2);
if(!topNode || testNode.offsetTop>topNode.offsetTop) {
topNode = testNode;
}
}
},
100
)
}
)();
}
window.onresize=function() {
var timer;
(function(){
clearTimeout(timer);
if(topNode) {
timer= setTimeout(function(){topNode.scrollIntoView(true)},10);
}
}
)();
}
}
)();
If there were a window.onbeforeresize() function, this would be more straightforward.
Note that this doesn't take into account the scrolled position of the element's textNode. We could handle that if only the height of the window were resized. But resizing the width would generally cause reformatting.
This works in Chrome, Firefox, IE, and Safari.
Edit
How it works
The code's closures make variables private, and the timers prevent the code from running constantly during scrolling/resizing. But both tend to obfuscate the code, so here's another version, which may aid in understanding. Note that the onscroll timer is required in IE, because elementFromPoint returns null when it used in onscroll event.
var topNode;
window.onscroll=function() {
setTimeout(
function() {
var testNode;
topNode= null;
for(var x = 0 ; x < document.body.offsetWidth ; x++) {
testNode= document.elementFromPoint(x,2);
if(!topNode || testNode.offsetTop>topNode.offsetTop) {
topNode = testNode;
}
}
},
100
)
}
window.onresize=function() {
if(topNode) {
topNode.scrollIntoView(true)
}
}
topNode maintains the screen's top-most element as the window scrolls.
The function scans the screen left to right, along the 3rd row: document.elementFromPoint(x,2)*
It doesn't scan along the 1st row, because when IE does scrollIntoView, it pushes the element down a couple pixels, making the top-most screen element the previous element. (Figured this out through trial and error.)
When the window is resized, it simply positions topNode at the top of the screen.
[*Originally, onscroll scanned left to right along the 11th row (in pixels) until it found an element with just one child. The child would often be a textNode, but that wouldn't always be the case. Example:
<div><ul><li>...<li>...<li>...</ul></div>
The div has only one child – the ul. If the window were scrolled to the 50th li, scanning left to right would incorrectly return the div due to the inherent padding of lis.
The original code has been updated.
]

Perform function when window.width is resized but not the height

I'm developing a responsive website and I have some tabs that change from tabs to an accordion on small screens. Here is how I am doing it at the moment:
var myGlobalVariable = {};
$(window).resize(function(e) {
duringResize();
myGlobalVariable.windowWidth = window.innerWidth;
});
function widthHasChanged() {
return window.innerWidth !== myGlobalVariable.windowWidth;
}
function duringResize() {
if(widthHasChanged()) {
if(Modernizr.mq('all and (min-width:1000px)')) {
/* Do stuff for tabs */
} else {
/* Do stuff for accordion */
}
}
}
I'm not happy about this because I'm having to use a global variable to store the last width of the browser window in order to check wether the width has changed.
The reason I have to do this is because on mobiles when the tabs are in accordion mode clicking on one actually makes the document taller to accommodate the tab content. For some reason this is classed as a resize even though the 'window' is still the same size on a mobile. This meant that my tab/accordion code was being called even when the width hadn't changed and this was messing things up.
Is there something I'm missing or is this the only way to achieve this? jQuery and vanilla javascript solutions are welcome.
There is no specific event to bind to for window width change. Your solution looks fine to me.
The only other way I could think of was to do a regular check on $(window).width inside a timer. I think on balance, the single global variable is preferable :)

Categories