How to provide a dynamic (calculated) css property - javascript

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);

Related

Efficient way to request back-end for data on input change

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.

Detect low processing speed

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.

Remove stickiness after scrolling element

It is pretty simple to make some element sticky after scrolling several pixels. However, how would you do an opposite of that?
I want an element to be sticky, but after scrolling e.g. 400px (to its original position) it would remain there.
A very good example can be found here http://ultrahd-3d-televize.heureka.cz
You can achieve this by changing position to fixed and absolute via jquery,
use fixed when you want sticky to move along and absolute when it should stop moving. you should also set top of sticky to make it stop at the right place:
var num = 400; //after num pixels, sticky doesn't move any more
$(window).bind('scroll', function () {
if ($(window).scrollTop() > num) {
var top = $(window).height() + num;
$('.menu').css({"position":"absolute","bottom":"auto","top":top + "px"});
} else {
$('.menu').css({"position":"fixed","bottom":"0","top":"auto"})
}
});
This Fiddle shows how to make it happen.
It took a while for me to write my answer, because I used the opportunity to learn about debouncing, to prevent that the piece of code that checks on scroll gets called every time one scroll is done (in theory flooding your browser).
JSFIDDLE
My jQuery:
var menuHeight = $('.menu').height();
console.log(menuHeight);
var scrollingMachine = debounce(function() {
var $this = $(this);
if($(document).scrollTop() > (menuHeight - 850)) {
console.log($(document).scrollTop() - 850);
$('.stickypart').addClass('absolute');
}
else {
$('.stickypart').removeClass('absolute');
}
}, 100);
window.addEventListener('scroll', scrollingMachine);
// 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);
};
};
Debouncing helps by only calling the function after the scrolling stops, with a maximum of once every 100ms. (just change the 100 to something else if you want it to react faster).
It's a good idea to debounce all functions that get triggered on scroll or for example resizing, to prevent the browser from calculating for every pixel scrolled or resized and only firing when the user is done scrolling or resizing. It can also be used in the case of typing or AJAX calls. Especially in the case of AJAX calls, you want to only fire a function when necessary, not whenever the user lifts his finger from a letter. See an example here.

Web page with parallax lags on scroll

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 ));

When using setInterval, if I switch tabs in Chrome and go back, the slider goes crazy catching up

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.

Categories

Resources