Jquery animating width accordion style banner - javascript

I built this simple accordion style banner. Here's what it's supposed to do:
Grab <li> containing images from selected <ul>.
Divide them equally within the container (div.banner)
On 'mouseenter', add class .active to the hovered <li>
Shrink the other <li>s widths (half their original width).
Enlarge active <li> to new width (remainder after halving the others)
On 'mouseleave', all return to original widths.
Works fine until you swipe over multiple panes quickly. If you do, the last of the floated <li>'s break to the next line. It appears the total width of the panes is exceeding their container.
Rounding error while animating? Does it have something to do with animate's default 'swing' easing?
Fiddle: http://jsfiddle.net/UNFc4/
var banner = $('.banner');
var list_items = banner.find('li');
var banner_width = $(banner).width();
var num_of_images = $(banner).find('li').length;
var original_width = banner_width / num_of_images;
var half_width = (banner_width / num_of_images) / 2;
var init = function () {
$(list_items).css('width', original_width);
$(list_items).on('mouseenter', function () {
$(this).addClass('active');
doAnimation();
});
$(list_items).on('mouseleave', function () {
resetAnimation();
$(this).removeClass('active');
});
}
var doAnimation = function () {
$(list_items).not(".active").stop().animate({
width: half_width + "px"
}, 500);
$(".active").stop().animate({
width: (original_width + (half_width * (num_of_images - 1))) + "px"
}, 500);
}
var resetAnimation = function () {
$(list_items).stop().animate({
width: original_width + "px"
}, 500);
}
init();
I could fix it by changing this line, slowing the animation of the others, giving things time to equal out. But, I'd rather solve what's going on here, hopefully learning a bit more about how jQuery's animate() works.
$(list_items).not(".active").stop().animate({
width: half_width + "px"
}, 480); // changed 500 to 480

For those interested, I realized I only needed the reset on the banner area. Now it works, as described, without all the jitteriness and the subsequent layout mis-alignments.
New Fiddle: http://jsfiddle.net/UNFc4/1/
$(list_items).on('mouseenter', function () {
$(this).addClass('active');
doAnimation();
});
$(list_items).on('mouseleave', function () {
$(this).removeClass('active');
doAnimation();
});
$(banner).on('mouseleave', function () {
resetAnimation();
});

Related

jquery image hover keeps growing as many times as i hover

