I want to provide the user with the experience of scrolling through content, but I would like to load the content dynamically so the content in their viewing area is what they would expect, but there is no data above or below what they are looking at. For performance reasons, I don't want that data loaded. So when they scroll down new data gets loaded into their view, and data previously in their view is discarded. Likewise when scrolling up. The scroll bar should represent their location within the entire content though, so using "infinite scrolling" or "lazy loading" does not look like what I need.
My solution may be that I need to re-architect things. As of now, my project is a hex-viewer that allows you to drop a binary file onto it. I create html elements for every byte. This causes performance issues when you end up with a 1MB file (1,000,000+ DOM elements). One solution would be to not use DOM elements/byte but I think this will make other features harder, so I'd like to just not display as many DOM elements at once.
Make a div, set overflow to scroll or auto. As user scrolls you can change the content of the div.
You could look at yahoo mail (the JavaScript based one) to see how they do it (they add rows with email as you scroll).
You don't necessarily need custom scroll bars.
You could look for some code here for custom scroll bars:
http://www.java2s.com/Code/JavaScript/GUI-Components/Scrolltextwithcustomscollbar.htm
or here:
http://www.dyn-web.com/code/scroll/
I'm looking for an answer to this question as well so I'll share where I'm at with it.
I have a large amount of content I want to display vertically and have the user scroll through it. I can load it all into the DOM and scroll normally but that initial creation phase is horribly slow and scrolling can awfully slow also. Also, I will dynamically add to it as I stream more data in.
So I want the same thing which is to be able to dynamically populate and update a non-scrolling area with content. I want to make it seem as if the user is scrolling through that content and have a model (which has lots of data) that is kept off the DOM until it would be seen.
I figure I'll use a queue concept for managing the visible DOM elements. I'd store queueHeadIndex and queueTailIndex to remember what off-DOM elements are shown in the DOM. When the user scrolls down, I'd work out what whether the head of queue falls off the screen and if it does update queueHeadIndex and remove it's DOM element. Secondly I'd then work out whether I need to update queueTailIndex and add a new element to the DOM. For the elements currently in the DOM I'd need to move them (not sure if they need animation here or not yet).
UPDATE:
I've found this which seems to have some promise http://jsfiddle.net/GdsEa/
My current thinking is that there are two parts to the problem.
Firstly, I think I want to disable scrolling and have some sort of virtual scrolling. I've just started looking at http://www.everyday3d.com/blog/index.php/2014/08/18/smooth-scrolling-with-virtualscroll/ for this. This would capture all the events and enable me to programmatically adjust what's currently visible etc. but the browser wouldn't actually be scrolling anything. This seems to provide mouse wheel driven scrolling.
Secondly, I think I need to display a scroll bar. I've had a look at http://codepen.io/chriscoyier/pen/gzBsA and I'm searching around more for something that looks more native. I just want it to visually display where the scroll is and allow the user to adjust the scroll position by dragging the scroller.
Stackoverflow is insisting I paste code so here is some code from that codepen link above
var elem = document.getElementById('scroll-area'),
track = elem.children[1],
thumb = track.children[0],
height = parseInt(elem.offsetHeight, 10),
cntHeight = parseInt(elem.children[0].offsetHeight, 10),
trcHeight = parseInt(track.offsetHeight, 10),
distance = cntHeight - height,
mean = 50, // For multiplier (go faster or slower)
current = 0;
elem.children[0].style.top = current + "px";
thumb.style.height = Math.round(trcHeight * height / cntHeight) + 'px';
var doScroll = function (e) {
// cross-browser wheel delta
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
// (1 = scroll-up, -1 = scroll-down)
if ((delta == -1 && current * mean >= -distance) || (delta == 1 && current * mean < 0)) {
current = current + delta;
}
// Move element up or down by updating the `top` value
elem.children[0].style.top = (current * mean) + 'px';
thumb.style.top = 0 - Math.round(trcHeight * (current * mean) / cntHeight) + 'px';
e.preventDefault();
};
if (elem.addEventListener) {
elem.addEventListener("mousewheel", doScroll, false);
elem.addEventListener("DOMMouseScroll", doScroll, false);
} else {
elem.attachEvent("onmousewheel", doScroll);
}
I imagine I'll have one class that listens to scroll events by either the virtual scroll method or the ui and then updates the ui scroller and the ui of the content I'm managing.
Anyway, I'll update this if I find anything more useful.
I think avoiding using DOM elements/byte is going to be the easier solution for me than creating a fake scrolling experience.
UPDATE: I ultimately solved this as explained here: Javascript "infinite" scrolling for finite content?
You're taking about using some serious javascript, specifically AJAX and JSON type elements. There is no easy answer to your questions. You'd need to do a lot of R&D on the subject.
Related
This question is more of an advice research, I do hope that it will be helpful for others and it won't closed, as I'm not quite sure where to ask for advice on this matter.
I've been developing for mobile for the past 6 months and I had the occasion to deal with all kinds of situations and bugs on various devices.
The most troubling was the scrolling issue, when it comes to scrolling in multiple areas of the website. On three projects that I have been working on I've been building a navigation that behaves the same way that the native iOS Facebook app has, or the Google website on mobile, etc. And for each one I came up with different solutions.
But a few days ago I have just released a new JavaScript library, drawerjs, that can be used to generate such navigation (so called off canvas concept). The difference between the other libs and this one is that is library agnostic, and it acts on touch behavior (the same way that the Facebook app behaves) not just open / close on click.
One of the things that I have left to implement is a solution for scrolling inside the menu and the navigation without affecting one another (most of the time when you scroll in such way, the content tends to scroll together with you menu or after you have reached the end of the menu scrolling).
I have two solutions in mind:
One approach would be to use the same principle I'm using for dragging the content and showing the navigation, on touchmove I prevent the default scrolling on document / content and I start translating the contents with the same amount you scroll. And with the same resistant behavior as a touch slider would have (when you exceed the boundaries and let go, the contents would translate back so it doesn't exceed the boundary anymore, or on swipe with the same behavior).
A second approach would be using the native overflow-scrolling that iOS has and would offer the same feel as described in the first approach. The downside of this would be that only iOS devices would have the nice resistant feature, but it would be, supposedly, less of a hassle the the first approach.
So I'm not quite sure which approach I should take, or if there any better solutions for that. I'm also trying to keep in mind that some users would like to hide the url bar, so scrolling on the body / html would have to be kept (on the y axis).
You could do touchmove . But as far as I understand, you want something like this?
http://jsfiddle.net/2DwyH/
using
var menu = $('#menu')
menu.on('mousewheel', function(e, d) {
if((this.scrollTop === (menu[0].scrollHeight - menu.height()) && d < 0) || (this.scrollTop === 0 && d > 0)) {
e.preventDefault();
}
});
Using this plugin from Brandon Aaron - github : https://github.com/brandonaaron/jquery-mousewheel
And it should work with Android: What DOM events are available to WebKit on Android?
Some more info here: Prevent scrolling of parent element?
Also without using the plugin above , using only jQuery you could do this like it says on the link above - answer from Troy Alford
$('.Scrollable').on('DOMMouseScroll mousewheel', function(ev) {
var $this = $(this),
scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
height = $this.height(),
delta = (ev.type == 'DOMMouseScroll' ?
ev.originalEvent.detail * -40 :
ev.originalEvent.wheelDelta),
up = delta > 0;
var prevent = function() {
ev.stopPropagation();
ev.preventDefault();
ev.returnValue = false;
return false;
}
if (!up && -delta > scrollHeight - height - scrollTop) {
// Scrolling down, but this will take us past the bottom.
$this.scrollTop(scrollHeight);
return prevent();
} else if (up && delta > scrollTop) {
// Scrolling up, but this will take us past the top.
$this.scrollTop(0);
return prevent();
}
});
The JS Fiddle he mentions: http://jsfiddle.net/TroyAlford/4wrxq/1/
Why not just provide a fixed height to your widget (min and max will also do). Then define like these -
height: x px;
overflow-y: auto;
This way till the focus is inside the widget, it'll only overflow the widget, once outside the page will scroll without affecting widget content at all.
Hello StackOverflow Community,
what I am trying to achieve is a header that can be moved with the mouse.
You klick into the header and drag the mouse and the elements inside the header will move with different speeds.
I achieved the parallaxing part but the performance is not really good. It is partially a bit laggy while dragging the backgrounds.
My question now is: what can be changed in the code to get a performance boost?
That's the part of the code that takes care of parallaxing. On every mousemove a each loop is executed which I think is the reason for the performance beeing so laggy:
var dragging = false;
var clickMouseX;
//Our object for the layers
//each layer has a different scrolling speed
var movingObjects = {
'#header-l1' : {'speed': 1},
'#header-l2' : {'speed': 1.4},
'#header-l3' : {'speed': 1.85},
'#header-l4' : {'speed': 2.2},
};
$('#header-wrapper').mousedown(function(e){
dragging = true;
//Get initial mouse position when clicked
clickMouseX = e.pageX;
$(this).mousemove(function(mme){
//execute only if mousedown
if(dragging){
//iterate through all layers which have to be parallaxed
$.each(movingObjects, function(el, opt){
var element = $(el);
//get difference of initial mouse position and current mouse position
var diff = clickMouseX - mme.pageX;
//scroll-position left speed 1
if(diff < 0) diff = -1;
//scroll position right speed 1
if(diff >= 0) diff = 1;
//get current position of layer
currLeft = parseInt(element.css('left'));
//get current layer width
elWidth = element.width();
//if right border is reached don't scroll further
if(currLeft < -(elWidth - 810)){
element.css('left', -(elWidth - 810));
}
//so do with left border
if(currLeft > 0){
element.css('left', 0);
}
//parallax it! Subtract the scroll position speed multiplied by the speed of the desired
//layer from the current left property
element.css('left', parseInt(element.css('left')) - diff*opt.speed);
});
}
});
/* Cursor */
$(this).css('cursor', 'pointer');
return false;
});
I also put a fiddle up:
http://jsfiddle.net/yWGDz/
Thanks in advance,
Thomas
P.S. maybe someone even finds out why layer two and three have the same scroll speed while having different speeds defined.
I worked at this a bit, and came up with this: http://jsfiddle.net/amqER/2/
This works a lot faster than the original (especially in firefox, where it performs a whole lot better, chrome it's still pretty slow). I also changed up some of the logic in your code, to make it make more sense.
A list of things that I did:
Minify your pngs
2 of your png files were over 2 megs, so I threw them into a png compressor (tinypng) and that reduced the size a lot. This helps with loading time and overall snappiness.
Re-use values as much as possible
In your original code, you wrote to and then subsequently read from the css left property a couple times in your code. Doing this is going to make it a lot slower. Instead, I kept an left property, and would only touch $.css when I absolutely needed to. Likewise for reading each element's width each update.
Also, like I said, I modified your logic to (I think) make more sense, given what you were trying to accomplish. It calculates a new diff each update, and tries to move according to that. Also, it doesn't try to keep moving once one of the images falls off (which yours does if you move all the way to the right, and it looks really weird). You can also look at this: http://jsfiddle.net/amqER/5/, which maybe is more like the control scheme you wanted.
Just some quick performance tips.
Try not to use $(this).mousemove instead save $(this) into a variable and use that.
var th = $(this);
th.mousemove...
Try to avoid using $.each. This is probably the part that's slowing your code down.
You can replace it with a for loop, but I would suggest, in this case, sending in each element one by one.
var parallax = function(img){
};
parallax(img1);
parallax(img2);
instantly-increase-your-jquery-performance
Whilst Xymostech's answer does greatly improve upon the original poster's original code; the performance is hardly improved for me in Chrome.
Whilst inspecting the page FPS, the solution posted here runs at 15FPS for me on a Retina MacBook Pro.
I made a very simple change to the code, altering it to use translate3d properties instead of left. Now, it runs at 55-60 FPS for me. I'd call that a massive performance boost.
If 'show paint rectangles' are turned on in Chrome, you'll see the previously posted solution is continually painting changes to the dom whilst the parallax is in motion. With the translate3d solution, there's simply zero painting done the whole time the parallax is in motion.
http://jsfiddle.net/LG47e/
I am looking for help / a point in the right direction / or a solution for a flicker/jump, when scrolling on a looping/infinite website, which can be seen in this fiddle.
What seems to be causing the jump is:
"$(window).scrollTop(half_way - child_height);", and what could also be a Chrome windows scrollTop bug, but it is happening in all browsers at the moment.
If I remove "- child_height" there is no longer a flicker but the page no longer scrolls correctly, which can be seen in this fiddle.
Also, on the very first scroll the right hand column jumps up by three boxes - also because of 'half_way', which I can fix by giving it a "bottom: -600px;"
The full code:
http://jsfiddle.net/djsbaker/j3d8r/1/
var num_children = $('#up-left').children().length;
var child_height = $('#up-left').height() / num_children;
var half_way = num_children * child_height / 2;
$(window).scrollTop(half_way);
function crisscross() {
$('#up-left').css('bottom', '-' + window.scrollY + 'px');
$('#down-right').css('bottom', '-' + window.scrollY + 'px');
var firstLeft = $('#up-left').children().first();
var lastLeft = $('#up-left').children().last();
var lastRight = $('#down-right').children().last();
var firstRight = $('#down-right').children().first();
if (window.scrollY > half_way ) {
$(window).scrollTop(half_way - child_height);
lastRight.appendTo('#up-left');
firstLeft.prependTo('#down-right');
} else if (window.scrollY < half_way - child_height) {
$(window).scrollTop(half_way);
lastLeft.appendTo('#down-right');
firstRight.prependTo('#up-left');
}
}
$(window).scroll(crisscross);
Okay - here is a 'working' version - and by works I mean it less flickery than before. I thought it was flicker free, and it was when I was on battery power, but plugged into the mains and the CPU is fast enough to get flicker.
As I mentioned, to get rid of the flicker you need to clone the objects, manipulate them and then replace them into the DOM, rather than just manipulating the DOM directly.
I did this by getting the contents of <div id="content"> manipulating them and then replacing them into that <div>.
Also, it's a good idea to only find things in the DOM once, and from then on use a reference to that object rather than searching repeatedly. e.g.
var leftSide = $(clone).find('.up-left');
....
lastRight.appendTo(leftSide);
....
$(leftSide).css('bottom', '-' + window.scrollY + 'px');
rather than:
lastRight.appendTo('#up-left');
$('#up-left').css('bottom', '-' + window.scrollY + 'px');
Searching the DOM is relatively slow, and so storing references can improve performance/reduce flicker.
Storing the object also makes the code easier to understand (imho) as you can easily see that you're referencing the same thing, rather than possibly different things.
I still get flickering in chrome on windows with Danack solution. For this site I would control all the scrolling (you already scroll manually one of the sides), and give elements absolute positions.
Or if you insist on using the browser scrolling, may be use animations: animate the height of the last elements till 0px then use appendTo, and then animato from 0px to the normal height...
This might be a long shot, but I had the same flickering when working with infinitescroll,
and ended up using imagesLoaded.I ended up appending the additional images (now loaded) with a fade in, and that prevented them from flickering because of the fact they were loaded.
So maybe by using the imagesloaded - or a callback on the images, you can solve the flickering. It does decrease the speed though. I can image that if you want to scroll through everything as fast as possible, this might not be the solution. Good luck!
A solution would be to not use the native scrolling functionality but to simulate scrolling. This would be done by setting the overflow of your content to "hidden" in addition with capturing the "mousewheel" event on it and triggering some action when it is called. I started to try this out here (using MooTools instead of jQuery since I'm more fimilar with it). It's currently just "working" on the left side by altering the margin-top of the first element.
My next steps would be:
Check if the negative margin-top of the first element is bigger than the height of it and move it to the right side if so.
Same logic for the last box on the right side with a negative margin-bottom.
This has some downsides, though. Simulating scrolling doesn't feel as natural as the native scrolling functionality and clicking the mousewheel doesn't work. These might be solveable but it would require some more coding to get it to work smoothly. Anyway, in the end you would have a solution without any flickering and with no sticky scrollbar at the side (An idea for a replacement could be a small area on the side that triggers the scrolling on mouseover).
I'm currently working under some tight restrictions with regard to what I can do with JavaScript (no frameworks such as jQuery for example, and nothing post Firefox 2.0).
Here's my problem; I have a persistent header floating at the top of the page. I have input elements scattered throughout (we're replicating a paper form exactly, including the background image). There is a field nearing the bottom of the page that gets tabbed out (using keyboard tab button) and puts the focus on a field at the top of the page. Firefox will automatically scroll the field "into view". However, while the browser believes the field is in view, it's actually hidden beneath the persistent header.
http://imageshack.us/a/img546/5561/scrollintoviewproblem.png
The blue field above is accessed by hitting "tab" from another location on the page. The browser believes the field has been scrolled into view, but it's in fact hidden beneath the floating persistent header.
What I'm looking for is ideas as to how I can detect that the field is beneath this header and scroll the entire page accordingly.
I've tried a few variations of margin & padding (see other considerations at http://code.stephenmorley.org/javascript/detachable-navigation/#considerations) without luck. I've also tried calling the JavaScript function "scrollIntoView(element)" each time we focus on a field, but given the amount of fields on the form (and the fact that we're aligning them to match the background image of a paper form exactly), this was causing some pretty severe "jumping" behavior when tabbing through fields close to each other that were at slightly different heights.
I can change how the persistent header is done, so long as it doesn't require too much effort. Unfortunately, frames are out of the question because we need to interact with the page content from the persistent header.
Ideally the solution would be in CSS, but I'm open to JavaScript if it solves my problem.
Another note, we require that the input elements have a background color, which means that adding padding to them would make the background color stretch, which hides parts of the background image. BUT, the input elements are in a div, so we might be able to use this to our advantage.
So after doing some more searching (thanks to #Kraz for leading on this route with the scrollTo() suggestion) I've found a solution that works for me.
I've added an onFocus call to each element dynamically, so they always call the scrollScreenArea(element) function, which determines if they're hidden beneath the top header or too close to the footer area (this solves another problem entirely, using the same solution).
/* This function finds an element's position relative to the top of the viewable area */
function findPosFromTop(node) {
var curtop = 0;
var curtopscroll = 0;
if (node.offsetParent) {
do {
curtop += node.offsetTop;
curtopscroll = window.scrollY;
} while (node = node.offsetParent);
}
return (curtop - curtopscroll);
}
/* This function scrolls the page to ensure that elements are
always in the viewable area */
function scrollScreenArea(el)
{
var topOffset = 200; //reserved space (in px) for header
var bottomOffset = 30; //amount of space to leave at bottom
var position = findPosFromTop(el);
//if hidden beneath header, scroll into view
if (position < topOffset)
{
// to scroll up use a negative number
scrollTo(el.offsetLeft,position-topOffset);
}
//if too close to bottom of view screen, scroll into view
else if ((position + bottomOffset) > window.innerHeight)
{
scrollTo(0,window.scrollY+bottomOffset);
}
}
Let me know if you have any questions. Thanks #Kraz for sending me onto this solution.
As well, I'd like to reference Can I detect the user viewable area on the browser? since I took some code from there and that partially described my problem (with a neat diagram to boot).
The easiest method for doing this will be listening for each element's focus event and then seeing if it is on the page. In pure JS, it is something like:
var input = Array.prototype.slice.call(document.getElementsByTagName('input'));
for ( var i in input )
input[i].addEventListener( 'focus', function (e) {
var diff = 150 /* Header height */ - e.target.getBoundingClientRect().top;
if ( diff > 0 ) {
document.body.scrollTop += diff;
document.documentElement && document.documentElement.scrollTop += diff;
}
}, false );
I didn't include the IE addEvent method, but it should be pretty easy to make that on your own given this base.
Is there a way in javascript to bind an event handler to a horizontal scroll as opposed to the generic scroll event which is fired when the user scrolls horizontally and vertically? I want to trigger an event only when the user scrolls horizontally.
I searched around for an answer to this question, but couldn't seem to find anything.
Thanks!
P.S. My apologies if I'm using some terminology incorrectly. I'm fairly new to javascript.
UPDATE
Thanks so much for all your answers! In summary, it looks like you are all saying that this isn't supported in javascript, but I that I can accomplish the functionality with something like this (using jQuery) (jsFiddle):
var oldScrollTop = $(window).scrollTop();
$(window).bind('scroll', function () {
if (oldScrollTop == $(window).scrollTop())
//scrolled horizontally
else {
//scrolled vertically
oldScrollTop = $(window).scrollTop();
}
});
That's all I needed to know. Thanks again!
Answering from my phone, so unable to provide code at the moment.
What you'll need to do is subscribe to the scroll event. There isn't a specific one for vertical/horizontal.
Next, you'll need to get some measurements about the current display area. You'll need to measure the window.clientHeight and window.clientWidth.
Next, get window.top and window.left. This will tell you where position of the viewport is, ie if it's greater than 0 then scroll bars have been used.
It's pretty simple math from here to get what you need. If no one else has provided a code example in the next few hours I'll try to do so.
Edit:
A bit further information.
You must capture the scroll event. You also need to store the initial window.top and window.left properties somewhere. Whenever the scroll event happens, do a simple check to see if the current top/left values differ from the stores value.
At this point, if either are different you can trigger your own custom events to indicate vertical or horizontal scrolling. If you are using jQuery, this is very easy. If you are writing js without library assistance, it's easy too but a little more involved.
Do some searches for event dispatching in js.
You can then write any other code you want to subscribe to your custom events without needing to tie them together with method calls.
I wrote a jQuery plugin for you that lets you attach functions to the scrollh event.
See it in action at jsfiddle.net.
/* Enable "scrollh" event jQuery plugin */
(function ($) {
$.fn.enableHScroll = function() {
function handler(el) {
var lastPos = el
.on('scroll', function() {
var newPos = $(this).scrollLeft();
if (newPos !== lastPos) {
$(this).trigger('scrollh', newPos - lastPos);
lastPos = newPos;
}
})
.scrollLeft();
}
return this.each(function() {
var el = $(this);
if (!el.data('hScrollEnabled')) {
el.data('hScrollEnabled', true);
handler(el);
}
});
}
}(jQuery));
It's this easy to use:
$('#container')
.enableHScroll()
.on('scrollh', function(obj, offset) {
$('#info').val(offset);
});
Please note that scroll events come very fast. Even if you click in the scrollbar to jump to a new position, many scroll events are generated. You may want to adjust this code to wait a short time and accumulate all the changes in position during that time before firing the hscroll event.
You can use the same scroll event, but within your handler use the scrollLeft function to see if the scrollbar moved horizontally from the last time the event was fired. If the scrollbar did not move then just return from your handler. Otherwise update your variable to the new position and take action.
You can check if the the x value of the page changes and ignore your y value.
If the x value changes: There is your horizontal scroll.
With page-load, store the initial scrollbar positions for both in two variables (presumably both will be 0). Next, whenever a scroll event occurs, find the scrollleft and scrolltop properties. If the scrollleft property's value is different and scrolltop's value is same as compared to their earlier values, that's a horizontal scroll. Then set the values of the variables to the new scroll values.
No, there is no special event for scroll horizontal (it is for global scroll), but you can try to check the position of content by property .scrollLeft and if it's different from the previous value it means that the user scrolled content horizontally.