Recall JS function on window resize - javascript

I am extremely new to JS, any help with this question is greatly appreciated.
I have the following bit of JS:
var isMobile = false;
if(Modernizr.mq('only all and (max-width: 1024px)') ) {
isMobile = true;
}
if (isMobile === false)
/* desired function: */
slider.ev.on('rsAfterSlideChange', playSlideVideo);
playSlideVideo();
}
Which indeed works as intended if the browser window loads below or above max-width: 1024px. The problem is that I would like my functions to run or stop if the user changes the size of their window after the page loads.
How can I go about setting that up?
This is amazing, thank you! Specially for explaining commenting your code along the way. Im 80% there. the script works when going from small window to big, but does not work from big to small. any thoughts on how I can fix? This is all of my code:
var slider = $(".royalSlider").data('royalSlider'),
prevSlideVideo,
playSlideVideo = function() {
if(prevSlideVideo) {
prevSlideVideo.pause();
}
var video = slider.currSlide.content.find('video');
if(video.length) {
prevSlideVideo = video[0];
prevSlideVideo.play();
} else {
prevSlideVideo = null;
}
};
var $window = $(window); // have a reference to window
var isMobile = false; // boolean to check for mobile
var timer = 0;
function updateSlider() {
if (!isMobile) {
/* desired shenanigans: */
slider.ev.on('rsAfterSlideChange', playSlideVideo);
playSlideVideo();
}
}
$window.on("resize.slider", function() {
// delay execution to 300ms after the last resize event
timer = setTimeout(function() {
clearTimeout(timer);
isMobile = Modernizr.mq('only all and (max-width: 1024px)'); // update isMobile with mq value
updateSlider(); // run function on resize
}, 300);
}).trigger("resize.slider"); // trigger this event so you dont have to explictly call updateSlider();

update
I would doubt the isMobile = Modernizr.mq('only all and (max-width: 1024px)'); part (even though it seems fine)
Also, it appears that you're assigning events to slider widget. There could be a case where multiple resize events would end up attaching multiple events 'rsAfterSlideChange' to the slider.
Looking at the api there doesn't seem to be an off method.
So, keeping above in mind. could you try something like below:
var $window = $(window); // have a reference to window
var isMobile = false; // boolean to check for mobile
var timer = 0;
var slider = $(".royalSlider").data('royalSlider'); // slider instance
var prevSlideVideo;
var playSlideVideo = function() {
if(prevSlideVideo) {
prevSlideVideo.pause();
}
var video = slider.currSlide.content.find('video');
if(video.length) {
prevSlideVideo = video[0];
prevSlideVideo.play();
} else {
prevSlideVideo = null;
}
};
// Looking at the [api](http://dimsemenov.com/plugins/royal-slider/documentation/#api) there doesn't seem to be an ```off``` method.
// So it is probably safe to attach event only once.
slider.ev.on('rsAfterSlideChange', playSlideVideo);
$window.on("resize.slider", function() {
// delay execution to 300ms after the last resize event
timer = setTimeout(function() {
clearTimeout(timer);
// quick and dirty check of window width instead of match media, this is unreliable but should do the job for now
if ($window.width() < 1024) {
playSlideVideo();
}
// updateSlider(); // dont need this anymore as you're calling ```playSlideVideo()``` straight away
}, 300);
}).trigger("resize.slider"); // trigger this event so you dont have to explictly call updateSlider();
There are many ways to do it.
Heres a quick way:
var $window = $(window); // have a reference to window
var isMobile = false; // boolean to check for mobile
function updateSlider() {
if (!isMobile) {
/* desired shenanigans: */
slider.ev.on('rsAfterSlideChange', playSlideVideo);
playSlideVideo();
}
}
$window.on("resize.slider", function() {
isMobile = Modernizr.mq('only all and (max-width: 1024px)'); // update isMobile with mq value
updateSlider(); // run function on resize
}).trigger("resize.slider"); // trigger this event so you don't have to explicitly call updateSlider();
Interesting thing with resize events is that they will fire many times when window is resized, there are many ways to throttle this event, below is one way
var $window = $(window); // have a reference to window
var isMobile = false; // boolean to check for mobile
var timer = 0;
function updateSlider() {
if (!isMobile) {
/* desired shenanigans: */
slider.ev.on('rsAfterSlideChange', playSlideVideo);
playSlideVideo();
}
}
$window.on("resize.slider", function() {
// delay execution to 300ms after the last resize event
timer = setTimeout(function() {
clearTimeout(timer);
isMobile = Modernizr.mq('only all and (max-width: 1024px)'); // update isMobile with mq value
updateSlider(); // run funtion on resize
}, 300);
}).trigger("resize.slider"); // trigger this event so you dont have to explictly call updateSlider();
I haven't tested above code (maybe provide a fiddle?) but should put you in right direction. Let me know if anything is unclear.
Good luck

