DOM onresize event - javascript

If I have this
window.onresize = function() {
alert('resized!!');
};
My function gets fired multiple times throughout the resize, but I want to capture the completion of the resize. This is in IE.
Any ideas? There are various ideas out there, but not has worked for me so far (example IE's supposed window.onresizeend event.)

In this case, I would strongly suggest debouncing. The most simple, effective, and reliable way to do this in JavaScript that I've found is Ben Alman's jQuery plugin, Throttle/Debounce (can be used with or without jQuery - I know... sounds odd).
With debouncing, the code to do this would be as simple as:
$(window).resize($.debounce(1000, function() {
// Handle your resize only once total, after a one second calm.
...
}));
Hope that can help someone. ;)

I always use this when I want to do something after resizing. The calls to setTimeout and clearTimeout are not of any noticable impact on the speed of the resizing, so it's not a problem that these are called multiple times.
var timeOut = null;
var func = function() { /* snip, onresize code here */};
window.onresize = function(){
if(timeOut != null) clearTimeout(timeOut);
timeOut = setTimeout(func, 100);
}

This is not perfect but it should give you the start you need.
var initialX = null;
var initialY = null;
var lastResize = null;
var waiting = false;
var first = true;
var id = 0;
function updateResizeTime()
{
if (initialX === event.clientX && initialY === event.clientY)
{
return;
}
initialX = event.clientX;
initialY = event.clientY;
if (first)
{
first = false;
return;
}
lastResize = new Date();
if (!waiting && id == 0)
{
waiting = true;
id = setInterval(checkForResizeEnd, 1000);
}
}
function checkForResizeEnd()
{
if ((new Date()).getTime() - lastResize.getTime() >= 1000)
{
waiting = false;
clearInterval(id);
id = 0;
alert('hey!');
}
}
window.onresize = function()
{
updateResizeTime();
}

You get multiple events because there really are multiple events. Windows animates the resize by doing it several times as you drag the window (by default, you can change it in the registry I think).
What you could do is add a delay. Do a clearTimeout, setTimout(myResize,1000) every time the IE event fires. Then, only the last one will do the actual resize.

simple pure javascript solution, just change the 1000 int value to be lower for more responsiveness
var resizing = false;
window.onresize = function() {
if(resizing) return;
console.log("resize");
resizing = true;
setTimeout(function() {resizing = false;}, 1000);
};

Not sure if this might help, but since it seems to be working perfectly, here it is.
I have taken the snippet from the previous post and modified it slightly. The function doCenter() first translates px to em and than substracts the width of the object and divides the remainder by 2. The result is assigned as left margin. doCenter() is executed to center the object. timeout fires when the window is resized executing doCenter() again.
function doCenter() {
document.getElementById("menuWrapper").style.position = "fixed";
var getEM = (window.innerWidth * 0.063);
document.getElementById("menuWrapper").style.left = (getEM - 40) / 2 + "em";
}
doCenter();
var timeOut = null;
var func = function() {doCenter()};
window.onresize = function(){
if (timeOut != null) clearTimeout(timeOut);
timeOut = setTimeout(func, 100);
};

I liked Pim Jager's elegant solution, though I think that there's an extra paren at the end and I think that maybe the setTimeout should be "timeOut = setTimeout(func,100);"
Here's my version using Dojo (assuming a function defined called demo_resize())...
var _semaphorRS = null;
dojo.connect(window,"resize",function(){
if (_semaphorRS != null) clearTimeout(_semaphorRS);
_semaphorRS = setTimeout(demo_resize, 500);
});
Note: in my version the trailing paren IS required.

Related

How to overwrite/cancel previous promise called one by one?

