Jquery animate problem with potential variable scope issue? - javascript

I've written an example of my problem, for some reason (which I don't seem to fully understand I guess), my variables are losing scope. I'm trying to animate several things on the screen, once they've reached their destination the 'complete' function within animate() kicks in, this is meant to basically delete the dom element as well as a couple other functions that are meant to be delayed within it. A good way to think of it is a gun firing, do the damage when the bullet hits and not before, and the damage the bullet does is tied into the animation itself.
As I mentioned in my first sentence, I've written an example to demonstrate exactly what I mean. I'll also paste in the console.log output that came with it and give a summary of what it shows.
I've placed the example in jsbin, as it should be easier for you guys to see (I hope -- I've never used that stuff before). For my example I just did an animation of a square block filling, and some squares flying near it. When those squares reach its destination, it empties some out of the 'Jar' and resets its animation, then it removes itself from the dom.
http://jsbin.com/ihozil/2
Here's the console.log text from chrome:
Set Height to: 30px
Testing.Start( [Object] )
Setup #I63326848.animate()
Setup #I22596539.animate()
Setup #I14561405.animate()
Setup #I57372916.animate()
Setup #I31994195.animate()
OnComplete for :I63326848
Set Height to: 30.6px
OnComplete for :I14561405
Set Height to: 33px
OnComplete for :I57372916
Set Height to: 34.2px
OnComplete for :I31994195
Set Height to: 36px
OnComplete for :I63326848
Set Height to: 36.6px
Finished filling
As you can see from the log above, #I22596539 (2nd one) was set up, however when it came to the OnComplete methods, it did not fire, all the others did and #I63326848 fired twice (1st method setup). I've also noticed that when I remove the .stop() part of the chain on the squared box (#Jar), that these problems do not happen. However that is needed so I don't end up with several animations trying to fill the jar at the same time. I've also noticed that it's ALWAYS the second animation to be set up that does this.
So I'm not entirely sure if it's variables losing scope, else surely this would happen to the others, I've sent the last couple of days trying to suss this one out and I've hit my road block. I've ran out of things to try to fix it. You guys are my last hope!
function Jar() {
this._iAmount = 50;
this._iMaxAmount = 100;
this._iRefillRate = 5;
return this;
}
Jar.prototype = {
ReduceAmount: function( m_iAmount ) {
this._iAmount -= m_iAmount;
if( this._iAmount < 0 ) {
this._iAmount = 0;
}
return;
},
StartFill: function() {
var iHeight = ( 60 - ( 60 * this._iAmount / this._iMaxAmount ) );
console.log( "Set Height to: "+iHeight+"px" );
jQuery( '#Jar' ).css( 'height', iHeight+'px' );
if( iHeight < 60 ) {
var iMillisecondsTilMax = ( ( this._iMaxAmount - this._iAmount ) / this._iRefillRate ) * 1000;
jQuery('#Jar').stop().animate({height: '0px'}, iMillisecondsTilMax, function() { console.log( "Finished filling" ); } );
}
return;
}
};
var Testing = {
Start: function( m_oJar ) {
console.log( "Testing.Start( [Object] )" );
for( var iLoop = 0; iLoop < 5; iLoop++ ) {
var elNewDiv = document.createElement('div');
var iRandomValue = Math.round( 1 + ( Math.random() * ( 99999999 - 1 ) ) );
var iAmount = Math.round( 1 + ( Math.random() * ( 5 - 1 ) ) );
var strID = "I"+iRandomValue;
elNewDiv.setAttribute( 'id', strID );
elNewDiv.style.border = 'solid 1px red';
elNewDiv.style.position = "absolute";
elNewDiv.style.height = '200px';
elNewDiv.style.width = '200px';
elNewDiv.style.left = '0px';
elNewDiv.style.top = '0px';
document.body.appendChild( elNewDiv );
this.Animate( m_oJar, strID, iAmount );
}
return;
},
Animate: function( m_oJar, m_strID, m_iAmount ) {
console.log( "Setup #"+m_strID+".animate()" );
jQuery( '#'+m_strID ).animate({ width: '30px', height: '30px', top: '100px', left: '200px' }, {
duration: 1000,
queue: false,
easing: 'linear',
complete: function() {
console.log( "OnComplete for :"+m_strID );
m_oJar.ReduceAmount( m_iAmount );
m_oJar.StartFill();
jQuery( '#'+m_strID ).remove();
}
});
}
};
jQuery(document).ready( function() {
var oJar = new Jar();
oJar.StartFill();
Testing.Start( oJar );
});

I don't have a great answer, but I think this might be a jQuery bug. I have been able to work around the issue by modifying the StartFill method, or just not calling it the first time in the ready() function. I've also seen it go away when I use the debugger which leans towards some sort of timing issue...possibly in jQuery, possibly in the browser. I've not tested this on anything other than Chrome so it's hard to be sure. However I don't think there is anything wrong with the logic in your code provided.

