JavaScript window.scroll delay causes asynchronous redraw - javascript

The Goal
So I have a list of <div>s all in a single column layout that have either the class "active" or "inactive". The active class shows a graphic to the right of the item and the inactive class doesn't. I have it setup so that hitting the up or down arrow key moves the "active" class (and the graphic with it) to the previous or next item. It isn't animated, but you can visually see the graphic disappearing and reappearing on the tag above or below.
Now I'd like to have the page scroll down on arrow keypress so that the top edge of the item is always in the same spot. Since the element list is larger than the page window, it's necessary to automatically scroll the browser so that the selected <div> is always in the center of the screen...
The Code
//Paging through items with arrow keys
theWindow.keydown(function (e) {
var key = e.keyCode,
oldItem = $('li.active')
if ((key === 40 && oldItem.next().length) || (key === 38 && oldItem.prev().length)) {
var theWindowMod = (window.innerHeight / 2) + 43,
theHTML = $('html'),
theDetail = $('.detail')
theHTML.addClass('notransition')
if (key === 40 && oldItem.next().length) {
oldItem.removeClass('active').next().addClass('active')
} else if (key === 38 && oldItem.prev().length) {
oldItem.removeClass('active').prev().addClass('active')
}
var newItem = $('li.active')
window.scroll(0, newItem.offset().top - theWindowMod)
e.preventDefault()
$('.detail-inner.active').fadeOut(0).removeClass('active')
$('section.active, .tab.active').removeClass('active')
newItem.find('.tab').add(theDetail).addClass('active')
theDetail.find('.detail-' + newItem.attr('class').split(' ')[0]).addClass('active').fadeIn(0)
setTimeout(function () {
theHTML.removeClass('notransition')
}, 1)
}
});
The Problem
The problem is that in all versions of Safari but no other browser, the window.scroll method is just a bit behind the CSS class switching performance wise. What happens is they end up in two different redraw events and it looks like the page is 'glitching' when you scroll down because you can briefly see the graphic to the right of the next element before the browser scrolls down.
The Live Demo
You can view it live here:
http://hashtag.ly/#minecraft
Use the arrow keys to page through items. Notice the jump. How should I go about resolving this?

I think a solution that avoids the issue is your best bet.
From a UX perspective, when I'm browsing a site, I don't like it when the site usurps control of the scroll position.
Also, for people with tall-ish browsers (like me) currently there can be a lot of white real-estate between the details and the selected post. (see screenshot)
My recommendation is to change the design so the details show up next to the selected post and let the user do the scrolling. Controlling the locaiton of the details with CSS, so they're next to the selected post, will put it in the same render-cycle as everything else.
The details being closer to the selected post might look something like this:
Update:
Come to think of it, AOL's email client Alto has the UX that you've implemented. In Alto, the left column does scroll automatically if you browse with the keys. But, you're not actually scrolling, they're adding content into the container element and bringing it into view (I forget what this is called... virtualization?). It looks like they're managing all the scroll-related visuals and behavior, themselves, and are not using the native functionality. So, it's all JS controlled CSS and DOM manipulation, there isn't actually a scrollTo() invocation. This puts it all in the same render cycle.

Try using a button instead of the div and use setfocus() when the specific item is activated. The browser will automatically scroll to make the focused button always visible. You can use CSS to make the button look exactly like the div.

Related

Generic code to fade in divs with particular class, remove function after first run