i got this strange behaviour
when i do a slow hover on image everything is working, the image grows and on hover out the image shrinks.
But when i repeat the hover fast the image keeps growing and growing and the position is changing according to the hover speed
Please see fiddle
Jquery
$(document).ready(function () {
var cont_left = $("#container").position().left;
$("a img").hover(function () {
// hover in
$(this).parent().parent().css("z-index", 1);
current_h = $(this, 'img')[0].height;
current_w = $(this, 'img')[0].width;
$(this).stop(true, false).animate({
width: (current_w * 1.3),
height: (current_h * 1.3),
left: "-=50",
top: "-=50"
}, 300);
}, function () {
// hover out
$(this).parent().parent().css("z-index", 0);
$(this).stop(true, false).animate({
width: current_w + 'px',
height: current_h + 'px',
left: "+=50",
top: "+=50"
}, 300);
});
$(".img").each(function (index) {
var left = (index * 160) + cont_left;
$(this).css("left", left + "px");
});
});
Please advise how to i fix the image grow and position.
P.S: every image has a different dimentions
These lines are the key to the problem:
current_h = $(this, 'img')[0].height;
current_w = $(this, 'img')[0].width;
When you .stop the image-growing animation, it doesn't shrink back to its original size (unless you set its second param to true - but you assign false to it explicitly, and I assume you know what you're doing here). So both dimensions are set to the increased value actually.
Solution is simple: always use the original size of the images:
$(document).ready(function () {
var current_h, current_w;
// ...
current_h = current_h || $(this, 'img')[0].height;
current_w = current_w || $(this, 'img')[0].width;
JS Fiddle.
Two sidenotes here. First, there's a similar problem with positioning of these elements: move too fast, and your images will shift to the left-upper or right-lower corners (depending on the phase); that's because, again, animation is done against the current state of things, which is not the same as original when the previous animation is stopped with .stop(true, false).
Second, using $(this, 'img')[0] in this case is essentially the same as just this. Remember, in event handlers this corresponds to the DOM element having this event handler assigned.
So this is how it can be done (demo):
$("a img").hover(function() {
var $this = $(this);
$this.closest('.img').css('z-index', 1);
var orig = $this.data('orig');
if (!orig) { // caching the original sizes via `jQuery.data`
orig = {
width: this.width,
height: this.height
};
$this.data('orig', orig);
}
$this.stop(true, false).animate({
width: orig.width * 1.3,
height: orig.height * 1.3,
left: -(orig.width * 0.3 / 2),
top: -(orig.height * 0.3 / 2)
}, 300);
}, function () {
var $this = $(this),
orig = $this.data('orig');
if (!orig) {
return false;
// actually, it should never be here,
// as calling 'mouseleave' without data precached
// means 'mouseenter' has been never called
}
$this.closest('.img').css('z-index', 0);
$this.stop(true, false).animate({
width: orig.width,
height: orig.height,
left: 0,
top: 0
}, 300);
});
The problem is that when you hover quickly, your values current_h and current_w don't measure the original height and width, but the current height and width. Thus, every time, you're increasing the value.
Solution
I've used a simple .each() function here to set the original height and width of each image as data attributes which can then be accessed when you're setting current_h and current_w.
$('img').each(function(i, el) {
$(el).attr({
"data-original-width": $(this).width(),
"data-original-height": $(this).height()
});
});
current_h = $(this).attr("data-original-height");
current_w = $(this).attr("data-original-width");
WORKING FIDDLE
You don't have to use the each function though. If you know the height and width of the images before rendering, then you can set these as data attributes in your HTML

Synchronized scrolling using jQuery?

I am trying to implement synchronized scrolling for two DIV with the following code.
DEMO
$(document).ready(function() {
$("#div1").scroll(function () {
$("#div2").scrollTop($("#div1").scrollTop());
});
$("#div2").scroll(function () {
$("#div1").scrollTop($("#div2").scrollTop());
});
});
#div1 and #div2 is having the very same content but different sizes, say
#div1 {
height : 800px;
width: 600px;
}
#div1 {
height : 400px;
width: 200px;
}
With this code, I am facing two issues.
1) Scrolling is not well synchronized, since the divs are of different sizes. I know, this is because, I am directly setting the scrollTop value. I need to find the percentage of scrolled content and calculate corresponding scrollTop value for the other div. I am not sure, how to find the actual height and current scroll position.
2) This issue is only found in firefox. In firefox, scrolling is not smooth as in other browsers. I think this because the above code is creating a infinite loop of scroll events.
I am not sure, why this is only happening with firefox. Is there any way to find the source of scroll event, so that I can resolve this issue.
Any help would be greatly appreciated.
You can use element.scrollTop / (element.scrollHeight - element.offsetHeight) to get the percentage (it'll be a value between 0 and 1). So you can multiply the other element's (.scrollHeight - .offsetHeight) by this value for proportional scrolling.
To avoid triggering the listeners in a loop you could temporarily unbind the listener, set the scrollTop and rebind again.
var $divs = $('#div1, #div2');
var sync = function(e){
var $other = $divs.not(this).off('scroll'), other = $other.get(0);
var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
other.scrollTop = percentage * (other.scrollHeight - other.offsetHeight);
// Firefox workaround. Rebinding without delay isn't enough.
setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on( 'scroll', sync);
http://jsfiddle.net/b75KZ/5/
Runs like clockwork (see DEMO)
$(document).ready(function(){
var master = "div1"; // this is id div
var slave = "div2"; // this is other id div
var master_tmp;
var slave_tmp;
var timer;
var sync = function ()
{
if($(this).attr('id') == slave)
{
master_tmp = master;
slave_tmp = slave;
master = slave;
slave = master_tmp;
}
$("#" + slave).unbind("scroll");
var percentage = this.scrollTop / (this.scrollHeight - this.offsetHeight);
var x = percentage * ($("#" + slave).get(0).scrollHeight - $("#" + slave).get(0).offsetHeight);
$("#" + slave).scrollTop(x);
if(typeof(timer) !== 'undefind')
clearTimeout(timer);
timer = setTimeout(function(){ $("#" + slave).scroll(sync) }, 200)
}
$('#' + master + ', #' + slave).scroll(sync);
});
This is what I'm using. Just call the syncScroll(...) function with the two elements you want to synchronize. I found pawel's solution had issues with continuing to slowly scroll after the mouse or trackpad was actually done with the operation.
See working example here.
// Sync up our elements.
syncScroll($('.scroll-elem-1'), $('.scroll-elem-2'));
/***
* Synchronize Scroll
* Synchronizes the vertical scrolling of two elements.
* The elements can have different content heights.
*
* #param $el1 {Object}
* Native DOM element or jQuery selector.
* First element to sync.
* #param $el2 {Object}
* Native DOM element or jQuery selector.
* Second element to sync.
*/
function syncScroll(el1, el2) {
var $el1 = $(el1);
var $el2 = $(el2);
// Lets us know when a scroll is organic
// or forced from the synced element.
var forcedScroll = false;
// Catch our elements' scroll events and
// syncronize the related element.
$el1.scroll(function() { performScroll($el1, $el2); });
$el2.scroll(function() { performScroll($el2, $el1); });
// Perform the scroll of the synced element
// based on the scrolled element.
function performScroll($scrolled, $toScroll) {
if (forcedScroll) return (forcedScroll = false);
var percent = ($scrolled.scrollTop() /
($scrolled[0].scrollHeight - $scrolled.outerHeight())) * 100;
setScrollTopFromPercent($toScroll, percent);
}
// Scroll to a position in the given
// element based on a percent.
function setScrollTopFromPercent($el, percent) {
var scrollTopPos = (percent / 100) *
($el[0].scrollHeight - $el.outerHeight());
forcedScroll = true;
$el.scrollTop(scrollTopPos);
}
}
If the divs are of equal sizes then this code below is a simple way to scroll them synchronously:
scroll_all_blocks: function(e) {
var scrollLeft = $(e.target)[0].scrollLeft;
var len = $('.scroll_class').length;
for (var i = 0; i < len; i++)
{
$('.scroll_class')[i].scrollLeft = scrollLeft;
}
}
Here im using horizontal scroll, but you can use scrollTop here instead. This function is call on scroll event on the div, so the e will have access to the event object.
Secondly, you can simply have the ratio of corresponding sizes of the divs calculated to apply in this line $('.scroll_class')[i].scrollLeft = scrollLeft;
I solved the sync scrolling loop problem by setting the scroll percentage to fixed-point notation: percent.toFixed(0), with 0 as the parameter. This prevents mismatched fractional scrolling heights between the two synced elements, which are constantly trying to "catch up" with each other. This code will let them catch up after at most a single extra step (i.e., the second element may continue to scroll an extra pixel after the user stops scrolling). Not a perfect solution or the most sophisticated, but certainly the simplest I could find.
var left = document.getElementById('left');
var right = document.getElementById('right');
var el2;
var percentage = function(el) { return (el.scrollTop / (el.scrollHeight - el.offsetHeight)) };
function syncScroll(el1) {
el1.getAttribute('id') === 'left' ? el2 = right : el2 = left;
el2.scrollTo( 0, (percentage(el1) * (el2.scrollHeight - el2.offsetHeight)).toFixed(0) ); // toFixed(0) prevents scrolling feedback loop
}
document.getElementById('left').addEventListener('scroll',function() {
syncScroll(this);
});
document.getElementById('right').addEventListener('scroll',function() {
syncScroll(this);
});
I like pawel's clean solution but it lacks something I need and has a strange scrolling bug where it continues to scroll and my plugin will work on multiple containers not just two.
http://www.xtf.dk/2015/12/jquery-plugin-synchronize-scroll.html
Example & demo: http://trunk.xtf.dk/Project/ScrollSync/
Plugin: http://trunk.xtf.dk/Project/ScrollSync/jquery.scrollSync.js
$('.scrollable').scrollSync();
If you don't want proportional scrolling, but rather to scroll an equal amount of pixels on each field, you could add the value of change to the current value of the field you're binding the scroll-event to.
Let's say that #left is the small field, and #right is the bigger field.
var oldRst = 0;
$('#right').on('scroll', function () {
l = $('#left');
var lst = l.scrollTop();
var rst = $(this).scrollTop();
l.scrollTop(lst+(rst-oldRst)); // <-- like this
oldRst = rst;
});
https://jsfiddle.net/vuvgc0a8/1/
By adding the value of change, and not just setting it equal to #right's scrollTop(), you can scroll up or down in the small field, regardless of its scrollTop() being less than the bigger field. An example of this is a user page on Facebook.
This is what I needed when I came here, so I thought I'd share.
From the pawel solution (first answer).
For the horizzontal synchronized scrolling using jQuery this is the solution:
var $divs = $('#div1, #div2'); //only 2 divs
var sync = function(e){
var $other = $divs.not(this).off('scroll');
var other = $other.get(0);
var percentage = this.scrollLeft / (this.scrollWidth - this.offsetWidth);
other.scrollLeft = percentage * (other.scrollWidth - other.offsetWidth);
setTimeout( function(){ $other.on('scroll', sync ); },10);
}
$divs.on('scroll', sync);
JSFiddle
An other solution for multiple horizontally synchronized divs is this, but it works for divs with same width.
var $divs = $('#div1, #div2, #div3'); //multiple divs
var sync = function (e) {
var me = $(this);
var $other = $divs.not(me).off('scroll');
$divs.not(me).each(function (index) {
$(this).scrollLeft(me.scrollLeft());
});
setTimeout(function () {
$other.on('scroll', sync);
}, 10);
}
$divs.on('scroll', sync);
NB: Only for divs with same width
JSFiddle

get height of LI using jQuery so news ticker scrolls up the exact amount needed

I am using a script from
http://www.yourinspirationweb.com/en/jquery-how-to-create-a-news-ticker-with-just-a-few-javascript-lines/
which is a great newsticker, designed to scroll up just one article at a time, then append it again at the end in a continuous loop. But the limitation of this script is that the news items in each have to be the same height as eachother, which isn't always possible.
I have been trying to modify the script so that it will automatically get the outerheight of '#ticker li:first'. and scroll it so that marginTop equals the negative of that outerheight. rather than the default '-120px'. But i've realised it's written as CSS style, i don't know how to rewrite it. Help!
here's the original script:
$(function()
{
var ticker = function()
{
setTimeout(function(){
$('#ticker li:first').animate( {marginTop: '-120px'}, 800, function()
{
$(this).detach().appendTo('ul#ticker').removeAttr('style');
});
ticker();
}, 4000);
};
ticker();
});
This should do the trick. Just put the height into a variable, multiply by -1 (to make the negative number you need), and then drop it into the marginTop property:
$(function() {
var ticker = function() {
setTimeout(function() {
// get the height of the li element, multiply by -1 to be negative
var h = parseInt($("#ticker li:first").outerHeight()) * -1;
$('#ticker li:first').animate( {marginTop: h + 'px'}, 800, function() {
$(this).detach().appendTo('ul#ticker').removeAttr('style');
});
ticker();
}, 4000);
};
ticker();
});
To retrieve the outerHeight (including border, padding, and optionally margin) of an html element using jQuery, do $(element).outerHeight()
To retrieve the innerHeight of an html element using jQuery, do $(element).innerHeight()
$(function()
{
var ticker = function()
{
setTimeout(function(){
var oHeight = $('#ticker li:first').outerHeight();
$('#ticker li:first').animate( {marginTop: -oHeight}, 800, function()
{
$(this).detach().appendTo('ul#ticker').removeAttr('style');
});
ticker();
}, 4000);
};
ticker();
});
The default unit is pixels so you don't have to worry about appending px to the value.

How to reset to original values?

It looks like it keeps adding a new newHeight and a newDistance each time i click, I am trying to save original height with a global var at the top and using data to do that but i get weird results, basically i should be able to reset newDistance and newHeight to first original values as per before to run the lot with a click but it doesn't and i get new added values each time i click breaking my layout as a result:
talents = $(".talenti");
filter = $(".filtra");
genHeight = $("#container").data($("#container").height());
filter.click(function(e) {
e.preventDefault();
if (talents.hasClass("opened")) {
$(".nasco").slideToggle();
$("#wrapNav").slideToggle("10", "linear");
talents.removeClass('opened');
filter.addClass('opened');
$("#container").css("height", genHeight);
} else {
filter.addClass('opened');
};
if (filter.hasClass("opened")) {
$("#wrapNav").slideToggle("10", "linear", function(){
$("#sliding-navigation").slideToggle();
var newHeight = $("#container").height() + $("#wrapNav").outerHeight(true);
var newDistance = newHeight - $("#container").height() + 22;
$("#container").animate({height: newHeight}, 50,function(){
$(".box").animate({top: newDistance});
});
});
}
});
talents.click(function(e) {
e.preventDefault();
if (filter.hasClass("opened")) {
$("#sliding-navigation").slideToggle();
$("#wrapNav").slideToggle("10", "linear");
filter.removeClass('opened');
talents.addClass('opened');
$("#container").css("height", genHeight);
} else {
talens.addClass('opened');
};
if (talents.hasClass("opened")) {
$("#wrapNav").slideToggle("10", "linear", function(){
$(".nasco").slideToggle();
var newHeight = $("#container").height() + $("#wrapNav").outerHeight(true);
var newDistance = newHeight - $("#container").height() + 156;
$("#container").animate({height: newHeight}, 50,function(){
$(".box").animate({top: newDistance});
});
});
}
});
Anyone?
So, based on the code I could download about 20min ago from your test site, I managed to get it working with the following code:
$(document).ready(function(){
// placeholder to contain the original height...
var original_height = 0;
talents = $(".talenti");
filter = $(".filtra");
filter.click(function(e){
e.preventDefault();
if (filter.hasClass('opened')){
filter.removeClass('opened');
// toggle the wrapping, just with a zero top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
$("#sliding-navigation").hide();
$(".box").animate({top: '0px'});
});
// reset to the original height...
$("#container").height(original_height);
}
else {
// get the original height if it's not already set...
if (original_height == 0)
original_height = $("#container").height();
filter.addClass('opened');
if (talents.hasClass("opened"))
{
$(".nasco").hide();
$("#wrapNav").slideToggle();
talents.removeClass('opened');
}
// toggle the wrapping with a height of the nav as top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
$("#sliding-navigation").slideToggle(true, function(){
// need the height of the nav before we know how far to move the boxes...
var newHeight = $("#wrapNav").outerHeight(true);
$(".box").animate({top: newHeight});
// set the container's new height, much like you had...
$("#container").height(original_height + newHeight);
});
});
}
});
talents.click(function(e) {
e.preventDefault();
if (talents.hasClass('opened')) {
talents.removeClass('opened');
// toggle the wrapping, just with a zero top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
$(".nasco").hide();
$(".box").animate({top: '0px'});
});
// reset to the original height...
$("#container").height(original_height);
}
else {
// get the original height if it's not already set...
if (original_height == 0)
original_height = $("#container").height();
talents.addClass('opened');
if (filter.hasClass("opened"))
{
$("#sliding-navigation").hide();
$("#wrapNav").slideToggle();
filter.removeClass('opened');
}
// toggle the wrapping with a height of the nav as top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
// need the height of the nav before we know how far to move the boxes...
$(".nasco").slideToggle(true, function(){
var newHeight = $("#wrapNav").outerHeight(true);
$(".box").animate({top: newHeight});
// set the container's new height, much like you had...
$("#container").height(original_height + newHeight);
});
});
}
});
});
A few points adding food for thought:
I simplified the multiple if statements to make it easier to understand and process
I used hide() to avoid messy animation problems if you clicked on FILTER multiple times in a row
I only adjusted the top coordinates of the boxes to achieve this
I would have preferred to contain the boxes in a more general container, allowing for easier animation and management, but I understand that wordpress doesn't always give you the most room to work, so this should get you on your way!
It might not be completely what you're looking for in your animation, but it's a working example of the code you had and should get you 90% of the way...hope this helps! :)
What about using the data collection of the container element rather than a global variable i.e. at the top record the height
$("#container").data('height', $("#container").height());
then to use
$("#container").data('height');
i.e. to reset the height
$("#container").css({height: $("#container").data('height') });
I feel a bit suspicious about how the global variable is working. Worth a try maybe