I have jQuery code that respond to user keyboard press and I'm checking if element is in view. The plugin return promise if user press the key very fast or hold it (right now it's too fast for my code) it create promise in kind of loop. I want to cancel previous promise before I call new one.
I came up with this code that ignores previous promise:
var scroll_callback_counter = 0;
function move_cursor_visible() {
var cursor = self.find('.cursor');
var i = scroll_callback_counter++;
return cursor.is_fully_in_viewport(self).then(function(visible) {
if (i === scroll_callback_counter && !visible) {
var offset = cursor.offset();
var container_offset = self.offset();
self.scrollTop(offset.top - container_offset.top - 5);
return true;
}
});
}
Is there a better way? It works but I don't know if this is correct way to create code that cancel/ignore promise callback.
EDIT:
Here is is_fully_in_viewport function:
function jquery_resolve(value) {
var defer = jQuery.Deferred();
defer.resolve(value);
return defer.promise();
}
$.fn.is_fully_in_viewport = (function() {
function is_visible(node, container) {
var box = node.getBoundingClientRect();
var viewport = container[0].getBoundingClientRect();
var top = box.top - viewport.top;
var bottom = box.bottom - viewport.top;
var height = container.height();
return bottom > 0 && top <= height;
}
if (window.IntersectionObserver) {
return function(container) {
var node = this[0];
var defer = jQuery.Deferred();
var item_observer = new window.IntersectionObserver(function(entries) {
defer.resolve(entries[0].isIntersecting);
item_observer.unobserve(node);
}, {
root: container[0]
});
item_observer.observe(node);
return defer.promise();
};
} else {
return function(container) {
return jquery_resolve(is_visible(this[0], container));
};
}
})();
Creates a debounced function that delays invoking func until after
wait milliseconds have elapsed since the last time the debounced
function was invoked.
You can use debounce method of loadash,
_.debounce(func, [wait=0], [options={}])
The debounced function comes with a cancel method to cancel delayed
func invocations and a flush method to immediately invoke them

How to make a javascript function that will stop running after 20 seconds? [duplicate]