Related

Videojs get video dimensions in fullscreen mode

I am able to get the width & size of the actual video (not the player) when not in fullscreen mode using the videoWidth() and videoHeight(), like this:
var SVideo = videojs('SingularVideo').ready(function(){
resizeFunc(this.videoWidth(), this.videoHeight())
});
The problem is that I need to check the video size when the user enters fullscreen mode, so I listen on the "fullscreenchange" event and check again:
SVideo.on("fullscreenchange",
function () {
resizeFunc(SVideo.videoWidth(), SVideo.videoHeight());
});
But in the fullscreenchange callback, the width and height don't change, the values are the same as if the video is not in fullscreen mode.
I would very much appreciate any suggestions as how to get the actual video width and height when in fullscreen mode. TIA!
I was looking for the video-dimensions, but not in fullscreen mode. I can see this is an old question, - so you probably don't have the issue still. But hopefully, it can help someone else.
I did find this in the documentation: currentDimensions ... However... I was hoping to get the aspect ratio of the video, - and not the size of the video-element (including the black edges/borders/fillers). I did figure out, that if I set fluid to true ( the fluid option ), then the video would take up the entire video-HTML-element.
Please note, that the fluid-option is implemented slightly after the video is rendered, - so I had to use setTimeout (uuuuuugly!), to get the correct aspectRatio.
Here is my (VueJS)-code:
mounted() {
this.player = videojs(this.$refs.videoPlayer, this.videoOptions, () => {
this.calcAspectRatio();
this.setMaxWidth();
setTimeout( () => {
this.calcAspectRatio();
this.setMaxWidth();
});
setTimeout( () => {
this.calcAspectRatio();
this.setMaxWidth();
}, 1000 );
setTimeout( () => {
this.calcAspectRatio();
this.setMaxWidth();
}, 2000 );
});
}
methods: {
calcAspectRatio(){
if( this.player && this.player.currentDimensions( 'width' ).width ){
this.aspectRatio = this.player.currentDimensions( 'width' ).height / this.player.currentDimensions( 'width' ).width;
}
},
setMaxWidth(){
let maxWidth = 1 / this.aspectRatio * ( ( this.VIEWPORTHEIGHTWHICHISCALCULATEDELSEWHERE - 100 ) * 0.90 );
document.querySelector( '#' + this.id ).style.maxWidth = maxWidth + 'px';
},
}
Since it is on full screen, you can get the width and height of the screen using the code below:
var width = screen.width;
var height = screen.height;

Fluid sliding panel : make it work on load and after resize with computed values?

I'm trying to put together a fluid sliding panel which should take into account the inner width of the window on load, as well as on resizes : depending on the actual window size, the panel should be moved left / right a fourth of the window width.
So far i managed to bypass the multiple resize events happening when the user resizes the window, thx to this thread.
var waitForFinalEvent = (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);
};
})();
var slidinNav = function(rtr){
document.getElementById('navPanel').style.left = -rtr + "px";
document.getElementById('navPanel').style.width = rtr + "px";
$('.showMenu').click(function(){
$('#navPanel').animate({left: '+=' + rtr +'px'}, 400);
});
$('.hideMenu').click(function(){
$('#navPanel').animate({left: '-=' + rtr + 'px'}, 400);
});
}
$(document).ready(function(){
var winW = window.innerWidth;
var navPosLeft=winW/4;
slidinNav(navPosLeft);
});
$(window).resize(function () {
waitForFinalEvent(function(){
var winW = window.innerWidth;
var navPosLeft=winW/4;
slidinNav(navPosLeft);
console.log(" Left / Width : " + navPosLeft);
}, 200, "un identifiant unique ?");
});
But being a complete javascript newbie i haven't found the solution to prevent the variables i use to store the window width value and offset to take all the successive values computed.
Better than a long and unclear explanation see jsfiddle here.
Here's my question : Should i reset variables (how and when) or rather try and get the last value (and again : how and when) ?
Thx for any help on this one ; - )
Correct me if I am not understanding exactly what you are looking for here but it looks to me like you may be making it more complicated than it needs to be.
Looks like you could simply just stay with using % and just have these functions:
$('.showMenu').click(function(){
$('#navPanel').animate({left: 0}, 400);
});
$('.hideMenu').click(function(){
$('#navPanel').animate({left: "-20%"}, 400);
});
As demonstrated here: http://jsfiddle.net/X9Jrc/3/

Triggering a video autoplay based on scroll position

