jQuery animate scrolling without $.browser & not firing twice - javascript

I found many questions regarding the cross bowser behaviour of $('html, body').animate();, but for some reason I couldn't find an answer for this one:
I want to (finally) remove $.browser, but at the same moment don't want the scroll event to be triggered twice again, which will happen in some browsers if the selector is $('html, body)
// animte page scrolling
pageScroll : function( scrollTo, speed, callback ) {
var rootElem;
scrollTo = scrollTo || 0;
speed = speed || 800;
if ( $.browser.webkit ) {
rootElem = $('body');
} else {
rootElem = $('html');
}
rootElem
.stop()
.animate(
{
scrollTop: scrollTo
}, speed, callback
);
}

I've looked into this for quite a while and haven't found a real solution other than a check on doc ready :
http://codepen.io/anon/pen/yyddER?editors=011
var mainelement;
$('html, body').animate({scrollTop: 1}, 1, function() {
if ($('html').scrollTop()) mainelement = $('html'); // FF and IE
if ($('body').scrollTop()) mainelement = $('body'); // Chrome, Safari and Opera
mainelement.scrollTop(0);
});
Assuming here the content is high enough to create a scrollbar...
Tested on the major browsers and works without a hitch.
Edit - a variation to make sure the trigger to scroll the page back is only acted upon a single time if the browser uses <html> as the main element for overflow :
var mainelement, tested = false;
$('html, body').animate({scrollTop: 1}, 1, function() {
if ($('html').scrollTop()) mainelement = $('html');
else if ($('body').scrollTop()) mainelement = $('body');
if (!tested) {
tested = true;
mainelement.scrollTop(0);
}
});
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
And later came up with this approach as well (which seems to be the most efficient) :
http://codepen.io/anon/pen/NPQNEr?editors=001
var mainelement;
$(window).scrollTop(1);
if ($('html').scrollTop()) mainelement = $('html');
else if ($('body').scrollTop()) mainelement = $('body');
mainelement.scrollTop(0);

This is the code I use in production; works just fine:
function ScrollPage(TheTop) {
TheTop = parseInt(TheTop, 10);
if (!$.isNumeric(TheTop)) {
return;
}
$('html,body').animate({ scrollTop: TheTop }, 400);
}
If the problem is that the event handler is firing twice then it means the function is somehow called twice, and that the bug is therefore elsewhere. On another note, the $.browser() function has been deprecated for a while.

Related

Smooth Scroll Javascript fails on one Mac (Chrome) but works on another Mac (Chrome)

We have a smooth scroll script that fails on some Mac machine (latest chrome).
We tweaked the script a little bit and it appears to be working on all Macs now.
But we have no idea why.
Can someone help?
Before (That fails to smooth scroll correctly for some Mac/Chrome test machine):
var topID = (ua("safari")) ? "body" : "html";
$(".SmoothScroll").unbind().click(function(){
var link = $(this).attr("href");
if(link.charAt(0)=="#" && link.charAt(1)!="") {
var offset = $(link).offset();
var tid = setTimeout(function() {
$(topID).stop().animate({scrollTop: offset.top}, 800, "easeInOutCubic", function() {
location.href = link;
});
}, 10);
return false;
}
});
After fix (works on all machine)
//var topID = (ua("safari")) ? "body" : "html";
var $root = $('html, body');
$(".SmoothScroll").unbind().click(function(){
var link = $(this).attr("href");
if(link.charAt(0)=="#" && link.charAt(1)!="") {
var offset = $(link).offset();
var tid = setTimeout(function() {
$root.stop().animate({scrollTop: offset.top}, 800, "easeInOutCubic", function() {
location.href = link;
});
}, 10);
return false;
}
});
Basically changing $(topID) to $('html, body') appears to working. But why? What is happening and what could be the reason that the previous script fails on some Mac+Chrome?
You probably do not have the same version of safari on both macs

jQuery "Snap To" Effect