lock a div on header when window scrolled past element

I want a circle div to lock in the header when the user scrolls past in.
I'm using the following code but it doesn't work
var circle$ = $('.circle'),
oCircleBottom = circle$.offset().top + circle$.outerHeight(true),
window$ = $(window);
window$.scroll(function() {
if (window$.scrollTop() > oCircleBottom) {
}
}.bind(this));
I want to perform an action when the user scrolls pass the circle div; however, the code above does not seem to work. Is oCircleBottom computed correctly?
Enclose your code in $(document).ready function
$(document).ready(function () {
var circle$ = $('.circle'),
oCircleBottom = circle$.offset().top + circle$.outerHeight(true),
window$ = $(window);
window$.scroll(function () {
if (window$.scrollTop() > oCircleBottom) {
$('.circle').css({
position: 'fixed',
top: '0',
left: '0'
});
}
else{
$('.circle').css({
position: 'static'});
}
}.bind(this));
});
You need to take window height into account because if the height of the page isnt enough to scroll down, your code doesnt work. Take a look at this example
However, if the increase page height, you code will work fine without subtracting window height. Take a look at this example
Hence, its better to subtract the window height. jsFiddle
$(window).bind('scroll', function() {
if($(window).scrollTop() >= $('.circle').offset().top + $('.circle').innerHeight() - window.innerHeight) {
//Do you stuff
}
});

Categories

Resources