I would like to trigger some functions according to a position of an element. This element's position changes every tenth second. There is two dozens functions to trigger.
I thought about this pseudo-code :
When element position changes{
Loop through all the coordinates to see if a function can be triggered{
if the current element position matches the function's triggering position
execute the function
}
}
But looping through all possible position each split seconds burdens the browser. So if there is a way to have events to do that.
Is it possible ?
Edit:
After Beetroot-Beetroot comment, I must say that the element that moves only moves on an X abscissa : so just one dimension.
It's much like a horizontal timeline moving from left to right, where some animation happen when a certain year is reached.
However the moving speed can be increased by the user, so fixed time to trigger animation is not an option.
There must be many ways to achieve what you want. The code below exploits jQuery's capability to handle custom events to provide a "loosely-coupled" observer pattern.
$(function() {
//Establish the two dozen functions that will be called.
var functionList = [
function() {...},
function() {...},
function() {...},
...
];
var gridParams = {offset:10, pitch:65};//Example grid parameters. Adjust as necessary.
//Establish a custom event and its handler.
var $myElement = $("#myID").data('lastIndex', -1).on('hasMoved', function() {
$element = $(this);
var pos = $element.position();//Position of the moved element relative to its offset parent.
var index = Math.floor((pos.left - gridParams.offset) / gridParams.pitch);//Example algorithm for converting pos.left to grid index.
if(index !== $element.data('lastIndex')) {//Has latest movement align the element with the next grid cell?
functionList[index](index, $element);//Call the selected function.
$element.data('lastIndex', index);//Remember index so it can be tested mext time.
}
});
});
$(function() {
//(Existing) function that moves the element must trigger the custom 'hasMoved' event after the postition has been changed.
function moveElement() {
...
...
...
myElement.trigger('hasMoved');//loosely coupled 'hasMoved' functionality.
}
var movementInterval = setInterval(moveElement, 100);
});
As you can see, an advantage of loose-coupling is that a function and the code that calls it can be in different scopes - .on('hasMoved', function() {...} and myElement.trigger('hasMoved') are in different $(function(){...}) structures.
If you wanted to add other functions to change the position of myElement (eg first, previous, next, last functions), then, after moving the element, they would each simply need to trigger 'hasMoved' to ensure that the appropriate one of your two dozen functions is called, without needing to worry about scopes.
The only thing you need to ensure is that your two dozen functions are scoped such that they can be called by the custom event handler (ie that they are in the same scope or an outer scope, up to and including the global scope).
I've had to make many assumptions, so the code above will not be 100% correct but hopefully it will provide you with a way ahead.
Related
I have a page with a lot of Div's, and I am using the panzoom jquery script. In order to speed up the page, I would like to disable the shadow on the divs if the page is zoomed out above a specified value, and enable the shadows if the page is zoomed in. currently I have this code, that is beeing called every 0.5 seconds:
function shadow()
{
var $panzoom = $('.panzoom').panzoom();
var matrix = $panzoom.panzoom("getMatrix");
$("#zoomf").html(matrix[0])
//if zoom is bigger than 0.35 enable shadows
if (matrix[0] > 0.35)
{
var $elements = $('.machinebox').addClass('shadow');
var $elements2 = $('.playerbox').addClass('shadow');
}
else
{
$elements.removeClass('shadow');
$elements2.removeClass('shadow');
}
}
The problems I am facing are basically two:
1) the var $elements does not exist Always, it only exists as long as the shadow exists.. thus, if I zoom out, it removes the shadow and then generates a error:
SCRIPT5007: Unable to get property 'removeClass' of undefined or null reference
de (282,3)
2) when it adds/removes the shadows it freezes for about one second. can this be avoided somehow?
The first problem is caused because the variables $element and $element2 are only declared and initialized when the condition is true. The second one seems to be caused because there are a lot of elements which have the classes machinebox and playerbox.
You can fix both of them by using something like this:
function shadow() {
var matrix = $('.panzoom').panzoom("getMatrix");
$("#zoomf").html(matrix[0])
$('.machinebox:visible, .playerbox:visible').toggleClass("shadow", (matrix[0] > 0.35))
}
For point 2 since you've solved point 1.
the function is running every 0.5 seconds, this is probably why you get that delay.
try pausing it until the next time the user zooms (idk if the library you're using allows you to detect such events)
I would like to know if there is any specific way to get javaScript code to stop executing at some point so as to allow another called function to execute, for example in a sorting algorithm using Div's you call a function which annimates the swapping of the two Div's so you can visually see the sort taking place. I have tried using the setTimeout(c,t); however it does not seem to be be waiting and the Divs do not seem to be moving, if I however place an alert(""); within the moving code it seems to allow the move to take place with thousands to alerts popping up.
The code I have is as follows:
var q;
var w;
function move(x,y)
{
q = x.style.top; // Keep a reference to the Top divs top element
w = y.style.top; // Keep a reference to the Top divs top element
doMove(x,y);
}
function doMove(topDiv, bottomDiv)
{
//alert("in doMove " + topDiv);
topDiv.style.top = parseInt(topDiv.style.top)+2+'px';
bottomDiv.style.top = parseInt(bottomDiv.style.top)-2+'px';
//alert("hi");
if(bottomDiv.style.top != q && topDiv.style.top != w) // Check if the Top and Bottom divs have finally swapped
{
setTimeout(doMove(topDiv,bottomDiv),20); // call doMove in 20msec
}
}
One problem with your code is that in this line:
setTimeout(doMove(topDiv,bottomDiv),20);
You are calling doMove immediately and passing the value undefined as the first parameter to setTimeout. Not entirely sure that the following will solve your problem, but I would recommend trying this:
setTimeout(function() { doMove(topDiv,bottomDiv); }, 20);
One of my script calls a function at some point, due to a 'changeCursor' event (I am using ACE editor). This slows down the movement of the cursor when I press it many times.
I really want this function to be called, but it's fine if it is only called once my cursor stopped moving (i.e. I do not need to see intermediary states).
Is there a standard way to have all but the last event ignored?
The classic way is to use a short timeout:
var cursorTimer;
function changeCursor() {
clearTimeout(cursorTimer);
cursorTimer = setTimeout(function() {
// process the actual cursor change here
}, 500);
}
Your regular code can continue calling changeCursor() every time it changes (just like it does now), but the actual code inside the setTimeout() will only execute when no cursor change events have occurred in the last 500ms. You can adjust that time value as desired.
The only way to know that events have stopped is to wait some short period of time and detect no further movement (which is what this does). It is common to use similar logic with scroll events.
It maybe overkill for this one issue, but check out RxJS: http://reactive-extensions.github.com/RxJS/#What is RxJS?
It adds some pretty powerful methods to "query/manipulate" event streams in JavaScript. https://github.com/Reactive-Extensions/RxJS/wiki/Observable
In this case the "throttle" method is what your after. Here is an example that uses Throttle with the keyup event to create auto-complete of wikipedia matches. https://github.com/Reactive-Extensions/RxJS-Examples/blob/master/autocomplete/autocomplete.js
// Get all distinct key up events from the input and only fire if long enough and distinct
var keyup = Rx.Observable.fromEvent(input, 'keyup').select(function (e) {
return e.target.value; // Project the text from the input
})
.where(function (text) {
return text.length > 2; // Only if the text is longer than 2 characters
})
.throttle(
750 // Pause for 750ms
)
.distinctUntilChanged(); // Only if the value has changed
I'm looking for an effecient way to constantly select the last element within the visible window/viewport.
So far, this is my code:
$(window).scroll(function () {
$('.post-content p').removeClass("temp last")
$('.post-content p').filter(":onScreen").addClass("temp")
$(".temp").eq(-1).addClass("last")
});
As you could probably imagine, this hauls up a lot of resources and doesn't perform very well. Can somebody please suggest from more elegant code?
My knowledge of Javascript is very basic, so please be patient with me. Thank you.
PS: I am using the onScreen plugin for the :onScreen selector: http://benpickles.github.com/onScreen/
Binding the scroll handler
Binding functions to the scroll Event can lead to serious performance problems. The scroll event fires really vigorously on page scroll, so binding functions with resource-heavy code to it is a bad idea.
What John suggests is setting up the interval and thereby having the code only execute some time after a scroll event.
Have a look at this jsfiddle to see difference between the implementations
The indirect handler solution comes at the cost of a noticeable lag between scrolling and executing the code, and it is your decision if you can trade in performance for snappier execution. Be sure to test performance on every browser you support.
Speeding up code execution
There are a lot of different concepts you can use to speed up your code. Regarding your code, it comes down to:
Caching selectors. You reselect elements every time the scroll handler fires, which is unnecessary
Not using jQuery plugins without knowing what they do. In your case, the plugin code is nice and quite straightforward, but for your goal you can have even snappier code.
preventing any unnecessary calculation. With your and the plugin's code, the offset of every element is calculated every time the scroll handler fires.
So what I've come up with is a Jsfiddle with an example how you could do you scroll handler. It's not exactly matched to your DOM because I don't know your html, but it should be easy to match it to your implementation.
I managed to reduce the time used by 95% compared to your code. You can see for yourself by profiling the two samples in chrome.
I assumed you just want to select the last element and you do not need the temp class
So, here's the code with explanations
// Store the offsets in an array
var offsets = [];
// Cache the elements to select
var elements = $('.elem');
// Cache the window jQuery Object
var jWindow = $(window);
// Cache the calculation of the window height
var jWindowHeight = jWindow.height();
// set up the variable for the current selected offset
var currentOffset;
// set up the variable for the current scrollOffset
var scrollOffset;
// set up the variable for scrolled, set it to true to be able to assign at
// the beginning
var scrolled = true;
// function to assign the different elements offsets,
// they don't change on scroll
var assignOffsets = function() {
elements.each(function() {
offsets.push({
offsetTop: $(this).offset().top,
height: $(this).height(),
element: $(this)
});
});
};
// execute the function once. Exectue it again if you added
// or removed elements
assignOffsets();
// function to assign a class to the last element
var assignLast = function() {
// only execute it if the user scrolled
if (scrolled) {
// assigning false to scrolled to prevent execution until the user
// scrolled again
scrolled = false;
// assign the scrolloffset
scrollOffset = jWindowHeight + jWindow.scrollTop();
// only execute the function if no current offset is set,
// or the user scrolled down or up enough for another element to be
// the last
if (!currentOffset || currentOffset.offsetTop < scrollOffset || currentOffset.offsetTop + currentOffset.height > scrollOffset) {
// Iterate starting from the bottom
// change this to positive iteration if the elements count below
// the fold is higher than above the fold
for (var i = offsets.length - 1; i >= 0; i--) {
// if the element is above the fold, reassign the current
// element
if (offsets[i].offsetTop + offsets[i].height < (scrollOffset)) {
currentOffset && (currentOffset.element.removeClass('last'));
currentOffset = offsets[i];
currentOffset.element.addClass('last');
// no further iteration needed and we can break;
break;
}
}
return true;
} else {
return false;
}
}
}
assignLast();
// reassign the window height on resize;
jWindow.on('resize', function() {
jWindowHeight = jWindow.height();
});
// scroll handler only doing assignment of scrolled variable to true
jWindow.scroll(function() {
scrolled = true;
});
// set the interval for the handler
setInterval(assignLast, 250);
// assigning the classes for the first time
assignLast();
I am working on coding for a situation where I need to construct a function of nested callbacks of an unknown length. It is to create a sequenced animation queue to move an element across an unknown # of positions.
For example, output would look something like this with X 'complete' callbacks nested inside:
$('#element').animate(css, { complete: function () {
$('#element').animate(css, { complete: function () {
// more nested calls inside
}
});
Right now I am generating these functions as a string, and then once completed, feeding it to new Function():
myFunc = new Function(generatedFuncString);
The content is trusted but this still uses eval() which has negative performance implications. I was just wondering if there is another/better way?
edit: The reason I am doing it this way is because I have a very complicated set of animations to perform and am working outside of the jQuery animation queue. If anyone has a better suggestion for how to accomplish a situation like this that would be helpful...
Imagine a baseball diamond with a runner(A) on 1st and a runner(B) on 3rd. In one animation bundle, I want to animate runner A to 3rd (stopping at 2nd in the middle, 2 advances), and runner B to HOME (1 advance).
I have to fire-off the initial advance with 'queue: false' so that runner A and B move to their first base at the same time (runner A to 2nd, runner B to home).
When Runner A is done moving to 2nd, I want to then move him to 3rd (hence constructing a animate() call with nested callbacks pro grammatically to ensure this sequencing is preserved).
The reason I am constructing the function via string is because I know what the inner-most callback is going to be first, and then recursively constructed 1 or more outer-callbacks from there. I couldn't figure out a way to do this by working with functions as objects and keeping all of the references in tact.
Keep in mind this is a simple example. Imagine a situation where the bases are loaded, and I need to animate a grand slam (all 4 runners circle all bases, runner originating at home needs to make 3 stops before running back to home). Etc etc.
Answering the question you ask in your title: You can create functions from strings via eval, new Function, and by inserting a script element with the text you want. But it all comes to the same thing: Firing up the JavaScript parser and creating the function.
But rather than nesting, I think you want chaining. Build a list of the animations in an array, and use the animate callback to call the next animation in the array. Something like:
var animations = [
/* css for first animation */,
/* css for second animation */,
/* etc. */
];
var index = 0;
function runAnimation() {
if (index < animations.length) {
$("#element").animate(animations[index++], runAnimation);
}
}
You'd build up the array dynamically, of course.
gdoron points out in the comments that if all of your animations are on the same element, it can be even simpler:
var animations = [
/* css for first animation */,
/* css for second animation */,
/* etc. */
];
var index = 0;
for (index = 0; index < animations.length; ++index) {
$("#element").animate(animations[index]);
}
...because when you call animate multiple times on the same element, by default the animations queue up (the queue option defaults to true; sadly the docs don't seem to say that). My code example above doesn't rely on the queue, and so in theory each entry in the array could be an object with a property for the selector for the elements to animate and the css to apply. But if it's all one element, you can just use a straight loop.