I have a web app that is essentially a text box that the user can type in.
The user types in the box and then clicks a Submit button, and it executes a script to display an image.
Using AngularJS I can also have the same JS function called whenever the user types in the box, removing the need to click the button and offering a much smoother experience.
However, this means that this rather lengthy and intensive function can be called multiple times per second, especially for fast typers. On a desktop, this is no problem. On a mobile - at least, an entry-level mobile - it's extremely slow and is a horrible experience.
The automatic submission behaviour is controlled by a boolean variable that is TRUE by default.
On mobile, I would like to set this variable to FALSE. Even better would just be to set it to false for slow devices, but I don't think that's possible to detect. What's the easiest way of doing this?
You can use a debounce function to only run the function after X time to stop typing, In this article you can find how to implement it
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
But if you still want to detect if it is a mobile device, you can use this small function
function isMobile() {
return /Mobi/.test(navigator.userAgent) || /Android/.test(navigator.userAgent);
}
Some of these answers might help you out:
https://stackoverflow.com/search?q=determine+mobile
(but for fun...)
Welll... wonky way first.. you could do a performance timer when loading the page.
show a busy indicator..
create a counter x++ for 1 second..
if x > 10000.. fast device (total out of my butt number)
if x < 10000.. slow device
remove busy indicator and proceed
Like I said.. that's wonky and people will argue a) you're wasting time and b) you're wasting battery. I would not make that argument if it really improves the user experience later.
There's also the library many folks use to determine the browser 'type' and the features it provides.
Related
This is more of a suggestion asking kind of question! I have a searchbox that user can type stuff in and the get a list of suggestion to choose from. I am using react, axios for data fetching and redux-saga for state managing.It is basically look like this:
handleChange(stringValue){
this.setState({
inputValue : stringValue
});
callServer(stringValue);
}
Now, everything works fine but the problem is that sending all those requests and handling the incoming response and changing state seems unnecessary because user doesn't stop to look at the suggestions in every char he types. I am looking for a way to only ask for suggestions when i know user is done fast typing. What i am thinking of doing looks like this :
handleChange(stringValue){
clearTimeOut(this.callerTimer);
this.callerTimer = null;
this.callerTimer = setTimeOut(function(){
this.callServer(stringValue);
this.callerTimer = null;
}.bind(this),300)
//i consider 300ms as the average time it takes people to stop typing and think
}
This works but i don't have a good feeling about it. So do you guys know any other clean and less timerly way to do what i want? is there any way to handle this in my saga effect or maybe an inbuilt time threshold thing in inputs that i am not aware of?
You want debounce functionality.
Basically it limits the rate at which a function can fire. So it waits a few ms before firing the event kind of like the user stopping the writing process.
Check this snippet
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
// This will apply the debounce effect on the keyup event
// And it only fires 500ms or half a second after the user stopped typing
$('#testInput').on('keyup', debounce(function () {
alert('typing occurred');
$('.content').text($(this).val());
}, 500));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="testInput" />
<p class="content"></p>
Check this codesandbox for React solution
https://codesandbox.io/embed/green-meadow-16r3p?fontsize=14
Basically now it's up to you. Set your own time in ms and you're good to go. There is no need to install any additional dependencies to your project.
Lodash has a debounce function but you don't want to install all of lodash just for one function.
I highly recommend using debounce from lodash: https://lodash.com/docs/4.17.11#debounce
From the docs:
Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
Therefore you pass your request function to debounce, so you limit the number of requests to your server.
I am working on task in which each time a user scrolls or resizes the screen I would like to recalculate a css properties for an element.
Let's say I want to impl. a progress bar and the progress is reported based on the scroll position in the window
<div class='progress-bar-wrap'>
<div class='progress-bar-progress'></div>
</div>
function updateScrollProgress () {
this.progressIndicator.css('width', this.calculateProgressBarWidth() + 'px');
}
I tried to hook on scroll and resize events, but this seem to have a laggy effect.
window.on('scroll', updateScrollProgress)
window.on('resize', updateScrollProgress)
I tried in the scroll and resize event handlers to requestAnimationFrame
window.on('scroll', function(){window.requestAnimationFrame( updateScrollProgress))
window.on('resize', function(){window.requestAnimationFrame( updateScrollProgress))
And experienced huge improvement in most browsers, however it is yet occasionally laggy.
I tried to request another frame, when from the requestAnimationFrame handler:
function updateScrollProgress () {
window.requestAnimationFrame( updateScrollProgress)
this.progressIndicator.css('width', this.calculateProgressBarWidth() + 'px');
}
This completely eliminated the laggy effect, but comes to the cost of endless loop of calls to this method, even when no recalculations are needed.
Is there a way to hook a handler just before the browser decides to draw element(s), so that I can provide/set the those "dynamic" css values for a property?
What's what you're doing when you use requestAnimationFrame. If you've gotten rid of the lag using it, it's unclear why you say the function is running "too often." Usually in this sort of context, "too often" means "is causing lag" (by running too often and slowing things down). If yours isn't, then...?
If you want the handler called less often, you can debounce it, but then you'll probably notice delays before the changes you want (because you've debounced), which it sounds like is what you're trying to avoid.
In any case, requestAnimationFrame is, for now at least, the right tool for the "just before the browser renders the frame" job.
Wrap your event function through a debounce function:
More info on debounce functions here:
https://davidwalsh.name/javascript-debounce-function
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
var myEfficientFn = debounce(function() {
// All the taxing stuff you do
}, 250);
window.addEventListener('resize', myEfficientFn);
When events are queued with setTimeout/setInterval, and the user is viewing a separate tab, Chrome and Firefox enforce a minimum 1000ms lag before the event is executed. This article details the behaviour.
This has been discussed on StackOverflow previously, but the questions and answers only applied to animations. Obviously, an animation can just be forced to update to the latest state when a user re-enters the tab.
But the solution does not work for sequenced audio. I have Web Audio API playing several audio files in sequence, and setTimeout is used to countdown to when the next audio file plays. If you put the tab in the background, you get an annoying 1 second gap between each pattern -- an extreme flaw in an API designed for advanced audio.
You can witness this behaviour in various HTML5 sequencers, e.g. with PatternSketch -- just by entering a pattern, playing, and going to another tab.
So I'm in need of a workaround: a way to queue events without the 1000ms clamp. Does anyone know of a way?
The only solution I can think of is to have window.postMessage run every single millisecond and check each time if the event is to execute. That is definitely detrimental to performance. Is this the only option?
Apparently there is no event system planned for Web Audio API, so that is out of question.
EDIT: Another answer is to use WebWorkers per https://stackoverflow.com/a/12522580/1481489 - this answer is a little specific, so here's something more generic:
interval.js
var intervalId = null;
onmessage = function(event) {
if ( event.data.start ) {
intervalId = setInterval(function(){
postMessage('interval.start');
},event.data.ms||0);
}
if ( event.data.stop && intervalId !== null ) {
clearInterval(intervalId);
}
};
and your main program:
var stuff = { // your custom class or object or whatever...
first: Date.now(),
last: Date.now(),
callback: function callback() {
var cur = Date.now();
document.title = ((cur-this.last)/1000).toString()+' | '+((cur-this.first)/1000).toString();
this.last = cur;
}
};
var doWork = new Worker('interval.js');
doWork.onmessage = function(event) {
if ( event.data === 'interval.start' ) {
stuff.callback(); // queue your custom methods in here or whatever
}
};
doWork.postMessage({start:true,ms:250}); // tell the worker to start up with 250ms intervals
// doWork.postMessage({stop:true}); // or tell it just to stop.
Totally ugly, but you could open up a child popup window. However, all this does is transfer some of the caveats to the child window, i.e. if child window is minimized the 1000ms problem appears, but if it is simply out of focus, there isn't an issue. Then again, if it is closed, then it stops, but all the user has to do is click the start button again.
So, I suppose this doesn't really solve your problem... but here's a rough draft:
var mainIntervalMs = 250;
var stuff = { // your custom class or object or whatever...
first: Date.now(),
last: Date.now(),
callback: function callback(){
var cur = Date.now();
document.title = ((cur-this.last)/1000).toString()+' | '+((cur-this.first)/1000).toString();
this.last = cur;
}
};
function openerCallbackHandler() {
stuff.callback(); // queue your custom methods in here or whatever
}
function openerTick(childIntervalMs) { // this isn't actually used in this window, but makes it easier to embed the code in the child window
setInterval(function() {
window.opener.openerCallbackHandler();
},childIntervalMs);
}
// build the popup that will handle the interval
function buildIntervalWindow() {
var controlWindow = window.open('about:blank','controlWindow','width=10,height=10');
var script = controlWindow.document.createElement('script');
script.type = 'text/javascript';
script.textContent = '('+openerTick+')('+mainIntervalMs+');';
controlWindow.document.body.appendChild(script);
}
// write the start button to circumvent popup blockers
document.write('<input type="button" onclick="buildIntervalWindow();return false;" value="Start" />');
I'd recommend working out a better way to organize, write, etc. but at the least it should point you in the right direction. It should also work in a lot of diff browsers (in theory, only tested in chrome). I'll leave you to the rest.
Oh, and don't forget to build in auto-closing of the child window if the parent drops.
I'm currently developing a web page with a scrolling parallax effect (Stellar.js) on the header and three other sections of the site: however, scrolling them causes lag, especially at the top of the page.
I've already tried to reduce the background images' size by using compression, but it hasn't made too much difference; removing the blur effect didn't solve the problem, either (it did reduce the lag, but it still wasn't smooth enough).
The website runs pretty well on Firefox (W10), with almost no frame drops, but there's quite some lag on Chrome (both Windows and OS X) and Safari.
There's a few JS scroll-triggered scripts running, but I don't know if those may be the cause. Any suggestions?
What you're going to want to do is throttle scroll events. Debouncing events means an event can't fire again until after a certain amount of time. Throttling events means that the event can only fire so much per period of time.
Here's function to throttle events (credit: http://sampsonblog.com/749/simple-throttle-function)
// Create the listener function
function throttle (callback, limit) {
var wait = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!wait) { // If we're not waiting
callback.call(); // Execute users function
wait = true; // Prevent future invocations
setTimeout(function () { // After a period of time
wait = false; // And allow future invocations
}, limit);
}
}
}
To use it just do something like this:
function callback () {
console.count("Throttled");
}
window.addEventListener("scroll", throttle( callback, 200 ));
I have a jQuery slider on my site and the code going to the next slide is in a function called nextImage. I used setInterval to run my function on a timer, and it does exactly what I want: it runs my slides on a timer. BUT, if I go to the site in Chrome, switch to another tab and return, the slider runs through the slides continuously until it 'catches up'. Does anyone know of a way to fix this. The following is my code.
setInterval(function() {
nextImage();
}, 8000);
How to detect when a tab is focused or not in Chrome with Javascript?
window.addEventListener('focus', function() {
document.title = 'focused';
},false);
window.addEventListener('blur', function() {
document.title = 'not focused';
},false);
To apply to your situation:
var autopager;
function startAutopager() {
autopager = window.setInterval(nextImage, 8000);
}
function stopAutopager() {
window.clearInterval(autopager);
}
window.addEventListener('focus', startAutopager);
window.addEventListener('blur', stopAutopager);
Note that in the latest version of Chromium, there is either a bug or a 'feature' which is making this less reliable, requiring that the user has clicked at least once anywhere in the window. See linked question above for details.
I post an answer here: How can I make setInterval also work when a tab is inactive in Chrome?
Just do this:
setInterval(function() {
$("#your-image-container").stop(true,true);
nextImage();
}, 1000);
inactive browser tabs buffer some of the setInterval or setTimeout functions.
stop(true,true) - will stop all buffered events and execute immadietly only last animation.
The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).
A few ideas comes to mind:
Idea #1
You can make it so that a short burst is idempotent. For example, you could say:
function now() {
return (new Date()).getTime();
}
var autopagerInterval = 8000;
function startAutopager() {
var startImage = getCurrentImageNumber();
var startTime = now();
var autopager = setInterval(
function() {
var timeSinceStart = now() - startTime();
var targetImage = getCurrentImageNumber + Math.ceil(timeSinceStart/autopagerInterval);
if (getCurrentImageNumber() != targetImage)
setImageNumber(targetImage); // trigger animation, etc.
},
autopagerInterval
);
return autopager;
}
This way even if the function runs 1000 times, it will still run in only a few milliseconds and animate only once.
note: If the user leaves the page and comes back, it will have scrolled. This is probably not what the original poster wants, but I leave this solution up since it is sometimes what you want.
Idea #2
Another way to add idempotence (while still keeping your nextImage() function and not having it scroll to the bottom of the page) would be to have the function set a mutex lock which disappears after a second (cleared by another timeout). Thus even if the setInterval function was called 1000 times, only the first instance would run and the others would do nothing.
var locked = false;
var autopager = window.setInterval(function(){
if (!locked) {
locked = true;
window.setTimeout(function(){
locked=false;
}, 1000);
nextImage();
}
}, 8000);
edit: this may not work, see below
Idea #3
I tried the following test:
function f() {
console.log((new Date()) + window.focus());
window.setTimeout(f, 1000);
}
f();
It seems to indicate that the function is being called every second. This is odd... but I think this means that the callbacks are being called, but that the page renderer refuses to update the page in any graphical way while the tab is unfocused, delaying all operations until the user returns, but operations keep piling up.
Also the window.focus() function doesn't say if the window has focus; it GIVES focus to the window, and is thus irrelevant.
What we want is probably this: How to detect when a tab is focused or not in Chrome with Javascript? -- you can unset your interval when the window loses focus (blur), and reset it when it gains focus.
I don't know exactly what is going on in your function nextImage(), but I had a similar issue. I was using animate() with setInterval() on a jQuery image slider that I created, and I was experiencing the same thing as you when I switched to a different tab and back again. In my case the animate() function was being queued, so once the window regained focus the slider would go crazy. To fix this I just stopped the animate() function from queuing.
There are a couple ways you can do this. the easiest is with .stop(), but this issue and ways to fix it are documented in the jQuery docs. Check this page near the bottom under the heading additional notes: http://api.jquery.com/animate/
I had faced similar issue, somehow this code below works fine for me.
var t1= window.setInterval('autoScroll()', 8000);
window.addEventListener('focus', function() {
focused = true;
window.clearInterval(t1);
t1 = window.setInterval('autoScroll()', 8000);
},false);
window.addEventListener('blur', function() {
focused = false;
window.clearInterval(t1);
},false)
function autoScroll()
{
if ( running == true){
if ( focused = true){
forwardSlide();
}
}
else {
running = true;
}
}
If you are using Soh Tanaka's image slider then just add this...to solve your Google Chrome issue:
$(".image_reel").stop(true, true).fadeOut(300).animate({ left: -image_reelPosition}, 500 ).fadeIn(300);
Take note of the .stop() function. Ignore the fading in and out stuff, that's what I used on my version
Thanks
Seconding the comment by jgerstle to use page visibility events instead, see https://www.w3.org/TR/page-visibility/#example-1-visibility-aware-video-playback for more around subscribing to 'visibilitychange' for hidden/visible states.
This seems to be more useful than focus/blur these days as it covers visible-but-not-selected windows if concerned also about multi-window operating systems.