Related

Sticky element issue on mobile safari

I have an element which I wish to stick to the top of the page when scrolling down. Functionally all of the code works thanks to another user on SO. However when scrolling down on the phone it seems that the sticky element lags behind by a bit. What I mean is the code seems to be calling every single time the parent element is scrolling and it causes hundreds or thousands of adjustments to the sticky element so it causes it to shake a bit.
Here is the code below:
HTML
<div id="scroller-wrapper">
<div id="scroller-anchor"></div>
<div id="scroller" class="row visible-xs-block meal-controls">
My sticky element is here and working
</div>
</div>
JS
$('#scroller-wrapper').scroll(function() {
var $anchor = $("#scroller-anchor");
var $scroller = $('#scroller');
var move = function() {
var st = $(window).scrollTop();
var ot = $anchor.offset().top;
if(st > ot) {
$scroller.addClass('fixedElement');
} else {
$scroller.removeClass('fixedElement');
}
};
$(window).scroll(move);
move();
});
CSS
.fixedElement {
position:fixed;
top:0;
right:0;
width:100%;
z-index:10000;
}
IMO, a possible and more effective solution would be to use position: sticky in CSS and not JS. You need to provide top: 0 as well. Some compatibility is lagging in IE, but it is a viable solution already. Worth to check it out here
If you are worried about old browsers you may add a fallback function in JS, which still be somewhat laggy
what you need to do is to throttle or debounce the call to update the element.
also why are you attaching a scroll listener to window inside of your wrapper scroll handler? that will mean that EVERY time that scroll listener is called, it will attach ANOTHER scroll listener to window.
all you need is the single handler on window, and allow propagation to do the rest.
// A debounce function wraps a function with a setTimeout,
// and then resets that timeout everytime it is called
function debounce(func, delay){
var timeout, that = this;
delay = delay || 300;
return function() {
if(timeout) clearTimeout(timeout)
timeout = setTimeout(function() {
return func.apply(that, arguments)
}, delay)
}
}
// a throttle function ensures that a function isn't
// called more than once every interval
function throttle(fn, interval, shouldDebounce){
var lastCall = 0, debouncedFn;
interval = interval || 300
if(shouldDebounce) debouncedFn = debounce(fn, interval);
return function(){
var now = (new Date()).getTime();
if(now - lastCall < interval)
return debouncedFn && debouncedFn.apply(this, arguments);
lastCall = now;
return fn.apply(this, arguments);
}
}
// create a function to set scroll listeners
function setScroller() {
var $anchor = $("#scroller-anchor"),
$scroller = $('#scroller'),
onMove = function onMove() {
var st = $(window).scrollTop(),
ot = $anchor.offset().top;
if(st > ot) {
$scroller.addClass('fixedElement');
} else {
$scroller.removeClass('fixedElement');
}
},
// Throttle the onMove function to make sure it isn't called too often
throttlededOnMove = throttle(onMove, 300);
$(window).scroll(throttlededOnMove);
}
// attach scroll listener on document ready
$(setScroller)

Responsive JavaScript: Attempts to Fire Functions based on Media Queries

