Dynamic divs and scrollTop - javascript

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);

Related

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.

Too many scroll events for smooth scrolling

Hello there I've been trying to find a fix for the many scroll events firing on one scroll. This is the only thing close to working for me so far. I want to smoothscroll between two divs (#boxes and #header) I want to use the scroll bar to trigger this smooth scroll and not a button. Any suggestions on how to only take one scroll event? I also used solutions based from prev stackoverflow questions. I used my own locator instead of offsets because thats also unreliable
$(window).scroll(function () {
if (timer) {
window.clearTimeout(timer);
}
timer = window.setTimeout(function () {
if (locator == 0) {
id = $("#boxes");
locator = 1;
} else if (locator = 1) {
id = $("#header");
locator = 0;
}
// target element
var $id = $(id);
if ($id.length === 0) {
return;
}
// top position relative to the document
var pos = $id.offset().top;
// animated top scrolling
$('html, body').animate({scrollTop: pos}, 1500, function () {
$('html, body').clearQueue();
$('html, body').stop();
});
}, 2);
});
So, to be clear, you want any minor scroll event to scroll between one item and the other? Note that when a user scrolls, there is a "momentum" that the browser implements, and you'll be battling with that.
Regardless: You don't need to wrap this in a setTimeout. Right now, your javascript is creating a new setTimeout function that is being fired every 2ms. Scroll events occur with every pixel of movement in the scroll, so if you scroll 100px, you're going to be firing 100 times every 2ms. (That's 50,000 times).
Instead, have a a variable (isScrolling) track the state, so, if you're in the middle of scrolling, the function won't fire.
var isScrolling = false;
var locator = 0;
$(window).scroll(function () {
if (isScrolling) return false;
if (locator == 0) {
id = $("#boxes");
locator = 1;
} else if (locator = 1) {
id = $("#header");
locator = 0;
}
// target element
var $id = $(id);
if ($id.length === 0) {
return;
}
// top position relative to the document
var pos = $id.offset().top;
// animated top scrolling
isScrolling = true;
$('html, body').animate({scrollTop: pos}, 1500, function () {
$('html, body').clearQueue();
$('html, body').stop();
isScrolling = false;
});
});
Here's a JSbin: http://jsbin.com/jugefup/edit?html,css,js,output

jQuery animate scrolling without $.browser & not firing twice

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.

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

Adding listener for position on screen

I'd like to set something up on my site where when you scroll within 15% of the bottom of the page an element flyouts from the side... I'm not sure how to get started here... should I add a listener for a scroll function or something?
I'm trying to recreate the effect at the bottom of this page: http://www.nytimes.com/2011/01/25/world/europe/25moscow.html?_r=1
update
I have this code....
console.log(document.body.scrollTop); //shows 0
console.log(document.body.scrollHeight * 0.85); //shows 1038.7
if (document.body.scrollTop > document.body.scrollHeight * 0.85) {
console.log();
$('#flyout').animate({
right: '0'
},
5000,
function() {
});
}
the console.log() values aren't changing when I scroll to the bottom of the page. The page is twice as long as my viewport.
[Working Demo]
$(document).ready(function () {
var ROOT = (function () {
var html = document.documentElement;
var htmlScrollTop = html.scrollTop++;
var root = html.scrollTop == htmlScrollTop + 1 ? html : document.body;
html.scrollTop = htmlScrollTop;
return root;
})();
// may be recalculated on resize
var limit = (document.body.scrollHeight - $(window).height()) * 0.85;
var visible = false;
var last = +new Date;
$(window).scroll(function () {
if (+new Date - last > 30) { // more than 30 ms elapsed
if (visible && ROOT.scrollTop < limit) {
setTimeout(function () { hide(); visible = false; }, 1);
} else if (!visible && ROOT.scrollTop > limit) {
setTimeout(function () { show(); visible = true; }, 1);
}
last = +new Date;
}
});
});
I know this is an old topic, but the above code that received the check mark was also triggering the $(window).scroll() event listener too many times.
I guess twitter had this same issue at one point. John Resig blogged about it here: http://ejohn.org/blog/learning-from-twitter/
$(document).ready(function(){
var ROOT = (function () {
var html = document.documentElement;
var htmlScrollTop = html.scrollTop++;
var root = html.scrollTop == htmlScrollTop + 1 ? html : document.body;
html.scrollTop = htmlScrollTop;
return root;
})();
// may be recalculated on resize
var limit = (document.body.scrollHeight - $(window).height()) * 0.85;
var visible = false;
var last = +new Date;
var didScroll = false;
$(window).scroll(function(){
didScroll = true;
})
setInterval(function(){
if(didScroll){
didScroll = false;
if (visible && ROOT.scrollTop < limit) {
hideCredit();
visible = false;
} else if (!visible && ROOT.scrollTop > limit) {
showCredit();
visible = true;
}
}
}, 30);
function hideCredit(){
console.log('The hideCredit function has been called.');
}
function showCredit(){
console.log('The showCredit function has been called.');
}
});
So the difference between the two blocks of code is when and how the timer is called. In this code the timer is called off the bat. So every 30 millaseconds, it checks to see if the page has been scrolled. if it's been scrolled, then it checks to see if we've passed the point on the page where we want to show the hidden content. Then, if that checks true, the actual function then gets called to show the content. (In my case I've just got a console.log print out in there right now.
This seems to be better to me than the other solution because the final function only gets called once per iteration. With the other solution, the final function was being called between 4 and 5 times. That's got to be saving resources. But maybe I'm missing something.
bad idea to capture the scroll event, best to use a timer and every few milliseconds check the scroll position and if in the range you need then execute the necessary code for what you need
Update: in the past few years the best practice is to subscribe to the event and use a throttle avoiding excessive processing https://lodash.com/docs#throttle
Something like this should work:
$(window).scroll(function() {
if (document.body.scrollTop > document.body.scrollHeight * 0.85) {
// flyout
}
});
document.body.scrollTop may not work equally well on all browsers (it actually depends on browser and doctype); so we need to abstract that in a function.
Also, we need to flyout only one time. So we can unbind the event handler after having flyed out.
And we don't want the flyout effect to slow down scrolling, so we will run our flytout function out of the event loop (by using setTimeout()).
Here is the final code:
// we bind the scroll event, with the 'flyout' namespace
// so we can unbind easily
$(window).bind('scroll.flyout', (function() {
// this function is defined only once
// it is private to our event handler
function getScrollTop() {
// if one of these values evaluates to false, this picks the other
return (document.documentElement.scrollTop||document.body.scrollTop);
}
// this is the actual event handler
// it has the getScrollTop() in its scope
return function() {
if (getScrollTop() > (document.body.scrollHeight-$(window).height()) * 0.85) {
// flyout
// out of the event loop
setTimeout(function() {
alert('flyout!');
}, 1);
// unbind the event handler
// so that it's not call anymore
$(this).unbind('scroll.flyout');
}
};
})());
So in the end, only getScrollTop() > document.body.scrollHeight * 0.85 is executed at each scroll event, which is acceptable.
The flyout effect is ran only one time, and after the event has returned, so it won't affect scrolling.

Categories

Resources