I'm trying to create a generic function that can be placed just once in my site and work across multiple pages, nice and lightweight.
I want to be able to make certain divs on the site fade-in when you reach 10px above them on the scroll.
I want to do this by simply adding the following attributes to my divs:
.fade-in-block
#specific-block-name
The idea is that I could go through the site, add this class and an ID, and the animation would work.
I almost have it working except for one thing, the scroll listening constantly continues to console.log after the function has been called. I don't like this as it feels like it's going to be constantly trying to apply the animation, which won't really be seen from the front-end but I feel the constant maths behind the scenes could slow stuff down.
Here is my jQuery:
$('body .fade-in-block').each(function(){
var block = '#'+$(this).attr('id');
console.log('Block class is = '+block);
var offset = $(block).offset().top;
var $w = $(window).scroll(function () {
if ($w.scrollTop() > offset - 10) {
console.log('reached block turn-on point for '+block);
$(block).removeAttr('id'); // remove the ID from the element so the script doesn't continue to find the element
// fade and rise animation here
}
});
});
And here is a JSFiddle. It works just fine, but once you hit the block you'll see it logs constantly every pixel scrolled.
I tried to remedy this by removing the selecting id from the element once the event has occurred, but it continues to run.
Scroll and resize events both have this problem and the solution is said to be debouncing. However, I've never actually gotten debouncing to work properly. Instead I typically create a sort of switch that is turned off once the scroll condition has activated. In your case, since you have multiple elements, you would need to assign a switch to each element.
$(window).on('scroll', function(){
$('.fade-in-block').each(function(){
var appear = $(this).attr('data-appeared');
if(!appear){
$(this).attr('data-appeared', true);
//do something to $(this)
}
})
})
Here I'm adding a data attribute after it has appeared and checking for it again once it has.

My firefox addon code is destroying navigation bar

I want to add a toolbar button before the firefox search container in my addon. But it is completely clearing my navigation bar.
I suspect the offending code is due to an empty array or something but i cant be certain.
//insert before search container
if(navBar && navBar.currentSet.indexOf("mybutton-id")== -1 )//navBar exist and our button doesnt
{
var arrayCurrentSet= navBar.currentSet.split(',');
var arrayFinalSet= [];//empty at first
if(arrayCurrentSet.indexOf("search-container") != -1)//if search-container exists in current set
{
// check item by item in current set
var i= null;
while(i=arrayCurrentSet.shift() != undefined)
{
if(i == "search-container")//"search-container" found !!
{
/*insert our button after it but only if our button does not already exist*/
if(arrayFinalSet.indexOf("mybutton-id") == -1) arrayFinalSet.push("mybutton-id");
}
arrayFinalSet.push(i);
dump("arrayFinalSet "+ i);
}
}
else //damn search-container doesnt exist
{
arrayFinalSet= arrayCurrentSet;
arrayFinalSet.push("mybutton-id");//add our button to the end of whatever is available in nav bar
}
//set new navBar
navBar.currentSet= arrayFinalSet.join(',');
}
The full code is available
https://builder.addons.mozilla.org/addon/1052494/latest/
http://jsfiddle.net/CQ4wA/
I'm not too sure why the navigation bar has been removed, but I think it would be better to approach this from a different angle. Rather than messing around with an array of strings, try using DOM methods instead.
e.g.
var sC=navBar.querySelector("#search-container");
navBar.insertBefore(btn, sC);
The code you have here seems to work - but the toolbar needs to find your button somehow. Your current code doesn't even insert the button into the document, meaning that the toolbar has no chance to find it by its ID. It should be in the toolbar palette palette however, the palette also determines which buttons the user can choose from when customizing the toolbar. So you probably want to do something like this first:
var toolbox = navBar.toolbox;
toolbox.palette.appendChild(btn);
You might also want to simplify your code:
var arrayCurrentSet = navBar.currentSet.split(',');
var insertionPoint = arrayCurrentSet.indexOf("search-container");
if (insertionPoint >= 0)
arrayCurrentSet.splice(insertionPoint, 0, "mybutton-id");
else
arrayCurrentSet.push("mybutton-id");
navBar.currentSet = arrayCurrentSet.join(',');
And finally, you probably want to make the browser remember the current button set, it doesn't happen automatically:
document.persist(navBar.id, "currentset");
Note that the button that will be inserted into the toolbar is not the same as the button you added to the palette - the toolbar code clones the button, with one copy being left in the palette. So event listeners added via addEventListener will sadly be lost. It is better to use a command attribute and insert a <command> element into the document that you will attach your listener to.
Note: in XUL you usually want the command and not the click event - unless you are really interested in mouse clicks only and want to ignore the button being triggered by keyboard or other means.