I have a specific effect I want for a website I'm building. As you can see in this website, I want the screen to "snap to" the next section after the user scrolls, but only after (not the instant) the scroll event has fired. The reason I don't want to use a plugin like panelSnap is because I
1: Want smaller code and
2. Want the website, when viewed on mobile, to have more of the "instant snap" effect (try reducing the browser size in the website mentioned above). I know I theoretically could try combining two plugins, like panelsnap and scrollify, and activate them appropriately when the browser is a certain width, but I don't know if I want to do that... :(
So all of that said, here's the code:
var scrollTimeout = null;
var currentElem = 0;
var options = {
scrollSpeed: 1100,
selector: 'div.panels',
scrollDelay: 500,
};
$(document).ready(function() {
var $snapElems = $(options.selector);
console.log($($snapElems[currentElem]).offset().top);
function snap() {
if ($('html, body').scrollTop() >= $($snapElems[currentElem]).offset().top) {
if (currentElem < $snapElems.length-1) {
currentElem++;
}
}else{
if (currentElem > 0) {
currentElem = currentElem - 1;
}
}
$('html, body').animate({
scrollTop: $($snapElems[currentElem]).offset().top
}, options.scrollSpeed);
}
$(window).scroll(function() {
if ($(window).innerWidth() > 766) {
if (scrollTimeout) {clearTimeout(scrollTimeout);}
scrollTimeout = setTimeout(function(){snap()}, options.scrollDelay);
}else{
//I'll deal with this later
}
});
});
My problem is that every time the snap function is called, it triggers the scroll event, which throws it into a loop where the window won't stop scrolling between the first and second elements. Here's the poor, dysfunctional site: https://tcfchurch.herokuapp.com/index.html Thank for the help.
You can use a boolean to record when the scroll animation in snap is in progress and prevent your $(window).scroll() event handler from taking any action.
Here's a working example:
var scrollTimeout = null;
var currentElem = 0;
var options = {
scrollSpeed: 1100,
selector: 'div.panels',
scrollDelay: 500,
};
$(document).ready(function() {
var scrollInProgress = false;
var $snapElems = $(options.selector);
console.log($($snapElems[currentElem]).offset().top);
function snap() {
if ($('html, body').scrollTop() >= $($snapElems[currentElem]).offset().top) {
if (currentElem < $snapElems.length-1) {
currentElem++;
}
}else{
if (currentElem > 0) {
currentElem = currentElem - 1;
}
}
scrollInProgress = true;
$('html, body').animate({
scrollTop: $($snapElems[currentElem]).offset().top
}, options.scrollSpeed, 'swing', function() {
// this function is invoked when the scroll animate is complete
scrollInProgress = false;
});
}
$(window).scroll(function() {
if (scrollInProgress == false) {
if ($(window).innerWidth() > 766) {
if (scrollTimeout) {clearTimeout(scrollTimeout);}
scrollTimeout = setTimeout(function(){snap()}, options.scrollDelay);
}else{
//I'll deal with this later
}
}
});
});
The variable scrollInProgress is set to false by default. It is then set to true when the scroll animate starts. When the animate finishes, scrollInProgress is set back to false. A simple if statement at the top of your $(window).scroll() event handler prevents the handler from taking any action while the animate scroll is in progress.
Have you considered using the well known fullPage.js library for that? Check out this normal scroll example. The snap timeout is configurable through the option fitToSectionDelay.
And nothing to worry about the size... it is 7Kb Gzipped!
I know I theoretically could try combining two plugins, like panelsnap and scrollify, and activate them appropriately when the browser is a certain width, but I don't know if I want to do that
fullPage.js also provides responsiveWidth and responsiveHeight options to turn it off under certain dimensions.

jQuery mouse scroll script speed will not change

