I'm using SlickGrid.js library and it is excellent!
Only major problem right now is with Internet Explorer (confirmed in 9, 10, and 11), but the standards compliant browsers like Chrome and FF work fine.
Problem: When grid is scrolled and then hidden and then re-shown in IE the scroll position is reset to top of grid, and the viewport/data is either cut off or completely hidden (depending on scroll amount).
Here is a fiddle that demonstrates the SlickGrid.js IE bug (using the author's simple example 1):
http://jsfiddle.net/crwxoc17/1/
Anybody have a generic fix for this or patch to slick grid?
I can call grid.resizeCanvas() to sorta fix the issue, but it resets scrollbar to top and it's very annoying to do this for every single grid just to deal with Internet Explorer.
Semi-working fix, but still screws up the scrolltop:
function onShowGrid1() { grid.resizeCanvas(); }
(Reviewing JS code now, but I have not yet confirmed whether the bug is Microsoft's or SlickGrid's)
This issue applies to any element in IE with overflow set to scroll or auto and whose visibility is toggled. There's a simple example here: https://jsfiddle.net/qkhxL6r8/4/
That said, if you'd like the scrollTop position to be preserved you could extend SlickGrid or create a wrapper a class that subscribes to the onScroll event, records the scrollTop value, and sets it on the viewport element when showing or hiding the grid. I modified your example code as a proof of concept here: http://jsfiddle.net/h9cu2cmp/4/
var lastScrollTop;
var scrollTimeout;
function updateScrollTop(e, args){
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(function(){
lastScrollTop = args.scrollTop;
}, 30);
}
//...
grid.onScroll.subscribe(updateScrollTop);
$('body').on('click', '.toggle-button', function(){
$("#myGrid").toggle();
if(lastScrollTop !== undefined){
$("#myGrid").find('.slick-viewport').get(0).scrollTop = lastScrollTop;
}
});
If you're using a remote data provider you can trigger ensureData for the updated scrollTop with grid.onViewportChanged.notify()
Related
I needed JavaScript for automatic scroll down in a smooth/slow manner.
I have a form with many radio buttons which is quite similar to survey form.
I used script from the below mentioned link. This link works fine smoothly for scrolling downwards.
But problem comes when you reach the bottom of page and cannot scroll upwards.
I am not so good in JavaScript. Does anyone here has solution or fix to this?
Link to Stack Overflow thread:
Slow down onclick window.scrollBy
function scrollByRate(y, rate)
{
//calculate the scroll height
var scrolling = Math.max( document.getElementsByTagName('html')[0].scrollTop, document.body.scrollTop);
//save the old value as "static" var
arguments.callee.tmp = arguments.callee.tmp || scrolling + y;
//make a little scrolling step
window.scrollBy(0, (arguments.callee.tmp - scrolling) / rate);
//are we arrived? if no, keep going recursively, else reset the static var
if(arguments.callee.tmp - scrolling > 100) setTimeout(function() { scrollByRate(y, rate); }, 10);
else arguments.callee.tmp = undefined;
}
Scrolling down slowly
I can see your approach having a negative impact on performance. It looks like the browser will block until the target scroll destination has been reached.
My suggestion is to use what is out there for smooth scrolling already. The scrollTo method of any scrollable pane (e.g. window object but also a scrollable div for example) has a "behavior" property that you can set to "smooth", e.g.:
window.scrollTo({
top: 100,
left: 100,
behavior: 'smooth'
});
Keep in mind that the compatibility at the time of writing is limited to Chrome, Firefox, Edge and Opera which means you'll have problems on Internet Explorer and Safari (so all Apple products). I myself use a polyfill to get the smooth scrolling back on my application, this one in particular: https://github.com/iamdustan/smoothscroll
In all versions prior to iOS8, I was able to prevent the iPhone keyboard from pushing up (and destroying) my html/css/js view when the keyboard appeared by the following method:
$('input, select').focus(function(event) {
$(window).scrollTop(0);
// or via the scrollTo function
});
Since iOS8, this no longer works. One workaround is to place this code within a setTimeOut
setTimeout(function() { $(window).scrollTop(0); }, 0);
But it only makes the view do a jerky motion as the view is initially pushed up by iOS, then dragged back down by my js code. preventDefault and stopPropagation does not help either.
I've tried everything available on the web of course including my own solution posted here: How to prevent keyboard push up webview at iOS app using phonegap but so far, nothing works for iOS8. Any clever ideas on how to prevent the keyboard in iOS8 to push/move the view?
Try position:fixed on body, and/or wrap content in a div and position:fixed on it as well.
There are some options :
Make listener on your ios code, to move the screen up along with the keyboard height, so everything move up along with the keyboard, then your design save.
Make your css design responsive. Then no problem with change height, it will be scrollable inside your webview.
When keyboard pushes up view in iOS, a scroll event is triggered ($(window).scrollTop() is changed). You can put $(window).scrollTop(0) inside the scroll event handler. To prevent the jerky motion, set opacity to 0 during scrolling. Related codes may look like this:
function forceScrollTop() {
var scrollTop = $(window).scrollTop();
if (scrollTop != 0) {
$(window).scrollTop(0);
$(selector).css('opacity', 1);
$(window).off('scroll', forceScrollTop);
}
}
// when an input is focused ...
$(selector).css('opacity', 0);
$(window).on('scroll', forceScrollTop);
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.
]
I am using the most wonderful javascript tool iScroll4 http://cubiq.org/iscroll-4 on a mobile website for iOS and Android. Here is what my layout looks like:
The horizontally scroll-able area is making use of iScroll4 with the following settings:
var myScroll = new iScroll('frame', { hScrollbar: false, vScrollbar: false, vScroll: false })
The horizontal scrolling part works great. This issue is what happens when a user attempts to scroll up or down the page placing their finger on the horizontal scrolling area. So I need native vertical scrolling, and iScroll horizontal scrolling on the same area.
What I have tried so far:
Removing e.preventDefault() in the iScroll code (allows for native scrolling, but in BOTH axes).
Removing e.preventDefault() and then disabling horizontal scrolling page wide with this:
var touchMove;
document.ontouchstart = function(e){
touchMove = e.touches[0];
}
document.ontouchmove = function(e){
var theTouch = e.touches[0] || e.changedTouches[0];
var Xer = rs(touchMove.pageX - theTouch.pageX).toPos();
var Yer = rs(touchMove.pageY - theTouch.pageY).toPos();
touchMove = theTouch;
if(Yer > Xer){ e.preventDefault(); }
}
which seems to do nothing. How can I allow for native vertical scrolling in the horizontal scrolling area, without loosing the horizontal scrolling of iScroll? I am really stumped here. Thanks in advance.
(just for the record rs(foo).toPos() is a function that makes foo a positive number regardless of its value).
If you would like to achieve the effect described by Fresheyeball without hacking the core, and without changing from iScroll to swipeview, then iScroll 4 does offer you its event listeners to work with.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollMove: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
} else {
// delegate the scrolling to window object
window.scrollBy( 0, -this.distY );
}
},
});
By doing so, the onBeforeScrollMove-Handler checks whether the scroll direction seems to be horizontal, and then prevents the default handler, thus effectively locking the scroll action to the X-Axis (try commenting it out, you'll see the difference). Otherwise, if the scroll direction needs to be vertical, we make the browser scroll via the window.scrollBy() method. This is not exactly native, but does the job just fine.
Hope that helps
Lukx
[EDIT]
My original solution, which didn't use window.scrollBy() ,did not work on slower Samsung phones, which is why I needed to adapt the answer.
Suggested edit to #Lukx's excellent solution. New versions of iScroll4 place the e.preventDefault() in onBeforeScrollMove which can be overridden. By placing the if block into this option, default is not prevented for vertical scrolling, and vertical can scroll natively.
myScroll = new iScroll('scrollpanel', {
// other options go here...
vScroll: false,
onBeforeScrollStart: function ( e ) {
if ( this.absDistX > (this.absDistY + 5 ) ) {
// user is scrolling the x axis, so prevent the browsers' native scrolling
e.preventDefault();
}
},
});
With iscroll 5, you can set eventPassthrough: true to achieve this. See http://iscrolljs.com/#configuring
OLD ANSWER
UPDATE a special pluggin has been written just to address this problem:
http://cubiq.org/swipeview
I found a way!
add a variable to the top of the document: if android is 15 and is iOS is 3
var scrollTolerance = ( rs().isDevice('android') )?15:3;
disable the original e.preventDefault(); for scrolling. This is under onBeforeScrollStart:
the in _move just under
timestamp = e.timeStamp || Date.now();
add this line
if( Math.sqrt(deltaX*deltaX) > scrollTolerance){e.preventDefault();}
What this does is the following:
the scrollTolerance sets, you guessed it, a tolerance for finger direction. We don't want to demand a perfect vertical angle to get the up down native scroll. Also iOS does not detect properly and will never be higher than 4 for some reason so I used 3. Then we disable iScroll's standard e.preventDefault(); which prevents native vertical scrolling on our bi-scrollable area. Then we insert e.preventDefault(); only upon move and based on finger direction from tolerance.
This does not work perfect. But is acceptable and works on iOS and Android. If anyone sees better ways please post here. This is something I (and assume others) need to use regularly, and we should have a perfect rock solid solution.
Thanks.
Please test this solution from Adam.
https://gist.github.com/hotmeteor/2231984
I think the trick is to add the check in onBeforeScrollMove. First get the initial touch position in onBeforeScrollTouchStart and then in onBeforeScrollMove check the new position and then disable the required scroll based on the difference.
iScroll 5 supports native scrolling of any axis!
http://iscrolljs.com/
on iScroll5 just set eventPassthrougt to true. That fixes it.
Ahoy!
I've built a little script to check the size of the left-hand margin on page load, resize a div there to fill it, and change the header div to float next to it.
Here's the code:
function buildHeader() {
var containerMarginLeft = $(".container_16:not(:first)").css("margin-left");
var headerHeight = $("#header").height();
$("#stripe").width(containerMarginLeft).height(headerHeight).css("float", "left");
$(".container_16:first").css("float", "left");
$("#header").css("margin-left", 0).width(950);
}
$(document).ready(function(){
// Manipulate layout for the first time
buildHeader();
// Manipulate layout when window is resized
var resizeTimer = null;
$(window).bind('resize', function() {
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(buildHeader, 100);
});
});
And the demonstration is here: http://robertmay.me.uk/mockups/plane.html (it creates the line that stretches on the left).
Now, it works in webkit browsers. It doesn't work in Mozilla, and I've not even tried it in IE.
Anyone have any ideas as to why it doesn't seem to work in Mozilla? I have a feeling it might have something to do with the CSS.
$(".container_16:not(:first)").css("margin-left");
This line gives a result of '0px' in Firefox regardless of how wide the window gets. However, Firebug Lite in Safari shows this value as changing depending on the width of the window.
The problem seems to be with the .css('margin-left') part of the statement, since $(".container_16:not(:first)") returns the same element in both browsers. Indeed, Firebug in Firefox shows the Computed Style for this element as having '0px' for marginLeft and marginRight, but this is non-zero in Safari.
As expected, changing from 'margin-left' to 'marginLeft' makes no difference, nor does accessing the attribute directly, like $(".container_16:not(:first)")[0].style.marginLeft, because Firefox is computing it wrong in the first place.
Sorry I don't have an answer, but hopefully this will lead you in the right direction. For me though I would try to align the layout using just CSS, resorting to JavaScript fixes only as a last resort.