How can I scroll to a page element in jQuery Mobile?

I have a long jQuery mobile page and would like to scroll to an element halfway down this page after the page loads.
So far I've tried a few things, the most successful being:
jQuery(document).bind("mobileinit", function() {
var target;
// if there's an element with id 'current_user'
if ($("#current_user").length > 0) {
// find this element's offset position
target = $("#current_user").get(0).offsetTop;
// scroll the page to that position
return $.mobile.silentScroll(target);
}
});
This works but then the page position is reset when the DOM is fully loaded. Can anyone suggest a better approach?
Thanks
A bit late, but I think I have a reliable solution with no need for setTimeout(). After a quick look into the code, it seems that JQM 1.2.0 issues a silentScroll(0) on window.load for chromeless viewport on iOS. See jquery.mobile-1.2.0.js, line 9145:
// window load event
// hide iOS browser chrome on load
$window.load( $.mobile.silentScroll );
What happens is that this conflicts with applicative calls to silentScroll(). Called too early, the framework scrolls back to top. Called too late, the UI flashes.
The solution is to bind a one-shot handler to the 'silentscroll' event that calls window.scrollTo() directly (silentScroll() is little more than an asynchronous window.scrollTo() anyway). That way, we capture the first JQM-issued silentScroll(0) and scroll to our position immediately.
For example, here is the code I use for deep linking to named elements (be sure to disable ajax load on inbound links with data-ajax="false"). Known anchor names are #unread and #p<ID>. The header is fixed and uses the #header ID.
$(document).bind('pageshow',function(e) {
var $anchor;
console.log("location.hash="+location.hash);
if (location.hash == "#unread" || location.hash.substr(0,2) == "#p") {
// Use anchor name as ID for the element to scroll to.
$anchor = $(location.hash);
}
if ($anchor) {
// Get y pos of anchor element.
var pos = $anchor.offset().top;
// Our header is fixed so offset pos by height.
pos -= $('#header').outerHeight();
// Don't use silentScroll() as it interferes with the automatic
// silentScroll(0) call done by JQM on page load. Instead, register
// a one-shot 'silentscroll' handler that performs a plain
// window.scrollTo() afterward.
$(document).bind('silentscroll',function(e,data) {
$(this).unbind(e);
window.scrollTo(0, pos);
});
}
});
No more UI flashes, and it seems to work reliably.
The event you're looking for is "pageshow".
I was digging a lot this issue, also at jQuery mobile official forum.
Currently it seems that there is no solution (at least for me).
I tried different events (mobileinit, pageshow) and different functions (silentscroll, scrolltop) as suggested above, but, as a result, I always have page scrolled until all images and html is finished loading, when page is scrolled to top again!
Partial and not really efficient solution is using a timer as suggested in comment to sgliser's answer; unfortunately with a timeout is difficult to know when page will be fully loaded and if scroll happened before that, it will scroll back to top at the end of load, while if it happens too long after page has fully loaded, the user is already scrolling page manually, and further automated scroll will create confusion.
Additionally, would be useful to have silentscroll or other function to address a specific id or class and not plain pixels, because with different browsers, resolutions and devices it may give different and not correct positioning of the scroll.
Hope someone will find a smarter and more efficient solution than this.

html scrolling: jump to end of page (horizontal) w/javascript?