had a google....
Tried changing my website scroll settings & nothing is happening.
Anyone have a write up or table on mouse scroll jQuery scripts and functions?
(Caches were cleared, cross browser test etc.)
jQuery(window).load(function(){
if(checkBrowser() == 'Google Chrome' && device.windows()){
if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel = document.onmousewheel = wheel;
var time = 330;
var distance = 300;
function wheel(event) {
if (event.wheelDelta) delta = event.wheelDelta / 90;
else if (event.detail) delta = -event.detail / 3;
handle();
if (event.preventDefault) event.preventDefault();
event.returnValue = false;
}
function handle() {
jQuery('html, body').stop().animate({
scrollTop: jQuery(window).scrollTop() - (distance * delta)
}, time);
}
}
});
function checkBrowser(){
var ua = navigator.userAgent;
if (ua.search(/MSIE/) > 0) return 'Internet Explorer';
if (ua.search(/Firefox/) > 0) return 'Firefox';
if (ua.search(/Opera/) > 0) return 'Opera';
if (ua.search(/Chrome/) > 0) return 'Google Chrome';
if (ua.search(/Safari/) > 0) return 'Safari';
if (ua.search(/Konqueror/) > 0) return 'Konqueror';
if (ua.search(/Iceweasel/) > 0) return 'Debian Iceweasel';
if (ua.search(/SeaMonkey/) > 0) return 'SeaMonkey';
if (ua.search(/Gecko/) > 0) return 'Gecko';
return 'Search Bot';
}
The script looks a bit outdated. The .load() function isn't used like that anymore and browser sniffing is deprecated as well. An approach with the mousewheel plugin (a real gem) would be more reliable and future proof. Here's a script that uses it, making the function itself quite compact :
http://codepen.io/anon/pen/KpPdmX?editors=001
jQuery(window).on('load', function() {
var time = 330;
var distance = 300;
jQuery(this).mousewheel(function(turn, delta) {
jQuery('html, body').stop().animate({
scrollTop: jQuery(window).scrollTop()-(distance*delta)
}, time);
return false;
});
});
// mousewheel.js can be placed here, outside of function scope
It needs a bit of extra script with that plugin but it is well worth it. There also is a wheel event but unfortunately this is still not supported by Opera. In any case, more code would be needed to normalise the delta of the mousewheel turns (this is where mousewheel.js is at it's best).
I'm guessing the $ character is reserved on the web page but if not, the jQuery references could be replaced with it. By the way - you might want to check which version of jQuery is linked to on the site... if there are any other scripts depending on deprecated features (not that there are too many), some things might stop functioning correctly when it is updated. The .on method was introduced in version 1.8 - if you'd like to stick with an older version the above script would need a minor rewrite.
add this function in you script tag
and add data-scroll-speed="10" in your body tag. you can adjust the scroller speed of body
$(function () {
var boxes = $('[data-scroll-speed]'),
$window = $(window);
$window.on('scroll', function () {
var scrollTop = $window.scrollTop();
boxes.each(function () {
var $this = $(this),
scrollspeed = parseInt($this.data('scroll-speed')),
val = -(scrollTop / scrollspeed);
$this.css('transform', 'translateY(' + val + 'px)');
});
});
})
example: fiddled here
check weather this is what you wanted

Dynamic divs and scrollTop

I have a single page site:
http://chiaroscuro.telegraphbranding.com/
Each section is dynamically sized based on the user's window. I'm trying to figure out how to have a jQuery smooth scroll function scroll to the top of each section when the link is clicked. It is working great for the first section, funding areas, where I just used a simple offset().top, but the others are not working because they don't know how far to scroll because the window size is always different.
I've been trying to get offset() or position() to work, but no dice. I appreciate any advice.
Here's my jQuery:
`
$(document).ready(function () {
var slowScrollFunding = $('#funding-areas').offset().top;
var slowScrollAbout = $('#about-us').offset().top;
var slowScrollProjects = $('#our-projects').offset().top + 600;
panelOpen = true;
$('#anchor-funding-areas').click(function(event) {
event.preventDefault();
if(panelOpen == true) {
$('#slide-panel-content').stop(true, true).animate({height: '0px'}, 600, function() {
$('#panel-content-container').hide();
$('.scrollableArea').css('z-index', '11');
// Scroll down to 'slowScrollTop'
$('html, body, #home-wrap').animate({scrollTop:slowScrollFunding}, 1000);
panelOpen = false;
});
}else{
$('html, body, #home-wrap').animate({scrollTop:slowScrollFunding}, 1000);
};
});
$('#anchor-aboutus').click(function(event) {
event.preventDefault();
if(panelOpen == true) {
$('#slide-panel-content').stop(true, true).animate({height: '0px'}, 600, function() {
$('#panel-content-container').hide();
$('.scrollableArea').css('z-index', '11');
// Scroll down to 'slowScrollTop'
$('html, body, #aboutus-wrap').animate({scrollTop:slowScrollAbout}, 1000);
panelOpen = false;
});
}else{
$('html, body, #home-wrap').animate({scrollTop:slowScrollAbout}, 1000);
};
});
$('#anchor-ourprojects').click(function(event) {
event.preventDefault();
if(panelOpen == true) {
$('#slide-panel-content').stop(true, true).animate({height: '0px'}, 600, function() {
$('#panel-content-container').hide();
$('.scrollableArea').css('z-index', '11');
// Scroll down to 'slowScrollTop'
$('html, body, #home-wrap').animate({scrollTop:slowScrollProjects}, 1000);
panelOpen = false;
});
}else{
$('html, body, #home-wrap').animate({scrollTop:slowScrollProjects}, 1000);
};
});
$('#header-logo').add('.homelink').click(function() {
if(panelOpen == false) {
$('.scrollableArea').css('z-index', '0');
$('#panel-content-container').show();
$('#slide-panel-content').stop(true, true).animate({height: '389px'}, 600, function() {
// Scroll down to 'slowScrollTop'
panelOpen = true;
});
};
});
});
`
$.offset and $.position can be a little unreliable, especially if you have lots of complicated layouts going on - as your page does. What I've used in the past is the following trick:
var de = document.documentElement ? document.documentElement : document.body;
var elm = $('get_your_anchor_element').get(0);
var destScroll, curScroll = de.scrollTop;
/// check for the function scrollIntoView
if ( elm.scrollIntoView ) {
/// get the browser to scrollIntoView (this wont show up yet)
elm.scrollIntoView();
/// however the new scrollTop is calculated
destScroll = de.scrollTop;
/// then set the scrollTop back to where we were
de.scrollTop = curScroll;
/// you now have your correct scrollTop value
$(de).animate({scrollTop:destScroll});
}
else {
/// most browsers support scrollIntoView so I didn't bother
/// with a fallback, but you could just use window.location
/// and jump to the anchor.
}
The above can occur on the click event. The only thing that needs to be improved is that different browsers scroll on different base elements (body or html). When I used this I had my own element that was scrollable so I didn't need to work out which one the agent was using... When I get a second I'll see if I can find a good bit of code for detecting the difference.
The above has worked in all the modern browsers I've tested (Firefox, Safari, Chrome) however I didn't need to support Internet Explorer so I'm not sure with regard to that.
update:
I'm not quite sure what is going on with your implementation - it is possible that the page is so heavy with content that you actually can see the .scrollIntoView() happening - this has never been my experience, but then I didn't have so much going on on-screen. With that in mind, I've implemented a bare bones system that I would advise you use and build each extra part you need into it:
http://pebbl.co.uk/stackoverflow/13035183.html
That way you know you have a working system to start with, and will easily detect what it is that stops it from working. With regards to chiaro.js your implementation seems to be ok - if a little exploded over many different areas of the file - however this part is slightly erroneous:
$('#anchor-aboutus').click(function() {
event.preventDefault();
if(panelOpen == true) {
$('#slide-panel-content')
.stop(true, true)
.animate({height: '0px'}, 600, function() {
$('#panel-content-container').hide();
$('.scrollableArea').css('z-index', '11');
elm.scrollIntoView(true)
.animate({scrollTop:destScroll}, 1000);
panelOpen = false;
});
}else{
elm.scrollIntoView(true).animate({scrollTop:destScroll});
};
});
In the code above you will only get the correct value of destScroll if panelOpen === true. Ahh, actually I've also spotted another problem - which will explain why it's not working:
elm.scrollIntoView(true)
.animate({scrollTop:destScroll}, 1000);
The above code is mixing pure JavaScript and jQuery, the elm var is a normal DOM element (this supports the scrollIntoView method). But you are then attempting to chain the animate method of jQuery into the mix - you should also be triggering the animate method on the element responsible for the scrollbar. What you should use is as follows:
$('#anchor-aboutus').click(function(e) {
var currentScroll, destScroll;
e.preventDefault();
if(panelOpen == true) {
$('#slide-panel-content')
.stop(true, true)
.animate({height: '0px'}, 600, function() {
$('#panel-content-container').hide();
$('.scrollableArea').css('z-index', '11');
currentScroll = de.scrollTop;
elm.scrollIntoView(true);
destScroll = de.scrollTop;
de.scrollTop = currentScroll;
$(de).animate({scrollTop:destScroll}, 1000);
panelOpen = false;
});
}else{
currentScroll = de.scrollTop;
elm.scrollIntoView(true);
destScroll = de.scrollTop;
de.scrollTop = currentScroll;
$(de).animate({scrollTop:destScroll}, 1000);
};
});
However, what you will also need to do is make sure your de element points to the right element - either html or body depending on the browser - for this you can use this:
var de;
/// calculate which element is the scroller element
$('body, html').each(function(){
if ( this.scrollHeight > this.offsetHeight ) {
de = this;
return false;
}
});
alert( $(de).is('body') ) /// will be true for Chrome, false for Firefox.
You will need to use this code in place of the following code:
var de = document.documentElement ? document.documentElement : document.body;
The reason for changing the code you were using is as follows:
/// store the current scroll position from the de element
currentScroll = de.scrollTop;
/// get the browser to do the scrollTo calculation
elm.scrollIntoView(true);
/// store where the browser scrolled to behind the scenes
destScroll = de.scrollTop;
/// reset our scroll position to where we were before scrollIntoView()
/// if you don't reset then the animation will happen instantaneously
/// because that is what scrollIntoView does.
de.scrollTop = currentScroll;
/// wrap the normal dom element de with jquery and then animate
$(de).animate({scrollTop:destScroll}, 1000);

Event when user stops scrolling

I'd like to do some fancy jQuery stuff when the user scrolls the page. But I have no idea how to tackle this problem, since there is only the scroll() method.
Any ideas?
You can make the scroll() have a time-out that gets overwritten each times the user scrolls. That way, when he stops after a certain amount of milliseconds your script is run, but if he scrolls in the meantime the counter will start over again and the script will wait until he is done scrolling again.
Update:
Because this question got some action again I figured I might as well update it with a jQuery extension that adds a scrollEnd event
// extension:
$.fn.scrollEnd = function(callback, timeout) {
$(this).on('scroll', function(){
var $this = $(this);
if ($this.data('scrollTimeout')) {
clearTimeout($this.data('scrollTimeout'));
}
$this.data('scrollTimeout', setTimeout(callback,timeout));
});
};
// how to call it (with a 1000ms timeout):
$(window).scrollEnd(function(){
alert('stopped scrolling');
}, 1000);
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<div style="height: 200vh">
Long div
</div>
Here is a simple example using setTimeout to fire a function when the user stops scrolling:
(function() {
var timer;
$(window).bind('scroll',function () {
clearTimeout(timer);
timer = setTimeout( refresh , 150 );
});
var refresh = function () {
// do stuff
console.log('Stopped Scrolling');
};
})();
The timer is cleared while the scroll event is firing. Once scrolling stops, the refresh function is fired.
Or as a plugin:
$.fn.afterwards = function (event, callback, timeout) {
var self = $(this), delay = timeout || 16;
self.each(function () {
var $t = $(this);
$t.on(event, function(){
if ($t.data(event+'-timeout')) {
clearTimeout($t.data(event+'-timeout'));
}
$t.data(event + '-timeout', setTimeout(function () { callback.apply($t); },delay));
})
});
return this;
};
To fire callback after 100ms of the last scroll event on a div (with namespace):
$('div.mydiv').afterwards('scroll.mynamespace', function(e) {
// do stuff when stops scrolling
$(this).addClass('stopped');
}, 100
);
I use this for scroll and resize.
Here is another more generic solution based on the same ideas mentioned:
var delayedExec = function(after, fn) {
var timer;
return function() {
timer && clearTimeout(timer);
timer = setTimeout(fn, after);
};
};
var scrollStopper = delayedExec(500, function() {
console.log('stopped it');
});
document.getElementById('box').addEventListener('scroll', scrollStopper);
I had the need to implement onScrollEnd event discussed hear as well.
The idea of using timer works for me.
I implement this using JavaScript Module Pattern:
var WindowCustomEventsModule = (function(){
var _scrollEndTimeout = 30;
var _delayedExec = function(callback){
var timer;
return function(){
timer && clearTimeout(timer);
timer = setTimeout(callback, _scrollEndTimeout);
}
};
var onScrollEnd = function(callback) {
window.addEventListener('scroll', _delayedExec(callback), false);
};
return {
onScrollEnd: onScrollEnd
}
})();
// usage example
WindowCustomEventsModule.onScrollEnd(function(){
//
// do stuff
//
});
Hope this will help / inspire someone
Why so complicated? As the documentation points out, this http://jsfiddle.net/x3s7F/9/ works!
$('.frame').scroll(function() {
$('.back').hide().fadeIn(100);
}
http://api.jquery.com/scroll/.
Note: The scroll event on Windows Chrome is differently to all others. You need to scroll fast to get the same as result as in e.g. FF. Look at https://liebdich.biz/back.min.js the "X" function.
Some findings from my how many ms a scroll event test:
Safari, Mac FF, Mac Chrome: ~16ms an event.
Windows FF: ~19ms an event.
Windows Chrome: up to ~130ms an event, when scrolling slow.
Internet Explorer: up to ~110ms an event.
http://jsfiddle.net/TRNCFRMCN/1Lygop32/4/.
There is no such event as 'scrollEnd'. I recommend that you check the value returned by scroll() every once in a while (say, 200ms) using setInterval, and record the delta between the current and the previous value. If the delta becomes zero, you can use it as your event.
There are scrollstart and scrollstop functions that are part of jquery mobile.
Example using scrollstop:
$(document).on("scrollstop",function(){
alert("Stopped scrolling!");
});
Hope this helps someone.
The scrollEnd event is coming. It's currently experimental and is only supported by Firefox. See the Mozilla documentation here - https://developer.mozilla.org/en-US/docs/Web/API/Document/scrollend_event
Once it's supported by more browsers, you can use it like this...
document.onscrollend = (event) => {
console.log('Document scrollend event fired!');
};
I pulled some code out of a quick piece I cobbled together that does this as an example (note that scroll.chain is an object containing two arrays start and end that are containers for the callback functions). Also note that I am using jQuery and underscore here.
$('body').on('scroll', scrollCall);
scrollBind('end', callbackFunction);
scrollBind('start', callbackFunction);
var scrollCall = function(e) {
if (scroll.last === false || (Date.now() - scroll.last) <= 500) {
scroll.last = Date.now();
if (scroll.timeout !== false) {
window.clearTimeout(scroll.timeout);
} else {
_(scroll.chain.start).each(function(f){
f.call(window, {type: 'start'}, e.event);
});
}
scroll.timeout = window.setTimeout(self.scrollCall, 550, {callback: true, event: e});
return;
}
if (e.callback !== undefined) {
_(scroll.chain.end).each(function(f){
f.call(window, {type: 'end'}, e.event);
});
scroll.last = false;
scroll.timeout = false;
}
};
var scrollBind = function(type, func) {
type = type.toLowerCase();
if (_(scroll.chain).has(type)) {
if (_(scroll.chain[type]).indexOf(func) === -1) {
scroll.chain[type].push(func);
return true;
}
return false;
}
return false;
}

Categories

Resources