Third day I'm trying to implement media queries in JavaScript.
Say function A() can be called only if (min-width: 768px),
and the function B() can be called only if (max-width: 767px).
This is easily achieved by using MediaQueryList object. But problems occur with browser resizing.
A function A() can not be called if the page was loaded on
(max-width: 767px), and then resizing to (min-width: 768px).
A function A() fires multiple times on click if I try call function on window resize.
I have tried different solutions:
using addListener
enquire.js
setTimeout / clearTimeout — http://go.shr.lc/1kGNpM6
etc
But obviously my knowledge of JavaScript is not enough to do things write. Please help
// Attempt #1 -----------------------------------------------------------------
function responsiveFunction(){
if(window.matchMedia('(max-width: 767px)').matches) {
$('.btn').click(function(event) {
// Knock knock
});
}
}
$(function(){
responsiveFunction();
});
$(window).resize(function(){
responsiveFunction();
});
// Attempt #2 -----------------------------------------------------------------
function responsiveFunction(mql) {
    if (mql.matches) {
     $('.btn').click(function(event) {
// Knock knock
});
    }
}
 
var mql = window.matchMedia('min-width: 768px'); // MQL for MediaQueryList object
 
mql.addListener(responsiveFunction); // Execute responsive function on resize
 
responsiveFunction(mql); // Execute responsive function on load
// Attempt #3 -----------------------------------------------------------------
var smartResize = (function() {
var timers = {};
return function(callback, ms, uniqueId) {
if (!uniqueId) {
uniqueId = 'Don\'t call this twice without a uniqueId';
}
if (timers[uniqueId]) {
clearTimeout(timers[uniqueId]);
}
timers[uniqueId] = setTimeout(callback, ms);
};
})();
function responsiveFunction() {
if (window.matchMedia('(min-width: 768px)').matches) {
$('.btn').click(function(event) {
// Knock knock
});
}
}
// Execute responsive function on load
responsiveFunction();
// Execute responsive function on resize
$(window).resize(function() {
smartResize(function() {
responsiveFunction();
}, 500, 'myUniqueId');
});
// Attempt #4 w enquire.min.js ---------------------------------------------
enquire.register('(min-width: 768px)', {
match: function() {
$('.btn').click(function(event) {
// Knock knock
});
}
});
This should work:
$(function(){
$('.btn').on('click', function(event) {
if(window.matchMedia('(max-width: 767px)').matches) {
// Only run the code if media query matches
}
});
});
Register the click handler without checking the max-width and check the width just before you run the code, if the width condition matches then run the code otherwise doesn't run the code.

passing a global javascript variable into jquery plugins on resize