Is there a script that will jump the page to the horizontal end.
A user can press "Home" and "End" on the keyboard to jump to the top and bottom of the webpage, but what about right and left?
How do I spare the user of the inconvenience of having to scroll to the far, far depths of the x scroll
Horizontal scroll is a somehow unfrequent situation, as naturally the browser will try to vertical scroll unless unresizable elements really overflow the visible area; I will thus assume you are more in an explicitly-horizontal design such as one of these: http://webdesignledger.com/inspiration/40-of-the-best-horizontal-scrolling-websites
In any case, the basic is the same: catch some key* event, and do what you want.
Catch the event and launch your scroll function:
document.body.addEventListener('keydown', scrollfct, true);
First caveat: 'keypress' will not catch special characters such as up/down etc., so use 'keydown' or 'keyup'
Now the scroll function:
function scrollfct(e) {
console.log(evt.keyCode); /* this will debug that your function is being called, and will help you get the keyCodes you want. Remove in production :-) */
var HOME_LEFT = 33; //HOME
var HOME_RIGHT = 34; //END
if (e.keyCode == HOME_LEFT) {
window.scroll(0,0);
};
if (e.keyCode == HOME_RIGHT) {
window.scroll(document.body.scrollWidth,0);
}
}
For window.scroll doc: https://developer.mozilla.org/en/Window.scroll
Second caveat: to be fully cross-browser, you'll have to do some homework.
If you want to deal with mousewheel too, or want some smooth scroll, check this out: http://paulicio.us/items/view/24/horizontal-page-scrolling-using-javascript or with jQuery: http://tympanus.net/codrops/2010/06/02/smooth-vertical-or-horizontal-page-scrolling-with-jquery/ OR just use HTML5 CSS Transitions.
Normally I'd say, pagination would save the user from scrolling all over the place, but there are a couple other techniques. You could include a "right" button that links to the ID of something at the right of the page. That would jump them there immediately. That would look like this
Goto the Right
. . .
<div id="overtotheright"> . . . </div>
Or in javascript, the following should work.
window.scrollTo(document.body.scrollWidth, 0);

Prevent/stop auto anchor link from occurring

I need to prevent the automatic scroll-to behavior in the browser when using link.html#idX and <div id="idX"/>.
The problem I am trying to solve is where I'm trying to do a custom scroll-to functionality on page load by detecting the anchor in the url, but so far have not been able to prevent the automatic scrolling functionality (specifically in Firefox).
Any ideas? I have tried preventDefault() on the $(window).load() handler, which did not seem to work.
Let me reiterate this is for links that are not clicked within the page that scrolls; it is for links that scroll on page load. Think of clicking on a link from another website with an #anchor in the link. What prevents that autoscroll to the id?
Everyone understand I'm not looking for a workaround; I need to know if (and how) it's possible to prevent autoscrolling to #anchors on page load.
NOTE
This isn't really an answer to the question, just a simple race-condition-style kluge.
Use jQuery's scrollTo plugin to scroll back to the top of the page, then reanimate the scroll using something custom. If the browser/computer is quick enough, there's no "flash" on the page.
I feel dirty just suggesting this...
$(document).ready(function(){
// fix the url#id scrollto "effect" (that can't be
// aborted apparently in FF), by scrolling back
// to the top of the page.
$.scrollTo('body',0);
otherAnimateStuffHappensNow();
});
Credit goes to wombleton for pointing it out. Thanks!
This seems the only option I can see with ids:
$(document).ready(function() {
$.scrollTo('0px');
});
It doesn't automatically scroll to classes.
So if you identify your divs with unique classes you will lose a bit of speed with looking up elements but gain the behaviour you're after.
(Thanks, by the way, for pointing out the scroll-to-id feature! Never knew it existed.)
EDIT:
I know this is an old thread but i found something without the need to scroll. Run this first before any other scripts. It puts an anchor before the first element on the page that prevents the scroll because it is on top of the page.
function getAnchor(sUrl)
{
if( typeof sUrl == 'string' )
{
var i = sUrl.indexOf( '#' );
if( i >= 0 )
{ return sUrl.substr( i+1 ).replace(/ /g, ''); }
}
return '';
};
var s = getAnchor(window.location.href);
if( s.length > 0 )
{ $('<a name="'+s+'"/>').insertBefore($('body').first()); }
Cheers!
Erwin Haantjes
Scroll first to top (fast, no effects pls), and then call your scroll function. (I know its not so pretty)
or just use a prefix
This worked well for me:
1- put this on your css file
a[name] { position: absolute; top: 0px }
2- put this on your document.ready bind right before you start animating (if you're animating at all)
$("a[name]").css("position","relative");
Might need tweaking depending on your stylesheet/code but you get the idea.
Credit to: http://cssbeauty.com/skillshare/discussion/1882/disable-anchor-jump/

Categories

Resources