I'm trying to make a simple game using HTML5 (Javascript). I want to put time constraints on events. For example, when a player enters a room where the roof is closing in on them, I want to give them some seconds to make a decision before automatically having some other event happen. But, if they make a decision, I don't want the timed function to fire at all.
How can I accomplish something like this?
For this, I like to use a custom Timer class
var Timer = function(callback, delay, autoRun){
this.running = (autoRun == undefined)?true:autoRun;
this.completed = false;
this.delay = delay;
this.callback = callback;
};
Timer.prototype.pause = function() {
this.running = false;
return this;
};
Timer.prototype.resume = function() {
this.running = true;
return this;
};
Timer.prototype.finish = function(){
this.running = false;
this.completed = true;
this.callback();
return this;
};
You simply create a new timer, add it to a list of timers that get updated by a fixed amount with your main draw loop (20ms is good). So users don't get penalized if your game is lagging ;)
Ex:
var listOfTimers = [];
function draw(){
//Called every frame
for(var i = 0; i<listOfTimers.length; i++){
if(listOfTimers[i].running){
listOfTimers[i].delay -= 20;
if(listOfTimers[i].delay <= 0){
listOfTimers[i].finish();
}
}
//The rest of your draw logic...
}

Ways in Javascript/jQuery/etc to measure time elapsed / trigger events after periods of time

I'm trying to make a simple game using HTML5 (Javascript). I want to put time constraints on events. For example, when a player enters a room where the roof is closing in on them, I want to give them some seconds to make a decision before automatically having some other event happen. But, if they make a decision, I don't want the timed function to fire at all.
How can I accomplish something like this?
For this, I like to use a custom Timer class
var Timer = function(callback, delay, autoRun){
this.running = (autoRun == undefined)?true:autoRun;
this.completed = false;
this.delay = delay;
this.callback = callback;
};
Timer.prototype.pause = function() {
this.running = false;
return this;
};
Timer.prototype.resume = function() {
this.running = true;
return this;
};
Timer.prototype.finish = function(){
this.running = false;
this.completed = true;
this.callback();
return this;
};
You simply create a new timer, add it to a list of timers that get updated by a fixed amount with your main draw loop (20ms is good). So users don't get penalized if your game is lagging ;)
Ex:
var listOfTimers = [];
function draw(){
//Called every frame
for(var i = 0; i<listOfTimers.length; i++){
if(listOfTimers[i].running){
listOfTimers[i].delay -= 20;
if(listOfTimers[i].delay <= 0){
listOfTimers[i].finish();
}
}
//The rest of your draw logic...
}

JQuery: How to call RESIZE event only once it's FINISHED resizing?

How do I call a function once the browser windows has FINISHED resizing?
I'm trying to do it like so, but am having problems. I'm using the JQuery Resize event function:
$(window).resize(function() {
... // how to call only once the browser has FINISHED resizing?
});
However, this function is called continuously if the user is manually resizing the browser window. Which means, it might call this function dozens of times in short interval of time.
How can I call the resize function only a single time (once the browser window has finished resizing)?
UPDATE
Also without having to use a global variable.
Here is an example using thejh's instructions
You can store a reference id to any setInterval or setTimeout. Like this:
var loop = setInterval(func, 30);
// some time later clear the interval
clearInterval(loop);
Debounce.
function debouncer( func , timeout ) {
var timeoutID , timeout = timeout || 200;
return function () {
var scope = this , args = arguments;
clearTimeout( timeoutID );
timeoutID = setTimeout( function () {
func.apply( scope , Array.prototype.slice.call( args ) );
} , timeout );
}
}
$( window ).resize( debouncer( function ( e ) {
// do stuff
} ) );
Note, you can use this method for anything you want to debounce (key events etc).
Tweak the timeout parameter for optimal desired effect.
You can use setTimeout() and clearTimeout() in conjunction with jQuery.data:
$(window).resize(function() {
clearTimeout($.data(this, 'resizeTimer'));
$.data(this, 'resizeTimer', setTimeout(function() {
//do something
alert("Haven't resized in 200ms!");
}, 200));
});
Update
I wrote an extension to enhance jQuery's default on (& bind)-event-handler. It attaches an event handler function for one or more events to the selected elements if the event was not triggered for a given interval. This is useful if you want to fire a callback only after a delay, like the resize event, or else.
https://github.com/yckart/jquery.unevent.js
;(function ($) {
var methods = { on: $.fn.on, bind: $.fn.bind };
$.each(methods, function(k){
$.fn[k] = function () {
var args = [].slice.call(arguments),
delay = args.pop(),
fn = args.pop(),
timer;
args.push(function () {
var self = this,
arg = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(self, [].slice.call(arg));
}, delay);
});
return methods[k].apply(this, isNaN(delay) ? arguments : args);
};
});
}(jQuery));
Use it like any other on or bind-event handler, except that you can pass an extra parameter as a last:
$(window).on('resize', function(e) {
console.log(e.type + '-event was 200ms not triggered');
}, 200);
http://jsfiddle.net/ARTsinn/EqqHx/
var lightbox_resize = false;
$(window).resize(function() {
console.log(true);
if (lightbox_resize)
clearTimeout(lightbox_resize);
lightbox_resize = setTimeout(function() {
console.log('resize');
}, 500);
});
Just to add to the above, it is common to get unwanted resize events because of scroll bars popping in and out, here is some code to avoid that:
function registerResize(f) {
$(window).resize(function() {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(function() {
var oldOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
var currHeight = $(window).height(),
currWidth = $(window).width();
document.body.style.overflow = oldOverflow;
var prevUndefined = (typeof this.prevHeight === 'undefined' || typeof this.prevWidth === 'undefined');
if (prevUndefined || this.prevHeight !== currHeight || this.prevWidth !== currWidth) {
//console.log('Window size ' + (prevUndefined ? '' : this.prevHeight + "," + this.prevWidth) + " -> " + currHeight + "," + currWidth);
this.prevHeight = currHeight;
this.prevWidth = currWidth;
f(currHeight, currWidth);
}
}, 200);
});
$(window).resize(); // initialize
}
registerResize(function(height, width) {
// this will be called only once per resize regardless of scrollbars changes
});
see jsfiddle
Underscore.js has a couple of great methods for this task: throttle and debounce. Even if you're not using Underscore, take a look at the source of these functions. Here's an example:
var redraw = function() {'redraw logic here'};
var debouncedRedraw = _.debounce(redraw, 750);
$(window).on('resize', debouncedRedraw);
This is my approach:
document.addEventListener('DOMContentLoaded', function(){
var tos = {};
var idi = 0;
var fn = function(id)
{
var len = Object.keys(tos).length;
if(len == 0)
return;
to = tos[id];
delete tos[id];
if(len-1 == 0)
console.log('Resize finished trigger');
};
window.addEventListener('resize', function(){
idi++;
var id = 'id-'+idi;
tos[id] = window.setTimeout(function(){fn(id)}, 500);
});
});
The resize-event-listener catches all incoming resize calls, creates a timeout-function for each and saves the timeout-identifier along with an iterating number prepended by 'id-' (to be usable as array key) in the tos-array.
each time, the timout triggers, it calls the fn-function, that checks, if that was the last timeout in the tos array (the fn-function deletes every executed timout). if true (= if(len-1 == 0)), the resizing is finished.
jQuery provides an off method to remove event handler
$(window).resize(function(){
if(magic == true) {
$(window).off('resize', arguments.callee);
}
});