I am creating a mobile first responsive web site that has a few custom jquery plugins which kick in at certain breakpoints.
First, snippets of the code, and then ill explain the issue...
CSS:
#media (min-width: 20em) {
html:after {
content: "320";
} }
#media (min-width: 25em) {
html:after {
content: "400";
} }
#media (min-width: 30em) {
html:after {
content: "480";
} }
GLOBAL JS
var FOO = {
mq: function() {
return parseInt(window.getComputedStyle(document.documentElement,':after').getPropertyValue('content').replace(/['"]/g,''),10);
}
}
JQUERY PLUGIN 1
this.init = function()
{
$(window).on("load resize orientationchange", function() {
if(FOO.mq() >= 480) {
DO SOMETHING
}
});
};
JQUERY PLUGIN 2
this.init = function()
{
$(window).on("load resize orientationchange", function() {
if(FOO.mq() >= 560) {
DO SOMETHING ELSE
}
});
};
Now the problem with putting the FOO.mq() in the jquery plugin is that it will fire the FOO.mq() function for every DOM object found (e.g. if the plugin does something to 10 images it will fire the FF.mq() 10 times) where really thats not needed as we only need to check once.
I have tried putting the resize/return value in a global object but it nevers seems to work and gets picked up bu the jquery plugins.
Any pointers would be greatly appreciated.
Thanks
Assuming that this calculated value only changes on document load, or if the window changes layout:
var FOO = FOO || {}; // create global namespace obj, if required
$(function() {
var mq; // scoped variable
// catch events that might change the #media CSS rules
$(window).on("load resize orientationchange", function() {
mq = parseInt(window.getComputedStyle(document.documentElement,':after').getPropertyValue('content').replace(/['"]/g,''),10);
});
FOO.mq = function() { // function that just returns the scoped variable
return mq;
}
});
Try to use a timeout:
this.init = function () {
var timeout;
$(window).on("load resize orientationchange", function () {
clearTimeout(timeout);
timeout = setTimeout(function(){
if (FOO.mq() >= 480) {
DO SOMETHING
}
},15);//even 0 should be enough
});
};

window.onresize fires twice

I'm new to js. Please, don't kick painfully.
I have this code
window.onresize=function() {alert(1);};
When I resize any browser`s window, this function fires twice. Why? And how to rewrite this code that code will fire once.
Thanx in advance.
You need a timeout to bundle the resize events.
var res;
window.onresize=function() {
if (res){clearTimeout(res)};
res = setTimeout(function(){console.log("resize triggered");},100);
};
live Example
This event will fire multiple times in different browsers (some once you've finished the the resize, others during).
One way to get around this is to wait a certain amount of time (say half a second) after the event fires, to see if there are further updates. If not, you can proceed with the alert.
e.g.
var resizeTimer;
window.onresize = function(){
if (resizeTimer){
clearTimeout(resizeTimer);
}
resizeTimer = setTimeout(function(){
alert(1);
}, 500);
};
See it work on this fiddle.
To prevent function from "firing" the same result more than once when user resize
var doc = document; //To access the dom only once
var cWidth = doc.body.clientWidth;
var newWidth = cWidth; //If you want a log to show at startup, change to: newWidth = 0
window.onresize = function (){
newWidth = doc.body.clientWidth;
if(cWidth != newWidth){
cWidth = newWidth;
console.log("clientWidth:", cWidth); //instead of alert(cWidth);
};
};
I propose other solution because I don't like the timeouts,
`var resized;
window.onresize = function () {
if(resized){
resized = false;
}
else{
resized = true;
alert('resize');
}
};`

Event when user stops scrolling

I'd like to do some fancy jQuery stuff when the user scrolls the page. But I have no idea how to tackle this problem, since there is only the scroll() method.
Any ideas?
You can make the scroll() have a time-out that gets overwritten each times the user scrolls. That way, when he stops after a certain amount of milliseconds your script is run, but if he scrolls in the meantime the counter will start over again and the script will wait until he is done scrolling again.
Update:
Because this question got some action again I figured I might as well update it with a jQuery extension that adds a scrollEnd event
// extension:
$.fn.scrollEnd = function(callback, timeout) {
$(this).on('scroll', function(){
var $this = $(this);
if ($this.data('scrollTimeout')) {
clearTimeout($this.data('scrollTimeout'));
}
$this.data('scrollTimeout', setTimeout(callback,timeout));
});
};
// how to call it (with a 1000ms timeout):
$(window).scrollEnd(function(){
alert('stopped scrolling');
}, 1000);
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<div style="height: 200vh">
Long div
</div>
Here is a simple example using setTimeout to fire a function when the user stops scrolling:
(function() {
var timer;
$(window).bind('scroll',function () {
clearTimeout(timer);
timer = setTimeout( refresh , 150 );
});
var refresh = function () {
// do stuff
console.log('Stopped Scrolling');
};
})();
The timer is cleared while the scroll event is firing. Once scrolling stops, the refresh function is fired.
Or as a plugin:
$.fn.afterwards = function (event, callback, timeout) {
var self = $(this), delay = timeout || 16;
self.each(function () {
var $t = $(this);
$t.on(event, function(){
if ($t.data(event+'-timeout')) {
clearTimeout($t.data(event+'-timeout'));
}
$t.data(event + '-timeout', setTimeout(function () { callback.apply($t); },delay));
})
});
return this;
};
To fire callback after 100ms of the last scroll event on a div (with namespace):
$('div.mydiv').afterwards('scroll.mynamespace', function(e) {
// do stuff when stops scrolling
$(this).addClass('stopped');
}, 100
);
I use this for scroll and resize.
Here is another more generic solution based on the same ideas mentioned:
var delayedExec = function(after, fn) {
var timer;
return function() {
timer && clearTimeout(timer);
timer = setTimeout(fn, after);
};
};
var scrollStopper = delayedExec(500, function() {
console.log('stopped it');
});
document.getElementById('box').addEventListener('scroll', scrollStopper);
I had the need to implement onScrollEnd event discussed hear as well.
The idea of using timer works for me.
I implement this using JavaScript Module Pattern:
var WindowCustomEventsModule = (function(){
var _scrollEndTimeout = 30;
var _delayedExec = function(callback){
var timer;
return function(){
timer && clearTimeout(timer);
timer = setTimeout(callback, _scrollEndTimeout);
}
};
var onScrollEnd = function(callback) {
window.addEventListener('scroll', _delayedExec(callback), false);
};
return {
onScrollEnd: onScrollEnd
}
})();
// usage example
WindowCustomEventsModule.onScrollEnd(function(){
//
// do stuff
//
});
Hope this will help / inspire someone
Why so complicated? As the documentation points out, this http://jsfiddle.net/x3s7F/9/ works!
$('.frame').scroll(function() {
$('.back').hide().fadeIn(100);
}
http://api.jquery.com/scroll/.
Note: The scroll event on Windows Chrome is differently to all others. You need to scroll fast to get the same as result as in e.g. FF. Look at https://liebdich.biz/back.min.js the "X" function.
Some findings from my how many ms a scroll event test:
Safari, Mac FF, Mac Chrome: ~16ms an event.
Windows FF: ~19ms an event.
Windows Chrome: up to ~130ms an event, when scrolling slow.
Internet Explorer: up to ~110ms an event.
http://jsfiddle.net/TRNCFRMCN/1Lygop32/4/.
There is no such event as 'scrollEnd'. I recommend that you check the value returned by scroll() every once in a while (say, 200ms) using setInterval, and record the delta between the current and the previous value. If the delta becomes zero, you can use it as your event.
There are scrollstart and scrollstop functions that are part of jquery mobile.
Example using scrollstop:
$(document).on("scrollstop",function(){
alert("Stopped scrolling!");
});
Hope this helps someone.
The scrollEnd event is coming. It's currently experimental and is only supported by Firefox. See the Mozilla documentation here - https://developer.mozilla.org/en-US/docs/Web/API/Document/scrollend_event
Once it's supported by more browsers, you can use it like this...
document.onscrollend = (event) => {
console.log('Document scrollend event fired!');
};
I pulled some code out of a quick piece I cobbled together that does this as an example (note that scroll.chain is an object containing two arrays start and end that are containers for the callback functions). Also note that I am using jQuery and underscore here.
$('body').on('scroll', scrollCall);
scrollBind('end', callbackFunction);
scrollBind('start', callbackFunction);
var scrollCall = function(e) {
if (scroll.last === false || (Date.now() - scroll.last) <= 500) {
scroll.last = Date.now();
if (scroll.timeout !== false) {
window.clearTimeout(scroll.timeout);
} else {
_(scroll.chain.start).each(function(f){
f.call(window, {type: 'start'}, e.event);
});
}
scroll.timeout = window.setTimeout(self.scrollCall, 550, {callback: true, event: e});
return;
}
if (e.callback !== undefined) {
_(scroll.chain.end).each(function(f){
f.call(window, {type: 'end'}, e.event);
});
scroll.last = false;
scroll.timeout = false;
}
};
var scrollBind = function(type, func) {
type = type.toLowerCase();
if (_(scroll.chain).has(type)) {
if (_(scroll.chain[type]).indexOf(func) === -1) {
scroll.chain[type].push(func);
return true;
}
return false;
}
return false;
}

Categories

Resources