I am writing a script that uses the Wipe animation from the scrollorama.js script. I am hoping to be able to implement a video to autoplay at certain markers in the scroll depth: ie, when a video page has wiped out another, and is now fully viewable. I have figured out how to measure scroll depth, i am successfully logging it in my console. I have figured out how to measure how deep i have scrolled, but maybe i am so tired, i don't know how to ask the video to play automatically at the scroll depth. I hope this is a legal question, and that I can get some assistance. Has anyone out there tried this before? Here is the code thus far.
enter code here $(document).ready(function() {
$(window).scroll(function(e) {
var scrollAmount = $('body').scrollTop();
console.log(scrollAmount);
});
var controller = $.superscrollorama();
var pinDur = 800;
// create animation timeline for pinned element
var pinAnimations = new TimelineLite();
//pinAnimations.append([TweenMax.to($('#green'), .5, {css:{top:0}})], .5)
pinAnimations.append([TweenMax.to($('#intromovie'), .5, {css:{top:0}})], .5 )
pinAnimations.append([TweenMax.to($('#red'), .5, {css:{top:0}})], .5)
pinAnimations.append([TweenMax.to($('#blue'), .5, {css:{top:0}})], .5 )
pinAnimations.append([TweenMax.to($('#movie1'), .5, {css:{top:0}})], .5);
pinAnimations.append([TweenMax.to($('#history1'), .5, {css:{top:0}})], .5);
//pinAnimations.append(TweenMax.to($('#pin-frame-unpin'), .5, {css:{top:'100px'}}));
controller.pin($('#content_wrapper'), pinDur, {
anim:pinAnimations,
onPin: function() {
$('#content_wrapper').css('height','100%');
},
onUnpin: function() {
$('#content_wrapper').css('height','1000px');
}
});
});
I figured this out, so i answer my own question with the help of a lot of other answers patched together here!
If anyone is interested, the html was simple:
<div id="videoHolder"></div>
Jquery was also simple:
$(function(){
$(window).scroll(function(e) {
var scrollAmount = $('body').scrollTop();
console.log(scrollAmount);
if(scrollAmount >="theamountyouwant" && scrollAmount <= "theotheramountyouwant") {
$("#videoHolder").html(
'<video width="1200" height="700" autoplay>' +
'<source src="http://itp.nyu.edu/~rnr217/HTML5/Week3/video/testopen.webm" type="video/webm"></source>' +
'<source src="http://itp.nyu.edu/~rnr217/HTML5/Week3/video/testopen.mp4" type="video/mp4"></source>' +
'</video>');
So in this case we can use an integrated JavaScript API namely Intersection Observer. Now our main task is to play the video at an particular position, so for this task we will set up a trigger on the intersectionRatio value.
const images = document.querySelectorAll(".mydivTriggerClass");
vid = document.getElementById("myVideoId");
observer = new IntersectionObserver((entries) => {
console.log(entries);
if (entry.intersectionRatio > 0.34) {
vid.play();
} else {
entry.target.style.animation = "none";
}
});
observer.observe(image);
NOTE: Please note that the console log entries are optional - its just so that when you inspect you get the info showing where this intersection ratio came from.
For a perfect working example please visit this link.
just add the script from below and add playonscroll param to your video tag anywhere on a page.
As well some times it's required to use different scroll container than body, sometimes its not obvious, so the following code works like a charm for me:
setInterval(function () {
$('video[playonscroll]').each(function () {
var videoElement = $(this)[0];
var eTop = $(this).offset().top;
var elementOffestY = (eTop - $(window).scrollTop());
var videoOffset = [elementOffestY, elementOffestY+$(this).height()];
if ((videoOffset[0] < 100) && (videoOffset[1] > 350)) {
console.log('play');
if (!videoElement.playing) {
videoElement.play();
}
} else {
if (videoElement.playing) {
videoElement.pause();
}
}
});
},300);
in case if you always use body container for scroll just change setInterval to $(window).scroll
And don't forget to define property for Video tag element:
Object.defineProperty(HTMLMediaElement.prototype, 'playing', {
get: function(){
return (this.currentTime > 0 && !this.paused && !this.ended && this.readyState > 2);
}
})

Adding an Animation to a RaphaelJS toggle function

I ran across this snippet of code and have "used" it as a reference for developing my own specific toggle function.
Raphael.js - if / else statement not toggling on click
http://jsfiddle.net/YLrzk/1/
I would like to apply an animation to the stroke-width per say when it is increased on click. I can't seem to figure out how to add this animation in alongside the toggle function.
I figured this would be applied around the variables StrON and StrOFF so I have tried things such as:
var strOff = function() {
this.animate({ 'stroke-width': '1' }, 100);
};
var strOn = function() {
this.animate({ 'stroke-width': '5' }, 100);
};
and even just:
var strOff =
this.animate({ 'stroke-width': '1' }, 100);
var strOn =
this.animate({ 'stroke-width': '5' }, 100);
Sorry about the lazy syntax If I missed anything on the two examples of what I've tried. Thanks for any help.
Neither of these will work because strOn and strOff are not the right data type -- they must be an object containing attributes for the selected and deselected states of a given rectangle. This represents a fundamental misunderstanding of what animate does: it is essentially an asynchronous version of attr.
You could solve your problem by simply restoring strOn and strOff to their original state and then calling this in the click handler for a given rectangle:
box1.animate( strOn, 100 );
box2.animate( strOff, 100 );
box3.animate( strOff, 100 );
This still leaves you with a complexity issue. If you want to add a fourth or fifth rectangle, you will quickly drown in conditional logic. This sort of state information should, in my opinion, almost never be implemented like this. Instead, I recommend doing this:
Use a single, generic click handler.
var genericClickHandler = function()
{
// First step: find and deselect the currently "active" rectangle
paper.forEach( function( el )
{
if ( el.data('box-sel' ) == 1 )
{
el.animate( strOff, 100 ).data('box-sel', 0 );
return false; // stops iteration
}
} );
this.animate( strOn, 100 ).data( 'box-sel', 1 );
}
This will iterate through all elements in the paper -- if one of them is marked as "active," it will be animated back into its inactive state.
Use data to keep track of the selected rectangle:
paper.rect( x1, y1, w1, h1 ).attr( {} ).data( 'box-sel', 0 ).click( genericClickHandler ); // note that we're setting data to indicate that this rectangle isn't "active"
paper.rect( x2, y2, w2, h2 ).attr( {} ).data( 'box-sel', 0 ).click( genericClickHandler );
// ... as many rectangles as you like
paper.rect( xN, yN, wN, hN ).attr( {} ).data( 'box-sel', 0 ).click( genericClickHandler );
Using this approach, there's no need to keep track of individual rectangles -- only whether or not a given rectangle is selected or not.
Here's an example supporting many rectangles.

overflow: hidden; image scrollBy

I had an idea for like a bus window as a fixed frame, about 800px wide, with a parallax city with the content on billboards spaced out so when you scroll between them it allows the parallax to look like bus is moving. The content will be much bigger than the window like a sprite and I'll put forward and back buttons that will scrollBy (x amount, 0). I have a working parallax script and a rough cityscape of 3 layers that all work fine.
I have hit a wall. I am trying to clear a scrollBy animation after it scrolls 1000px. Then you click it again and it goes another 1000px. This is my function.
function scrollForward() {
window.scrollBy(5,0);
scrollLoop = setInterval('scrollForward()',10);
}
So far I can only clear it when it gets to 1000. I tried doing 1000 || 2000 ect but after the first one it goes really fast and won't clear.
Excelsior https://stackoverflow.com/users/66580/majid-fouladpour wrote a great piece of code for someone else with a different question. It wasn't quite right for what the other guy wanted but it is perfect for me.
function scrollForward() {
var scrolledSoFar = 0;
var scrollStep = 75;
var scrollEnd = 1000;
var timerID = setInterval(function() {
window.scrollBy(scrollStep, 0);
scrolledSoFar += scrollStep;
if( scrolledSoFar >= scrollEnd ) clearInterval(timerID);
}, 10);
}
function scrollBack() {
var scrolledSoFar = 0;
var scrollStep = -75;
var scrollEnd = -1000;
var timerID = setInterval(function() {
window.scrollBy(scrollStep, 0);
scrolledSoFar += scrollStep;
if( scrolledSoFar <= scrollEnd ) clearInterval(timerID);
}, 10);
}
Now for step two figuring out how to put this content animation behind a frame.
Not quite sure what your asking here. Perhaps you could provide more relevant code?
I do see a potential issue with your code. You call setInterval('scrollForward()', 10) which will cause scrollForward to be called every 10ms. However, each of those calls to scrollForward will create more intervals to scrollForward creating a sort of explosion of recursion. You probably want to use setTimeout or create your interval outside of this function.
Also, as an aside you can change your code to simply: setInterval(scrollForward, 10). Removing the quotes and the parens makes it a littler easier to read and manager. You can even put complex, lambda functions like:
setInterval(function() {
scrollForward();
// do something else
}, 10);
edit:
So if you know that scrollForward moves the item 10px, and you want it to stop after it moves the item 1000px, then you simply need to stop it has moved that much. I still don't know how your code is actually structured, but it might look something like the following:
(function() {
var moved_by = 0;
var interval = null;
var scrollForward = function() {
// move by 10px
moved_by += 10;
if (moved_by === 1000 && interval !== null) {
clearInterval(interval);
}
};
var interval = setInterval(scrollForward, 10);
})();
If you want to clear it after 1000 or 2000, you simply adjust the if statement accordingly. I hope that helps.

Categories