How to stop all timeouts and intervals using javascript? [duplicate]

This question already has answers here:
javascript: Clear all timeouts?
(13 answers)
Closed 6 years ago.
I'm working on an ajax web appliation which contains many running timeouts and intervals. And now I need to clear all running timeouts and intervals sometimes. Is there a simple way to stop everything without need to store every timeout and interval ID and iterate through them and clear them?
Sometimes it's possible to save the timer Id / Handle to clear it later which would be the best solution. So this is a second best. But I wanted to give a better understanding of what's going on. It basically grabs the highest timer id and clears everything less than that. But it's also possible to clear other timers that you do not want to clear!
It is a little hackish, so be warned!
// Set a fake timeout to get the highest timeout id
var highestTimeoutId = setTimeout(";");
for (var i = 0 ; i < highestTimeoutId ; i++) {
clearTimeout(i);
}
Updated answer after reading the duplicate I closed this question with -
It works and tested in Chrome on OSX
// run something
var id1 = setInterval(function() { console.log("interval", new Date())}, 1000);
var id2 = setTimeout(function() { console.log("timeout 1", new Date())}, 2000);
var id3 = setTimeout(function() { console.log("timeout 2", new Date())}, 5000); // not run
setTimeout(function() { console.log("timeout 3", new Date())}, 6000); // not run
// this will kill all intervals and timeouts too in 3 seconds.
// Change 3000 to anything larger than 10
var killId = setTimeout(function() {
for (var i = killId; i > 0; i--) clearInterval(i)
}, 3000);
console.log(id1, id2, id3, killId); // the IDs set by the function I used
NOTE: Looked at window objects that had a typeof number - funnily enough IE assigns an 8 digit number, FF a single digit starting with 2
Here is a workaround.
window.timeoutList = new Array();
window.intervalList = new Array();
window.oldSetTimeout = window.setTimeout;
window.oldSetInterval = window.setInterval;
window.oldClearTimeout = window.clearTimeout;
window.oldClearInterval = window.clearInterval;
window.setTimeout = function(code, delay) {
var retval = window.oldSetTimeout(code, delay);
window.timeoutList.push(retval);
return retval;
};
window.clearTimeout = function(id) {
var ind = window.timeoutList.indexOf(id);
if(ind >= 0) {
window.timeoutList.splice(ind, 1);
}
var retval = window.oldClearTimeout(id);
return retval;
};
window.setInterval = function(code, delay) {
var retval = window.oldSetInterval(code, delay);
window.intervalList.push(retval);
return retval;
};
window.clearInterval = function(id) {
var ind = window.intervalList.indexOf(id);
if(ind >= 0) {
window.intervalList.splice(ind, 1);
}
var retval = window.oldClearInterval(id);
return retval;
};
window.clearAllTimeouts = function() {
for(var i in window.timeoutList) {
window.oldClearTimeout(window.timeoutList[i]);
}
window.timeoutList = new Array();
};
window.clearAllIntervals = function() {
for(var i in window.intervalList) {
window.oldClearInterval(window.intervalList[i]);
}
window.intervalList = new Array();
};
It works for set/clear timeout/interval functions called after these lines are executed. Try and see it works:
setInterval('console.log(\'a\')', 1000);
setInterval('console.log(\'b\')', 500);
setInterval('console.log(\'c\')', 750);
setTimeout('clearAllIntervals()', 10000);
Proxying does the magic.
var noofTimeOuts = setTimeout('');
for (var i = 0 ; i < noofTimeOuts ; i++) clearTimeout(i);
var max = setTimeout(function(){ /* Empty function */ },1);
for (var i = 1; i <= max ; i++) {
window.clearInterval(i);
window.clearTimeout(i);
if(window.mozCancelAnimationFrame)window.mozCancelAnimationFrame(i); // Firefox
}
There's nothing built-in, but it's pretty easy to blast through all currently outstanding deferred execution functions by calling this clearAll() function:
function clearAll() {
for (var i = setTimeout(function() {}, 0); i > 0; i--) {
window.clearInterval(i);
window.clearTimeout(i);
if (window.cancelAnimationFrame) window.cancelAnimationFrame(i);
}
}
If you are in charge of the page you run, and can wrap the native deferred execution functions in wrappers that do the house keeping for of course equip each setter function with a corresponding .clearAll() too:
(function(deferFunctions) {
for (var setter in deferFunctions) (function(setter, clearer) {
var ids = [];
var startFn = window[setter];
var clearFn = window[clearer];
function clear(id) {
var index = ids.indexOf(id);
if (index !== -1) ids.splice(index, 1);
return clearFn.apply(window, arguments);
}
function set() {
var id = startFn.apply(window, arguments);
ids.push(id);
return id;
}
set.clearAll = function() { ids.slice(0).forEach(clear); };
if (startFn && clearFn) {
window[setter] = set;
window[clearer] = clear;
}
})(setter, deferFunctions[setter]);
})(
{ setTimeout: 'clearTimeout'
, setInterval: 'clearInterval'
, requestAnimationFrame: 'cancelAnimationFrame'
});
To try that it works, you could then try doing this, for instance, which will remain silent, as none of the callbacks end up firing before they're cancelled again:
// Some example timers of all types:
requestAnimationFrame(console.error);
setInterval(console.info, 1000, 'interval');
setTimeout(alert, 0, 'timeout');
// Now you can clear all deferred functions
// by execution type, whenever you want to:
window.setTimeout.clearAll();
window.setInterval.clearAll();
window.requestAnimationFrame.clearAll();
A little hack added to Gokhan Ozturk's answer
If you are using third party libraries which uses Timeouts and Intervals then they will also be cleared, so I added one parameter to notify function that this interval is to be push'ed or not to array.
window.setTimeout = function(code, delay, toBeAdded) {
var retval = window.oldSetTimeout(code, delay);
var toBeAdded = toBeAdded || false;
if(toBeAdded) {
window.timeoutList.push(retval);
}
return retval;
};
... // likewise for all functions.
You might be better off creating a scheduler. Take a look at this approach by Nader Zeid:
https://www.onsip.com/blog/avoiding-javascript-settimeout-and-setinterval-problems
It's an approach that help create some determinacy (because "the time interval argument of each of those functions really only establishes that the given function will execute after at least that amount of time. So a timed event can miss its target by literally any amount of time.").
Specifically, to the question you raise here, you can easily add and remove functions from the queue. While this response is long after the question was raised, hopefully it's helpful to any who find themselves struggling with Timeouts and Intervals.
You cannot clear any timeouts and intervals you don't know about.
You'd need something like getTimeoutList which isn't in the DOM3 spec, or even planned, AFAIK.
The previous proxying trick is nice, but if you have a lot of timeouts and intervals, I would not fill the arrays with consecutive numbers [1,2,3....], but with intervals. For example, instead of having [1,2,3,7,8,9], you would have maybe something like ['1-3','7-9'] or [[1,3],[7,9]], as a memory optimization. Of course this trick is only suited if you have a lot of timeouts and intervals and also if you would not stop arbitrary intervals that often.

Categories

Resources