I am working on this vertical scrolling gallery.
Now I cannot work it out but I need to make this code less verbose.
the code I've been writing is to repetative.
this is the code
var target1 = $("#myImg1").offset().top;
var target2 = $("#myImg2").offset().top;
if ($(window).scrollTop() >= target1 - 150
&& $(window).scrollTop() <= target1 + 150) {
showBgOnTop('#firstImage','#secondImage');
}
if ($(window).scrollTop() >= target2 - 150
&& $(window).scrollTop() <= target2 + 150) {
showBgOnTop('#secondImage','#firstImage');
}
Can I use some kind of function here to shorten the code.
Like an array.
please tell me what i am doing wrong here.
thanks.
Explanation
It seems like you have a jQuery object as your target that's linked to two images and you'd like to have multiple instances of this, say at least 2.
So my solution is to have an array of objects with this format
var array = [
{target: ['image1', 'image2']}
]
target being the target element id and the value being an array of the ids of the associated images.
Now all the user has to do is keep on adding new objects to the array in the format shown above.
Solution
// Put the target object id as keys and the array of the linked/associated elements as values
var arrs = [
{'#myImg1': ['#firstImage', '#secondImage']},
{'#myImg2': ['#secondImage', '#firstImage']},
{'#myImg3': ['...', '...']} // keep adding new items as object here
],
$windowScrollTop = $(window).scrollTop(); // cache window scroll function
// loop through the array here
$.each(arrs, function (idx, obj) {
for (id in obj) {
var $el = $(id), // cache the target element here
args = obj[id],
img1 = args[0].toString(),
img2 = args[1].toString();
if ($windowScrollTop >= $el - 150 && $windowScrollTop <= $el + 150) {
showBgOnTop(img1, img2);
}
}
});
Well, this actually is a jQuery function.
The only thing you can do to lessen the code is to use
$(window).scrollTop()
as a variable:
var windowScrollTop = $(window).scrollTop();
Remaining part is a condition, you cannot use conditions are variables.
How about this:
function show(image1id, image2id) {
var target1 = $(image1id).offset().top;
var target2 = $(image2id).offset().top;
if (($(window).scrollTop() >= target2 - 150) &&
$(window).scrollTop() <= target2 + 150)) {
showBgOnTop(image1id, image2id);
}
}
show('#firstImage','#secondImage');
show('#secondImage','#firstImage');
Related
I recently tried to work on a jQuery-Plugin which is used to spread an amount of containers randomly in a container to afterwards be able to animate them in a special way. You can see my attempts here: http://www.manuelmaurer.at/randposplugin.php
The Problem is, that some of them are overlapping and I can't figure out why. At first I thought that the reason for this might be that $.each() is not waiting for one loop to finish before starting the next one but I also tried to solve that using recursive functions - didn't help. I hope someone could give me a little push to figure out where the problem actually is, thanks in advance! You can see the code either on the page itself or just have a look at the following important parts.
This code is used to loop over all the elements. This is not further important, but the function "NoCollision" is trying to figure out, if an element exists in that area. If yes, it returns false, if the space can be used, it returns true. If the space can not be used, some random other coordinates are chosen and it will be tried again.
var Counter2 = 0;
$(FlowContainer).children(':not(:last)').each(function(elem) {
Counter2++;
ElemNow = $(FlowContainer).children().eq(Counter2);
ElemWidth = $(ElemNow).data("animwidth")
ElemHeight = $(ElemNow).data("animheight")
var Tries = 0;
var TryNowX = ElemPrevLeft;
var TryNowY = ElemPrevTop;
while (!NoCollision(TryNowX, TryNowY, ElemWidth, ElemHeight, PositionsArray, Settings.MinSpreadX, Settings.MinSpreadY) && Tries <= Settings.MaxTries) {
if (TryNowY < 15) {
TryNowY += randomIntFromInterval(0, 10);
} else if (TryNowY > (FlowContainer.height() - ElemHeight - 15)) {
TryNowY += randomIntFromInterval(-10, 0);
} else {
TryNowY += randomIntFromInterval(-10, 10);
}
if (TryNowX < 15) {
TryNowX += randomIntFromInterval(0, 10);
} else if (TryNowX > (FlowContainer.width() - ElemWidth - 15)) {
TryNowX += randomIntFromInterval(-10, 0);
} else {
TryNowX += randomIntFromInterval(-10, 10);
}
Tries++;
}
if (Tries == Settings.MaxTries) {
console.log("Warning: Couldn't fit all elements - hiding some.")
$(ElemNow).remove();
} else {
$(ElemNow).css({ top: TryNowY, left: TryNowX });
ElemPrevLeft = TryNowX;
ElemPrevTop = TryNowY;
PositionArray = [TryNowY, TryNowX, ElemHeight, ElemWidth];
PositionsArray[Counter2] = PositionArray;
}
})
The actual check, if the space can be used, takes part in the NoCollision-Function, which you can see in the following code.
function NoCollision(X, Y, W, H, PositionsArray, SpreadX, SpreadY) {
var NoErrors = true;
//Jedes Element im PositionsArray durchgehen und Prüfen
$.each(PositionsArray, function(PositionArray) {
var ArrY = PositionsArray[PositionArray][0];
var ArrX = PositionsArray[PositionArray][1];
var ArrW = PositionsArray[PositionArray][3];
var ArrH = PositionsArray[PositionArray][2];
if ((X < (ArrX - W - SpreadX) || X > (ArrX + ArrW + SpreadX)) && (Y < (ArrY - H - SpreadY) || Y > (ArrY + ArrH + SpreadY))) {
//SHOULD BE OKAY HERE
} else {
NoErrors = false;
}
})
return NoErrors;
}
The array which I am using to save the coordinates of all the already positioned divs looks like this.
PositionsArray[
[Elem1PositionY, Elem1PositionX, Elem1Height, Elem1Width]
[Elem2PositionY, Elem2PositionX, Elem2Height, Elem2Width]
]
My thought was to do it like this. Is there something wrong with the way I'd do it or is there a mistake in my implementation?
I am absolutely thankful for every bit of help! Thanks in advance!
I finally solved the issue - my calculation of the overlapping was a bit wrong, just had to change the && (and) to || (or) - works like charm now.
Found a better solution using a plugin which was referenced on another stackoverflow-thread - works way better now.
So this is somewhat of a math problem that I'd like to solve using JavaScript. I'm creating a fixed canvas on a website that outputs a different image based on every X amount of pixels scrolled from a particular .offset().top from the top of the window. I 'could' explicitly map a new image to a particular position but I've got a lot of images and it would behoove me to create a function that can handle this process multiple times until particular end point. I'm sort of stuck on how to express this and was wondering if anyone could steer me in the right direction.
EDIT
After consider #Richard Hamilton answer below I've been able to somewhat successfully implement his solution to my own project. It's a little verbose, but here's what I have...
// Preload Images
var totalImages = 203
var images = new Array()
for (var i = 1; i <= totalImages; i++) {
var filename = 'img_'
if (i < 10) filename += '00'
if (i > 9 && i < 100) filename += '0'
filename += i + '.jpg'
var img = new Image
img.src = '/images/temp/' + filename
images.push(img)
}
// Set initial frame index
var currentLocation = 0
// Canvas Context
var canv = document.getElementById('canvas')
var context = canv.getContext('2d')
$(canv)
.width(768)
.height(432)
// Frame Starting Location
var currentLocation = 0
// Determin the breakpoint increment to fit inside the context
var contextHeight = $('.about--context').height() - 200
var frameHeight = contextHeight / totalImages
// Set first breakpoint
var breakpoint = 63
// Get top of context in relation to window
var contextPos = $('.about--context').offset().top - $(window).scrollTop()
// Set where to start scrubbing through frames
var scrubStart = 62
// Initial scroll direction
var lastScrollTop = 0,
st,
direction
// Output the scroll direction as up or down
function detectDirection() {
st = window.pageYOffset;
if (st > lastScrollTop) {
direction = "down"
} else {
direction = "up"
}
lastScrollTop = st
return direction
}
window.addEventListener('scroll', function() {
var dir = detectDirection()
var contextPos = $('.about--context').offset().top - $(window).scrollTop()
var contextHeight = $('.about--context').height()
var frameHeight = contextHeight / totalImages
if (contextPos <= breakpoint && dir === 'down') {
breakpoint -= frameHeight
currentLocation++
context.drawImage(images[currentLocation], 0, 0, 768, 432)
console.log('Breakpoint = ' + breakpoint + ', index = ' + currentLocation)
}
if (contextPos > breakpoint && dir === 'up') {
breakpoint += frameHeight
currentLocation--
context.drawImage(images[currentLocation], 0, 0, 768, 432)
console.log('Breakpoint = ' + breakpoint + ', index = ' + currentLocation)
}
})
This mostly works, but there seems to be a discrepancy between how the frames change during scroll between a mouse wheel and a trackpad. The trackpad is much more sensitive and can get the breakpoint increment correctly, but the mouse wheel ends up scrolling through the section much quicker without correctly keeping up with the proper frame rate, so I never end up reach the final frame by the end of the section. Other than that the frames are moving correctly when scrolling up and down.
Let's say you have an image tag. If you have a lot of different image files, it would be a good idea to store them in array. This is a hard coded example, but shows the general structure.
var image = document.getElementById("myImage");
var sources = ["image1.png", "image2.png", "image3.png", "image4.png"];
var i = 0;
var breakpoint = 100; // Change to whatever you like
window.addEventListener("scroll", function() {
var scrollDown = document.body.scrollTop;
if (scrollDown >= breakpoint) {
img.setAttribute(src, sources[i]);
breakpoint += 100; //Change to whatever you like
i++;
}
}
You could also have something like this included
var windowHeight = window.innerHeight;
var scrollBottom = document.body.clientHeight - document.body.scrollTop;
if (scrollBottom === windowHeight) {
// Do something
}
First set a breakpoint variable equal to the number of pixels you want to scroll. For an example, I chose 100 because it's a nice round number. You then attach an event listener on the window object, to detect if a user is scrolling.
The scrollTop function represents how far the top of the screen is from the top of the window. If that value is higher than the breakpoint, that's when we call our code. We then increment this by 100.
Say I have a total width of 585px. And I wanted to divide the space into equal sections and assign each an index value within position. I could do something like this if I had lets say 6 sections: (assigned by total width / number of sections)
//Set up elements with variables
this.sliderContent = config.sliderContent;
this.sectionsWrap = config.sectionsWrap;
//Selects <a>
this.sectionsLinks = this.sectionsWrap.children().children();
//Create drag handle
this.sectionsWrap.parent().append($(document.createElement("div")).addClass("handle-containment")
.append($(document.createElement("a")).addClass("handle ui-corner-all").text("DRAG")));
//Select handle
this.sliderHandle = $(".handle");
var left = ui.position.left,
position = [];
var position = ((left >= 0 && left <= 80) ? [0, 1] :
((left >= 81 && left <= 198) ? [117, 2] :
((left >= 199 && left <= 315) ? [234, 3] :
((left >= 316 && left <= 430) ? [351, 4] :
((left >= 431 && left <= 548) ? [468, 5] :
((left >= 549) ? [585, 6] : [] ) ) ) ) ) );
if (position.length) {
$(".handle").animate({
left : position[0]
}, 400);
Slider.contentTransitions(position);
}
But what if I had an x number of sections. These sections are just elements like
<li><a></a></li>
<li><a></a></li>
<li><a></a></li>
Or
<div><a></a></div>
<div><a></a></div>
<div><a></a></div>
<div><a></a></div>
How would I divide the total of 585px and classify the index in position according to the current left value of the .handle element? I can know where the drag handle is by using ui.position.left, what I want is to be able to set an index for each element and be able to animate handle depending on where the handle is within the indexed elements. Since each element is indexed I later call a transition method and pass in the current index # to be displayed. The code I show above works, but isn't really efficient. I also need to account for the width of the handle to fit the section width. http://jsfiddle.net/yfqhV/1/
Ok, there is a slight inconsistency in the difference between the range figures in the question, which makes it hard to algorithmise [ my made-up-word de jour =) ] this exactly:
81 to 199 = 118
199 to 316 = 117
316 to 431 = 115
431 to 518 = 118
If you can adjust for that, I have a solution - it's not especially clever JavaScript, so there may well be better ways to do this (SO JS people, feel free to educate me!) but it works.
First we need a function to find the index of an array range, a given value falls within (this replaces your nested if-else shorthands), then we have a function to set up the positional arrays, and finally we can do a range search and return the corresponding array of values.
This solution should dynamically deal with a varying number of sections, as long as this line:
var len = $("#sectionContainer").children().length;
is adjusted accordingly. The only other values that may need adjusting are:
var totalWidth = 585;
var xPos = 81;
although you could set them if you have elements you can draw the values from, making it even more of a dynamic solution.
/**
* function to find the index of an array element where a given value falls
* between the range of values defined by array[index] and array[index+1]
*/
function findInRangeArray(arr, val){
for (var n = 0; n < arr.length-1; n++){
if ((val >= arr[n]) && (val < (arr[n+1]))) {
break;
}
}
return n;
}
/**
* function to set up arrays containing positional values
*/
function initPositionArrays() {
posArray = [];
leftPosArray = [];
var totalWidth = 585;
var xPos = 81;
var len = $("#sectionContainer").children().length;
var unit = totalWidth/(len - 1);
for (var i=1; i<=len; i++) {
pos = unit*(i-1);
posArray.push([Math.round(pos), i]);
xMin = (i >= 2 ? (i==2 ? xPos : leftPosArray[i-2] + posArray[1][0]) : 0);
leftPosArray.push(Math.round(xMin));
}
}
var left = ui.position.left;
initPositionArrays();
// find which index of "leftPosArray" range that "left" falls within
foundPos = findInRangeArray(leftPosArray, left);
var position = posArray[foundPos];
if (position.length) {
$(".handle").animate({
left : position[0]
}, 400);
Slider.contentTransitions(position);
}
I've set up a jsFiddle to illustrate.
Enjoy!
Edit
I've looked at #JonnySooter s own answer, and whilst it calculates the positioning correctly, it won't deal with a variable number of sections.
To get it to work with any number of sections, the handleContainment div (that is created on-the-fly) needs to have it's width set dynamically (via inline styling).
This is calculated by multiplying the number of sections by the width of each section (which is actually the same as the width of the slider).
This is all done after creating the handle so that the width can be extracted from the "handle" css class, meaning a change to the width of the handle will cascade into the routine when applied at the css level.
See this jsFiddle where the number of sections can be altered and the slider behaves properly.
var numSections = // ...;
var totalWidth = // ...;
var sectionWidth = totalWidth / numSections;
var index = Math.floor($(".handle").position().left / sectionWidth);
var leftPosition = index * sectionWidth;
var rightPosition = leftPosition + sectionWidth - 1;
UPDATE:
I worked on trying to find a solution myself and this is what I came up with:
function( event, ui ) {
var left = ui.position.left, //Get the current position of the handle
self = Slider, //Set to the Slider object cus func is a callback
position = 1;
sections_count = self.sectionsLinks.length, //Count the sections
section_position = Math.floor(self.sectionsWrap.width() / sections_count); //Set width of each section according to total width and section count
left = Math.round(left / section_position); //Set the index
position = (left * section_position); //Set the left ammount
if(position < section_position){ //If handle is dropped in the first section
position = 0.1; //Set the distance to animate
left = 0; //Set index to first section
}
if (position.length) {
$(this).animate({
left : position //Animate according to distance
}, 200);
left = left += 1; //Add one to the index so that I can use the nth() child selector later.
self.contentTransitions(left);
}
}
I'm looking for a very fast solution to a div scrolling problem.
I have a set of divs, like forum posts, that are laid out one on top of the other. As the page scrolls down or up, I'd like to know when one of those divs hit's an arbitrary point on the page.
One way I tried was adding an onScroll event to each item, but as the number of items grow the page really starts to lag.
Anyone know a more efficient way to do this? Thanks /w
Well, I'm new to all this, so may be someone should correct me :)
I propose to
cache posts position
caсhe current
use binary search
Demo: http://jsfiddle.net/zYe8M/
<div class="post"></div>
<div class="post"></div>
<div class="post"></div>
...
var posts = $(".post"), // our elements
postsPos = [], // caсhe for positions
postsCur = -1, // cache for current
targetOffset = 50; // position from top of window where you want to make post current
// filling postsPos with positions
posts.each(function(){
postsPos.push($(this).offset().top);
});
// on window scroll
$(window).bind("scroll", function(){
// get target post number
var targ = postsPos.binarySearch($(window).scrollTop() + targetOffset);
// only if we scrolled to another post
if (targ != postsCur) {
// set new cur
postsCur = targ;
// moving cur class
posts.removeClass("cur").eq(targ).addClass("cur");
}
});
// binary search with little tuning on return to get nearest from bottom
Array.prototype.binarySearch = function(find) {
var low = 0, high = this.length - 1,
i, comparison;
while (low <= high) {
i = Math.floor((low + high) / 2);
if (this[i] < find) { low = i + 1; continue; };
if (this[i] > find) { high = i - 1; continue; };
return i;
}
return this[i] > find ? i-1 : i;
};
You shouldn't bind scroll event to all the divs but only to window instead. Then, you should check whether one of the divs overlap with the target point by making a simple calculation of the element offset values.
$(window).scroll(function(event)
{
var isCaptured = capture();
console.log(isCaptured);
});
function capture()
{
var c = $('.box'); //this is the divs
var t = $('#target'); //this is the target element
var cPos = c.offset(); var tPos = t.offset();
var overlapY = (cPos.top <= tPos.top + t.height() && cPos.top + c.height() >= tPos.top);
var overlapX = (cPos.left <= tPos.left + t.width() && cPos.left + c.width() >= tPos.left);
return overlapY && overlapX;
}
Instead of the $('#target') element, you can pass top and left (X, Y) offset values directly to the function.
Well, here is a dirty demonstration.
Is there a simple way to find the min/max property from an array of elements in jQuery?
I constantly find myself dynamically resizing groups of elements based on the minimum and maximum counterparts. Most of the time this pertains to the width and/or height of an element but I'm sure this could be applied to any property of an element.
I usually do something like this:
var maxWidth = 0;
$('img').each(function(index){
if ($(this).width() > maxWidth)
{
maxWidth = $(this).width();
}
});
But it seems like you should be able to do something like this:
var maxWidth = $('img').max('width');
Does this functionality exist in jQuery or can someone explain how to create a basic plugin that does this?
Thanks!
Use Fast JavaScript Max/Min - John Resig
Example with three logos of google, yahoo and bing.
HTML
<img src="http://www.google.co.in/intl/en_com/images/srpr/logo1w.png" alt="Google Logo" /><br/>
<img src="http://l.yimg.com/a/i/ww/met/yahoo_logo_in_061509.png" alt="Yahoo Logo" /><br/>
<img src="http://www.bing.com/fd/s/a/h1.png" alt="Bing Logo" />
Javascript
$(document).ready(function(){
// Function to get the Max value in Array
Array.max = function( array ){
return Math.max.apply( Math, array );
};
// Function to get the Min value in Array
Array.min = function( array ){
return Math.min.apply( Math, array );
};
//updated as per Sime Vidas comment.
var widths= $('img').map(function() {
return $(this).width();
}).get();
alert("Max Width: " + Array.max(widths));
alert("Min Width: " + Array.min(widths));
});
P.S: jsfiddle here
You can use apply outside the context of OO, no need to extend the prototype:
var maxHeight = Math.max.apply( null,
$('img').map(function(){ return $(this).height(); }).get() );
I like the elegant solution posted as a .map() example in the jQuery docs on how to equalize div heights. I basically adapted it to work with widths and made a demo.
$.fn.limitWidth = function(max){
var limit = (max) ? 'max' : 'min';
return this.width( Math[limit].apply(this, $(this).map(function(i,e){
return $(e).width();
}).get() ) );
};
// Use the function above as follows
$('.max-width').limitWidth(true); // true flag means set to max
$('.min-width').limitWidth(); // no flag/false flag means set to min
Take a look at the calculation plugin, maybe it can help you with your problems.
They offer a number of math functions, like min, max and avg on DOM-elements.
Examples:
$("input[name^='min']").min();
$("input[name^='max']").max();
Rolled up as a plugin to return min-max of width and height:
// Functions to get the Min & Max value in Array
if (!Array.min) { Array.min = function( array ){return Math.min.apply( Math, array )} }
if (!Array.max) { Array.max = function( array ){return Math.max.apply( Math, array )} }
(function( $ ){ // Standard jQuery closure to hide '$' from other libraries.
// jQuery plug-in to get the min and max widths of a set of elements
$.fn.dimensionsMinMax = function(whnx) {
/*
################################################################################
Name
====
dimensionsMinMax(whnx) - jQuery plug-in to get min & max width & height
Parameters
==========
whnx - A 4-element array to receive the min and max values of the elements:
whnx[0] = minimum width;
whnx[1] = maximum width;
whnx[2] = minimum height;
whnx[3] = maximum height.
Returns
=======
this - so it can be "chained".
Example
=======
var minmax = new Array(4);
var number_of_images = $('img').dimensionsMinMax(minmax).class('abc').length;
console.log('number of images = ', number_of_images);
console.log('width range = ', minmax[0], ' to ', minmax[1]);
console.log('height range = ', minmax[2], ' to ', minmax[3]);
################################################################################
*/
var widths = new Array(this.length);
var heights = new Array(this.length);
this.each(function(i){
$this = $(this);
widths[i] = $this.width();
heights[i] = $this.height();
});
whnx[0] = Array.min( widths);
whnx[1] = Array.max( widths);
whnx[2] = Array.min(heights);
whnx[3] = Array.max(heights);
return this;
}
})( jQuery ); // End of standard jQuery closure.
I wrote a simple plugin to do exactly this - see gregbrown.co.nz/code/jquery-aggregate . With it installed, you could do:
var maxWidth = $('img').aggregate('width', 'max');
You can use native "sort" function to have more control over which elements are compared
Array.prototype.deepMax = function(comparator){
if(typeof comparator === 'function'){
var sorted = this.slice(0).sort(comparator);
return sorted[sort.length - 1];
}
return Math.max.apply(Math, this);
};
and you can call it like
var maxWidth = $('img').deepMax(function(a, b){
//-1 if a < b; +1 otherwise
return $(a).width() - $(b).width();
});
OR
you can use _.max of Underscore which is can be implemented like...
Array.prototype.max = function(iterator){
if(!iterator && obj[0] === +obj[0])
return Math.max.apply(Math, this);
var result = -Infinity, lastComputed = -Infinity;
this.forEach(function(value, index){
var computed = iterator ? iterator(value, index, this) : value;
computed > lastComputed && (result = value, lastComputed = computed);
});
return result;
};
var maxWidth = $('img').max(function(val){ return $(val).width();});
The Plugins/Authoring page actually has an example for determining the tallest element.
It's basically what you have here, just rolled into a plugin for easy access. Maybe you could appropriate it for your uses.