I am trying to implement kind of player on my website.
If press 'Play' button, the music starts and the page smoothly scrolls down.
But when you press 'Mute' button (function(){music.volume=0}) I am not sure why the page appears at the top again. window.scroll() doesn't do anything without delay. So i am using setTimeout function to scroll the page on the current place. The problem is that in Opera and IE setTimeout takes about 10 ms, so when i click 'Mute' button i see like ticks to top and back. In chrome it takes only 2 ms and there is no problems.
Now when i decide to create my own timeout function the window.scroll() does not work again.
Here is my code:
var isMuted = false;
muteButton.onclick = function() { ////This function works with big delay.
if (!isMuted) {
mainAudio.volume = 0;
isMuted = true;
} else {
mainAudio.volume = bgAudioTrackVolume;
isMuted = false;
}
setTimeout(function() {
window.scroll(0, offset); /// Works
}, 0)
};
Change setTimeout with:
i = 9542155.873; /// I have tried delay time from 1ms - 250ms by changing this value.
while (i > 0.00001) {
i = i / 1.0001234567;
if (i < 0.00001) {
window.scroll(0, offset); /// Does not do anything. Strange! Have tried to change variable with a number.
}
}
Every time i check offset value, it is always available before calling scroll function.
I know that my problem is not usual and i am realy need your help.
The reason that the page scrolls to the top is that you are using a link with the empty bookmark #, which represents the top of the page. The reason that the scroll method doesn't work without a timeout is that jumping to the bookmark happens after the event handler.
Instead of trying to scroll the page back to where it was, just stop the default action of the link by returning false from the event handler:
var isMuted = false;
muteButton.onclick = function() {
if (!isMuted) {
mainAudio.volume = 0;
isMuted = true;
} else {
mainAudio.volume = bgAudioTrackVolume;
isMuted = false;
}
return false;
};
Alternatively, use some other element than a link.
Related
How can you synchronize scrollbars between two elements without recursively calling each event?
Usually you would expect the following code:
$div1.scroll(function() {
$div2.scrollTop($div1.scrollTop());
});
$div2.scroll(function(){
$div1.scrollTop($div2.scrollTop());
});
But in this case, if you scroll $div1 1px, it will scroll $div2 1px as well, which will prompt the $div2 scroll event to fire and re-apply the scroll position to $div1. While this might not seem like an issue, when this code is applied to a page and you naturally scroll with a mouse, it scrolls in 1px increments because the handlers call eachother and don't allow the scroll to flow.
So, how would you solve this issue?
Example: https://jsfiddle.net/axtn/a91fsar3/2
Found out a good solution. Debouncing does the trick.
You can use a combination of timers and bools to make sure the element is being scrolled by the user. Thus, when the scroll event is rapidly and consecutively fired (like when a user scrolls down), it prevents the handlers from recursively calling eachother. The following code does the trick:
var userScroll1 = true;
var userScroll2 = true;
var timer;
$div1.scroll(function() {
if(userScroll2) {
userScroll1 = false;
clearTimeout(timer);
$div2.scrollTop($div1.scrollTop());
timer = setTimeout(function() {
userScroll1 = true;
}, 100);
}
});
$div2.scroll(function(){
if(userScroll1) {
userScroll2 = false;
clearTimeout(timer);
$div1.scrollTop($div2.scrollTop());
timer = setTimeout(function() {
userScroll2 = true;
}, 100);
}
});
Check out the properly functioning jsbin: https://jsfiddle.net/axtn/a91fsar3
On my website, a related content box should be animated into the viewport when it gets visible.
I’m trying to make my animation as efficient as possible through CSS and JavaScript, so that it doesn’t affects scroll performance negatively.
While the CSS part was simple (using transform, will-change, contain), I’m struggling a bit with when to use window.requestAnimationFrame.
Should I use it only when the class is added to the element or also when the function isScrolledIntoView is called or even inside isScrolledIntoView, when the elements position is measured?
var percentVisible = 0.25;
window.addEventListener('scroll', function(){
relatedContent(related, percentVisible);
}
)
function relatedContent(r, pV){
window.requestAnimationFrame(function() {
if(isScrolledIntoView(r, pV)){
window.requestAnimationFrame(function(){
r.classList.add("visible");
}, r)
}
}, r)
}
function isScrolledIntoView(el, percentV) {
var elemTop, elemBottom, elemHeight, overhang, isVisible;
/*window.requestAnimationFrame(
function(){*/
elemTop = el.getBoundingClientRect().top;
elemBottom = el.getBoundingClientRect().bottom;
elemHeight = el.getBoundingClientRect().height;
/*}
);*/
overhang = elemHeight * (1 - percentV);
isVisible = (elemTop >= -overhang) && (elemBottom <= window.innerHeight + overhang);
return isVisible;
}
requestAnimationFrame returns a non-zero long that can be used to cancel your request, so instead of writing your own throttle implementation, you can use the following simpler approach to prevent multiple handlers stacking up:
let currentRequest;
document.addEventListener('scroll', function () {
cancelAnimationFrame(currentRequest);
currentRequest = requestAnimationFrame(handleScroll);
});
No don't use it like that...
requestAnimationFrame (rAF) is a timing function that does synchronize with the screen refresh rate (generally 60fps).
Scroll event may fire more often than 60 events per second.
Each call to rAF will stack all the functions passed as its parameter in some kind of a big function called just before the next screen refresh.
Combine all of this and what you get is multiple calls to the same function in a stack, just before the next screen refresh.
Instead, what you seem to want is to prevent your scroll event to fire when not useful. This is called a throttle function, and you're a bit far from it.
Here is a simple throttle implementation using rAF :
var throttle = function(callback) {
var active = false; // a simple flag
var evt; // to keep track of the last event
var handler = function(){ // fired only when screen has refreshed
active = false; // release our flag
callback(evt);
}
return function handleEvent(e) { // the actual event handler
evt = e; // save our event at each call
if (!active) { // only if we weren't already doing it
active = true; // raise the flag
requestAnimationFrame(handler); // wait for next screen refresh
};
}
}
That you could use like this :
window.addEventListener('scroll', throttle(yourScrollCallback));
So I think I am doing everything correct with my jquery mobile slider, but the control is not being re-enabled. I've made a pretty decent jsFiddle with it, in hopes someone will spot the error quickly.
On the fiddle you will see the jQuery moblie control. If you click and move the slider position the event will fire that the control value changed. If you end up changing the value more than 5 times within 20 seconds the control will lock up. You can think of this as being a cooldown period. Well after the control cools down it should be re-enabled for more mashing.
Problem is, the control never comes back from being disabled!
http://jsfiddle.net/Narq6/
Sample Javascript:
var sent = 0;
var disabled = false;
$('#slider-fill').on( 'slidestop', function()
{
send();
writeConsole(sent);
})
function send()
{
setTimeout(decrease, 4000);
sent +=1;
if(sent > 5)
{
$('#slider-fill').prop('disabled', 'disabled');
disabled = true;
}
}
function decrease()
{
if(sent > 0)
sent -= 1;
writeConsole('decrease');
writeConsole(sent);
if(sent === 0)
{
//CODE TO DISABLE HERE!!!
//LOOK HERE THIS IS WHERE I REMOVE THE DISABLE!!!
writeConsole('no longer disabled!');
$('#slider-fill').prop('disabled', '');
///YOU LOOKED TOO FAR GO BACK A LITTLE BIT :D
}
}
function writeConsole(message)
{
var miniconsole = $('#miniConsole');
var contents = miniconsole.html();
miniconsole.html(contents + message + '<br/>' );
miniconsole.scrollTop(10000);
}
You were using incorrect enable/disable syntax.
This one is a coorect syntax:
$('#slider-fill').slider('disable');
and
$('#slider-fill').slider('enable');
Here's am working example made from your jsFiddle: http://jsfiddle.net/Gajotres/djDDr/
i'm looking for another way to execute this code :
$.each($("#gallery > img"), function(index,curImg) {
setTimeout(function() {
clearCanvas();
cvsCtx.drawImage(curImg,0,0);
} , index*animationMs);
});
This code draw an image from my gallery to my canvas every animationMs .
But i would like to make it possible to stop the animation, with a "Play/stop" button, I can't do it this way...
Any idea or workaround ?? thank you !!
I can't test it. But you can stop animation by using a variable to hold the setTimeout function as following:
var x; // public var
....
x = setTimeout(......);
// To stop it use:
clearTimeout(x);
Hope this works for you
I find that creating timeouts in a loop is usually too hard to manage - you don't want to have to cancel multiple timeouts. Better to have the function doing the work call itself (indirectly) by setting a timeout just before it completes, because then you can put in a simple if test to decide whether to set the next timeout and continue your animation.
Perhaps a little something like this:
<input id="playPause" type="button" value="Play">
<script>
function initAnimation(animationMs, autoRepeat, waitForPlayButton) {
var currentFrame = 0,
$imgList = $("#gallery > img"),
paused = waitForPlayButton;
function drawNext() {
clearCanvas();
cvsCtx.drawImage($imgList[currentFrame++],0,0);
if (currentFrame >= $imgList.length) {
currentFrame = 0;
if (!autoRepeat) {
paused = true;
$("playPause").prop("value", "Play");
}
}
if (!paused)
setTimeout(drawNext, animationMs);
}
$("playPause").prop("value", waitForPlayButton ? "Play" : "Pause")
.click(function() {
this.value = (paused = !paused) ? "Play" : "Pause";
if (!paused)
drawNext();
});
if (!waitForPlayButton)
drawNext();
}
initAnimation(100, true, false);
</script>
If autoRepeat param is false the animation will run once and stop, but can be restarted via the button, otherwise (obviously) it just keeps repeating.
If waitForPlayButton is false the animation will start immediately, otherwise (obviously) it will start when the button is pressed.
Pressing the button will pause at the current frame.
(Not tested since I don't have a bunch of images handy, but I'm sure you get the idea and can fix any problems yourself. Or let me know if you get errors...)
var images = $("#gallery > img").clone(), interval;
function startLoop() {
interval = setInterval(function(){
var image = images[0];
clearCanvas();
cvsCtx.drawImage(image,0,0);
images.append(image);
}, animationMs);
}
$(".stop").click(function() {clearInterval(interval);});
$(".start").click(startLoop);
setTimeout return a timeoutID which can be given to clearTimeout as a parameter to stop the timeout from happening.
You can read more about this at: https://developer.mozilla.org/en/DOM/window.setTimeout
Good luck
It's not really an animation... but still:
$("#gallery > img").each(function(index,curImg) {
$(this).delay(index*animationMs).queue(function(next) {
clearCanvas();
cvsCtx.drawImage(curImg,0,0);
if (next) next();
});
});
Using jQuery queues like I did allows you to do .stop(true), on $("#gallery > img") or a single image and stop their "animation".
First you could add images to a javascript array variable (eventually global) and then call a function cycle() on that array for all its length.
You should put your setTimeout() call inside that function, assigning it to a variable: var t=setTimeout("cycle()",animationMs); and execute clearTimeout(t); when you want to stop the animation.
Of course you could also save in a variable the frame where you were when stopping the animation and restart exactly from that frame when pressing "play" button.
Is there a way to stop setTimeout("myfunction()",10000); from counting up when the page isn't active. For instance,
A user arrives at a "some page" and stays there for 2000ms
User goes to another tab, leaves "some page" open.
myfunction() doesn't fire until they've come back for another 8000ms.
(function() {
var time = 10000,
delta = 100,
tid;
tid = setInterval(function() {
if ( document.hidden ) { return; }
time -= delta;
if ( time <= 0 ) {
clearInterval(tid);
myFunction(); // time passed - do your work
}
}, delta);
})();
Live demo: https://jsbin.com/xaxodaw/quiet
Changelog:
June 9, 2019: I’ve switched to using document.hidden to detect when the page is not visible.
Great answer by Šime Vidas, it helped me with my own coding. For completeness sake I made an example for if you want to use setTimeout instead of setInterval:
(function() {
function myFunction() {
if(window.blurred) {
setTimeout(myFunction, 100);
return;
}
// What you normally want to happen
setTimeout(myFunction, 10000);
};
setTimeout(myFunction, 10000);
window.onblur = function() {window.blurred = true;};
window.onfocus = function() {window.blurred = false;};
})();
You'll see that the window blurred check has a shorter time set than normal, so you can set this depending on how soon you require the rest of the function to be run when the window regains focus.
You can do something like:
$([window, document]).blur(function() {
// Clear timeout here
}).focus(function() {
// start timeout back up here
});
Window is for IE, document is for the rest of the browser world.
I use almost the same approach as Šime Vidas in my slider
but my code is based on document.visibilityState for page visibility checking:
document.addEventListener("visibilitychange", () => {
if ( document.visibilityState === "visible" ) {
slideshow.play();
} else {
slideshow.pause();
}
});
About Page Visibility
API: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
What you'd have to do is set up a mechanism to set timeouts at small intervals, keeping track of total elapsed time. You'd also track "mouseenter" and "mouseleave" on the whole page (the <body> or something). When the short-term timeouts expire, they can check the window state (in or out) and not restart the process when the window is not in focus. The "mouseenter" handler would start all paused timers.
edit — #Šime Vidas has posted an excellent example.
I've finally implemented a variation of #Šime Vidas' answer, because the interval was still running if I opened another program and the browser window was not visible, but the page executing the interval was the active browser tab. So, I've modified the condition to document.hidden || !document.hasFocus(). This way, if the document is hidden or the document doesn't have the focus, the interval function just returns.
(function() {
var time = 10000,
delta = 100,
tid;
tid = setInterval(function() {
if ( document.hidden || !document.hasFocus() ) { return; }
time -= delta;
if ( time <= 0 ) {
clearInterval(tid);
myFunction(); // time passed - do your work
}
}, delta);
})();