I am trying to add an scrollTo command to the following accordion code.
The problem is the slideup() command changes the screen then the position is detected wrong.
$handlers.removeClass('active');
$panels.slideUp();
$(this).addClass('active').parent().find('.accordion-container').slideDown();
var position = $(this).position();
window.scrollTo(position.left, position.top - 110);
The above code works correct if the all accordion containers are closed. If one of the containers are open and it is closed with the slideup() command then the position command fails and then the screen is moved to completely wrong position.
Full function
!(function($){
$.fn.Accordion = function(options){
var settings = $.extend({
hidefirst: 0
}, options);
return this.each(function(){
var $items = $(this).find('>div');
var $handlers = $items.find('.toggler');
var $panels = $items.find('.accordion-container');
if( settings.hidefirst === 1 )
{
$panels.hide().first();
}
else
{
$handlers.first().addClass('active');
$panels.hide().first().slideDown();
}
$handlers.on('click', function(){
if( $(this).hasClass('active') )
{
$(this).removeClass('active');
$panels.slideUp();
}
else
{
$handlers.removeClass('active');
$panels.slideUp();
$(this).addClass('active').parent().find('.accordion-container').slideDown();
var position = $(this).position();
window.scrollTo(position.left, position.top - 110);
}
event.preventDefault();
});
});
};
})(jQuery);
If you cannot use callbacks for some reason, you could also add a timeout of lets say 100ms and scroll after it has completed.
That way the dom will probably be already built when the scrolling kicks in.
But I would recommend using the callback of slideDown.
It takes a function 'complete' as seen here: http://api.jquery.com/slidedown/
So change your code to something like this:
$(this).addClass('active');
var accCont = $(this).parent().find('.accordion-container');
accCont.slideDown(200, function() {
var position = accCont.position();
window.scrollTo(position.left, position.top - 110);
});
It may not be 100% correct for your use-case, but I guess you get the point.
Related
I have a custom function that I want to make dynamic as much as possible because, as you can see right now, whenever it gets to the trigger point, the two lines of text both come in at same time.
I want the second line of text to wait a bit (maybe halfway through the first line of text's animation) before it starts firing up the function.
My solution is to set parameters when creating the function to pass in different values for each new instance of the function and probably put a delay or queue between them to stop them from executing at the same time.
i.e. $(document).scroll(revealTextOnScroll(paragraph1, overlay1);
$(document).delay(2000).scroll(revealTextOnScroll(paragraph2, overlay2);
Is this possible?
https://codepen.io/alexyap/pen/jmQqvQ?editors=0010
$(document).ready(function(){
var text = $("p");
var overlay = $(".someYellowOverlay");
var flag = false;
$(document).scroll(revealTextOnScroll);
function revealTextOnScroll() {
var scrollPos = $(document).scrollTop();
if (scrollPos >= 250 && flag === false) {
flag = true;
revealText();
} else if (scrollPos < 250) {
flag = false;
$(text).css("display", "none");
$(overlay).css("transform-origin", "left center");
}
function revealText() {
$(overlay).addClass("someAnimation").delay(250).queue(function(next){
$(this).css("transform-origin", "right center");
next();
}).delay(125).queue(function(next){
$(text).css("display", "block");
next();
}).delay(250).queue(function(next){
$(this).removeClass("someAnimation");
next();
})
}
}
})
When you define this:
var overlay = $(".someYellowOverlay");
overlay can hold a single element OR a collection (many elements).
By the way, you don't need to redo a lookup for element after.
$(overlay) is useless... You can use overlay directly.
Now have in mind that it may be a collection, so what you will do will apply on each of them.
That is why I took the parents instead.
I now can "cycle" through them to apply the delays I want to their children.
Here is the code, you can play with it in CodePen.
Feel free to ask if you have questions.
;)
$(document).ready(function(){
var text = $("p");
var overlay = $(".someYellowOverlay").parent();
var flag = false;
$(document).scroll(revealTextOnScroll);
function revealTextOnScroll() {
var scrollPos = $(document).scrollTop();
var targetPos = $("header").offset().top;
if (scrollPos >= 250 && flag === false) {
flag = true;
revealText();
} else if (scrollPos < 250) {
flag = false;
text.css("display", "none");
overlay.children().css("transform-origin", "left center");
}
function revealText() {
var delayBetweenLines = 2000/overlay.length;
for(i=0;i<overlay.length;i++){
overlay.eq(i).children().first().addClass("someAnimation")
.delay((i*delayBetweenLines)+250).queue(function(next){
$(this).css("transform-origin", "right center");
next();
})
.delay((i*delayBetweenLines)+125).queue(function(next){
$(this).next().css("display", "block");
next();
})
.delay((i*delayBetweenLines)+250).queue(function(next){
$(this).removeClass("someAnimation");
next();
})
}
}
}
});
I have this code and it works exactly as I want. The menu bar sits on top and recognizes the section it is on or in. You can click the links in the yellow menu to move between the sections.
http://jsfiddle.net/spadez/2atkZ/9/
http://jsfiddle.net/spadez/2atkZ/9/embedded/result/
$(function () {
var $select = $('#select');
var $window = $(window);
var isFixed = false;
var init = $select.length ? $select.offset().top : 0;
$window.scroll(function () {
var currentScrollTop = $window.scrollTop();
if (currentScrollTop > init && isFixed === false) {
isFixed = true;
$select.css({
top: 0,
position: 'fixed'
});
$('body').css('padding-top', $select.height());
} else if (currentScrollTop <= init) {
isFixed = false;
$select.css('position', 'relative');
$('#select span').removeClass('active');
$('body').css('padding-top', 0);
}
//active state in menu
$('.section').each(function(){
var eleDistance = $(this).offset().top;
if (currentScrollTop >= eleDistance-$select.outerHeight()) {
var makeActive = $(this).attr('id');
$('#select span').removeClass('active');
$('#select span.' + makeActive).addClass('active');
}
});
});
$(".nav").click(function (e) {
var divId = $(this).data('sec');
$('body').animate({
scrollTop: $(divId).offset().top - $select.height()
}, 500);
});
});
However, the code itself gets quite laggy as soon as you start putting any content in the boxes. I wondered if there is any opportunity to optimize the code and make it run a bit smoother.
The problem you have is that you're repeatedly changing page layout properties (via the animation) and querying page layout properties (in the scroll handler), thus triggering a large number of forced layouts.
If i understand your code correctly you could get a big improvement by disabling the scroll handler during the click animation and instead triggering the effects with no checks made (set the active class on the clicked element).
I am trying to add class or remove class on getting element top by using This DEMO . Here is the code as well:
$(document).ready(function () {
var sec1_offset = $("#sec1").offset();
var sec2_offset = $("#sec2").offset();
var sec3_offset = $("#sec3").offset();
var sec4_offset = $("#sec4").offset();
var sec5_offset = $("#sec5").offset();
var sec6_offset = $("#sec6").offset();
var sec7_offset = $("#sec7").offset();
$("section").scroll(function () {
if (sec4_offset.top < 100) {
alert("You Are in Sec 4");
}
});
});
I also change the $("section").scroll(function () { to $(body).scroll(function () { and $(document).scroll(function () { but it didn't work!
Can you please let me know what I am doing wrong? Thanks
You can listen to the scroll event of the window object, scroll event like the resize event is fired so many times, for efficiency you can throttle the handler, ie the handler is executed after a specified timeout.
$(document).ready(function () {
var $sec = $("section"),
handle = null;
var $w = $(window).scroll(function () {
// clear the timeout handle
clearTimeout(handle);
// throttling the event handler
handle = setTimeout(function() {
var top = $w.scrollTop();
// filtering the first matched element
var $f = $sec.filter(function() {
return $(this).offset().top + $(this).height() >= top;
}).first().addClass('active');
$sec.not($f).removeClass('active');
}, 50);
}).scroll();
});
http://jsfiddle.net/UTCER/
edit: If you want to add a class to another element, the most efficient way is using the index method:
// Cache the object outside the `scroll` handler
var $items = $('#menu li');
// within the `setTimeout` context:
var $f = $sec.filter(function() {
return $(this).offset().top + $(this).height() >= top;
}).first();
$items.removeClass('active').eq( $sec.index($f) ).addClass('active');
use $(window).scroll for the scroll event listener
also you want to check sec4_offset.top against window.scrollY
JS
$(document).ready(function () {
var sec1_offset = $("#sec1").offset();
var sec2_offset = $("#sec2").offset();
var sec3_offset = $("#sec3").offset();
var sec4_offset = $("#sec4").offset();
var sec5_offset = $("#sec5").offset();
var sec6_offset = $("#sec6").offset();
var sec7_offset = $("#sec7").offset();
$(window).scroll(function () {
if (window.scrollY >= sec4_offset.top) {
alert("You Are in Sec 4");
}
});
});
JSFiddle Demo
Use $(window).scroll()
Here's what jQuery documentation says about scroll event
The scroll event is sent to an element when the user scrolls to a different place in the element. It applies to window objects, but also to scrollable frames and elements with the overflow CSS property set to scroll (or auto when the element's explicit height or width is less than the height or width of its contents).
I know this answer has already been answered, but I'd like to provide an alternative answer on JSFiddle that might accomplish what you're looking for to a more dynamic extent. I would not ask to be voted as the answer, but simply noted as a reference for an alternative approach to this problem
http://jsfiddle.net/mLfAq/5/
$(document).ready(function () {
var offsets = [];
$('[id^="#sec"]').each(function() {
offsets.push([$(this).attr('id'), $(this).offset().top + $(this).height()]);
});
$(window).scroll(function () {
for(var i = 0; i < offsets.length; i++) {
if(offsets[i][1] > $(window).scrollTop()) {
console.log('You are in ' + offsets[i][0]);
return;
}
}
});
});
I am writing a small jQuery function and I seem to be having trouble.
What I am trying to do here is when the user scrolls down the page by 90px, a div tag should animate down (from top:-50px to top:0), and vice-versa when they scroll back to the top of the page.
The problem I am having is that the animation seems to be very slow and unresponsive at times. I test in 3 different browsers and different computers but I am having no joy.
Here is my code:
// Show div
var scrollValue = "90";
// Animate functions
var showHead = function (){
$(".element").animate({top: "0"}, 250);
}
var hideHead = function (){
$(".element").animate({top: "-50px"}, 250);
}
$(window).scroll(function() {
if ($(this).scrollTop() > scrollValue) {
showHead();
} else {
hideHead();
}
});
The .element properties:
.element { positoin:fixed; top:-50px; }
Could anyone figure out why my code the hide/showHead functions are so sloppy?
Thanks,
Peter
The scroll event is triggered several times and even though it is rate-limited it keeps being a rather intensive operation. Actually, you may be queuing several animations and the fx stack may be growing very quickly.
One possibility you can try is stopping all previous animations before triggering a new one. You can do this by using .stop().
$(".element").stop().animate({top: "0"}, 250);
The .stop() function also provides some other options which you can use to tweak it even more.
Try this one :
$(window).scroll(function() {
if (window.scrollY > scrollValue) {
showHead();
} else {
hideHead();
}
});
scroll events occurred many time durring user scrolling.
You need to check if your animation is in progress before starting the animation again.
Try this :
// Show div
var scrollValue = "90";
var inProgress = false;
// Animate functions
var showHead = function () {
if(inProgress)
return false;
//Animate only if the animation is not in progress
inProgress = true;
$(".element").animate({
top: "0"
},250,function(){
inProgress = false; //Reset when animation is done
});
}
var hideHead = function () {
if(inProgress)
return false;
//Animate only if the animation is not in progress
inProgress = true;
$(".element").animate({
top: "-50px"
}, 250,function(){
inProgress = false; //Reset when animation is done
});
}
$(window).scroll(function () {
if ($(this).scrollTop() > scrollValue) {
showHead();
} else {
hideHead();
}
});
Assuming you have position:fixed (or some other sort of styling making the bar visible when necessary):
var scrollheight = 90;
var $el = $('.element');
function showHead(){
$el.stop().animate({
top: '0px'
}, 250);
}
function hideHead(){
$el.stop().animate({
top: '-50px'
}, 250);
}
$(window).scroll(function(){
if ($(window).scrollTop() > scrollheight){
showHead();
}else{
hideHead();
}
});
example: http://jsfiddle.net/L4LfL/
try using queue: false and as Alexander said use .stop()
here jsfiddle
http://jsfiddle.net/hwbPz/
I wrote a slideshow plugin, but for some reason maybe because I've been working on it all day, I can't figure out exactly how to get it to go back to state one, once it's reached the very last state when it's on auto mode.
I'm thinking it's an architectual issue at this point, because basically I'm attaching the amount to scroll left to (negatively) for each panel (a panel contains 4 images which is what is currently shown to the user). The first tab should get: 0, the second 680, the third, 1360, etc. This is just done by calculating the width of the 4 images plus the padding.
I have it on a setTimeout(function(){}) currently to automatically move it which works pretty well (unless you also click tabs, but that's another issue). I just want to make it so when it's at the last state (numTabs - 1), to animate and move its state back to the first one.
Code:
(function($) {
var methods = {
init: function(options) {
var settings = $.extend({
'speed': '1000',
'interval': '1000',
'auto': 'on'
}, options);
return this.each(function() {
var $wrapper = $(this);
var $sliderContainer = $wrapper.find('.js-slider-container');
$sliderContainer.hide().fadeIn();
var $tabs = $wrapper.find('.js-slider-tabs li a');
var numTabs = $tabs.size();
var innerWidth = $wrapper.find('.js-slider-container').width();
var $elements = $wrapper.find('.js-slider-container a');
var $firstElement = $elements.first();
var containerHeight = $firstElement.height();
$sliderContainer.height(containerHeight);
// Loop through each list element in `.js-slider-tabs` and add the
// distance to move for each "panel". A panel in this example is 4 images
$tabs.each(function(i) {
// Set amount to scroll for each tab
if (i === 1) {
$(this).attr('data-to-move', innerWidth + 20); // 20 is the padding between elements
} else {
$(this).attr('data-to-move', innerWidth * (i) + (i * 20));
}
});
// If they hovered on the panel, add paused to the data attribute
$('.js-slider-container').hover(function() {
$sliderContainer.attr('data-paused', true);
}, function() {
$sliderContainer.attr('data-paused', false);
});
// Start the auto slide
if (settings.auto === 'on') {
methods.auto($tabs, settings, $sliderContainer);
}
$tabs.click(function() {
var $tab = $(this);
var $panelNum = $(this).attr('data-slider-panel');
var $amountToMove = $(this).attr('data-to-move');
// Remove the active class of the `li` if it contains it
$tabs.each(function() {
var $tab = $(this);
if ($tab.parent().hasClass('active')) {
$tab.parent().removeClass('active');
}
});
// Add active state to current tab
$tab.parent().addClass('active');
// Animate to panel position
methods.animate($amountToMove, settings);
return false;
});
});
},
auto: function($tabs, settings, $sliderContainer) {
$tabs.each(function(i) {
var $amountToMove = $(this).attr('data-to-move');
setTimeout(function() {
methods.animate($amountToMove, settings, i, $sliderContainer);
}, i * settings.interval);
});
},
animate: function($amountToMove, settings, i, $sliderContainer) {
// Animate
$('.js-slider-tabs li').eq(i - 1).removeClass('active');
$('.js-slider-tabs li').eq(i).addClass('active');
$('#js-to-move').animate({
'left': -$amountToMove
}, settings.speed, 'linear', function() {});
}
};
$.fn.slider = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
return false;
}
};
})(jQuery);
$(window).ready(function() {
$('.js-slider').slider({
'speed': '10000',
'interval': '10000',
'auto': 'on'
});
});
The auto and animate methods are where the magic happens. The parameters speed is how fast it's animated and interval is how often, currently set at 10 seconds.
Can anyone help me figure out how to get this to "infinitely loop", if you will?
Here is a JSFiddle
It would probably be better to let go of the .each() and setTimeout() combo and use just setInterval() instead. Using .each() naturally limits your loop to the length of your collection, so it's better to use a looping mechanism that's not, and that you can break at any point you choose.
Besides, you can readily identify the current visible element by just checking for .active, from what I can see.
You'd probably need something like this:
setInterval(function () {
// do this check here.
// it saves you a function call and having to pass in $sliderContainer
if ($sliderContainer.attr('data-paused') === 'true') { return; }
// you really need to just pass in the settings object.
// the current element you can identify (as mentioned),
// and $amountToMove is derivable from that.
methods.animate(settings);
}, i * settings.interval);
// ...
// cache your slider tabs outside of the function
// and just form a closure on that to speed up your manips
var slidertabs = $('.js-slider-tabs');
animate : function (settings) {
// identify the current tab
var current = slidertabs.find('li.active'),
// and then do some magic to determine the next element in the loop
next = current.next().length >= 0 ?
current.next() :
slidertabs.find('li:eq(0)')
;
current.removeClass('active');
next.addClass('active');
// do your stuff
};
The code is not optimized, but I hope you see where I'm getting at here.