Slide Up Linear Easing First Step Does Nothing - javascript

I'm developing an accordion plugin, and it's mostly done except for one bug where for the first few steps of the slideUp/slideDown, the accordion is 1px taller than it's meant to be, causing a visual bug. I've narrowed it down to the fact that the first step in the slideUp animation doesn't do anything, and I can't figure out why. Here's an example:
console.log('Start');
var diff = 0;
var upNow = 100;
var downNow = 0;
$.fx.interval = 1000;
var duration = $.fx.interval * 100;
$("#div1").slideUp({
easing: 'linear',
duration: duration,
step: function(now) {
if (now != 0 && now > 90) {
console.log("Slide Up: " + now);
upNow = now;
}
}
});
$("#div2").slideDown({
easing: 'linear',
duration: duration,
step: function(now) {
if (now != 0 && now < 10) {
downNow = now;
diff = 100 - (upNow + downNow);
console.log("Slide Down: " + now);
console.log("Slide Difference:" + diff);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div style='height: 100px; background-color: red;' id='div1'>
</div>
<div style='height: 100px; background-color: blue; display: none;' id='div2'>
</div>
http://jsfiddle.net/hbh6U/
The problem is that I need these to be in sync, and I can't figure out why they're not, or how to get them in sync. One idea I've had is to skip the first step of the slideDown animation, but I'm not sure how to do that either. Has anyone got any ideas, or faced this bug before?

The problem comes down to this line in jQuery's internal defaultPrefilter method:
tween.start = prop === "width" || prop === "height" ? 1 : 0;
This causes the animation for the second div (from 1px to 100px) to be shorter than that of the first div (from 0 to 100px).
To solve this modify your step function like this:
function linearStep(now, animation){
var animationStart = animation.start;
if (animationStart === 1){
animationStart = 0;
}
animation.now = (animation.end - animationStart ) * animation.pos + animationStart;
}
It overwrites the calculated now value by doing the same calculation with a fixed animationStart, which is 0 instead of 1.
This will break if the animation actually starts at 1, but there'd be other ways to handle it then.
Side-by-side Fiddle: http://jsfiddle.net/Nd3w2/3/

i don't exactly know where is this issue coming from... Sunday morning... not too much time to investigate... But i found two possible solution based on your fiddle...
First one was to wrap these two DIVs in another DIV with overflow:hidden.
Second one... probably more appropriate is to call "slide" function only on one of the divs and then update the size of second one in callback, something like that:
console.log('Start');
var diff = 0;
var upNow = 100;
var downNow = 0;
$.fx.interval = 1000;
var duration = $.fx.interval * 100;
$("#div1").slideUp({ easing: 'linear', duration: duration, step: function(now)
{
if(now != 0 && now > 90)
{
console.log("Slide Up: " + now);
upNow = now;
}
$("#div2").height(100- $("#div1").height());
}});
Also remove "disply:none" form div2 styles...
It fixes the issue and is a bit more elegant solution in my opinion... Calling two separate animation functions can lead to possible sync problems... Hope that helps...

Related

How can I make my setTimeouts get stopped here?

The behavior I'm trying to achieve is this:
On hover/mouseenter, change the background image from the placeholder to a gif whose positions changes in order to achieve an animated effect, then go back to the placeholder when the mouse leaves.
My code is
$('.filmstrip').mouseenter(function(){
var $that = $(this),
w = $that.width(),
fr = $that.attr('data-framerate');
$that.css('background-image','url('+$that.attr('data-gifurl')+')');
for ( var i = 1, n = $that.attr('data-ticks'); i <= n; ++i )
{
(function(j){
setTimeout(function(){
$that.css('background-position-x','-'+(w*j)+'px');
}, j*fr);
})(i);
}
$that.bind('mouseleave',function(){
$that.css('background-image','url('+$that.attr('data-placeholder')+')').css('background-position-x','0');
});
});
and the bug I'm having is that if the gif hasn't finished animating, then the
.css('background-position-x','0')
part of
$that.css('background-image','url('+$that.attr('data-placeholder')+')').css('background-position-x','0');
});
doesn't work because the background position is still being moved by the animation. So I need some way to first stop the setTimeout stuff if it isn't finished running. Any idea how I can do that?
This may be something better done with CSS rather than javascript.
Option #1 - Use an actual GIF
You could compile the frames which you want animated into an actual GIF file, and then have the background image change based on hover:
<div class="filmstrip"></div>
And then CSS
.filmstrip { background:transparent url('static_image.jpg') no-repeat 0 0 }
.filmstrip:hover { background-image:url( 'animated_image.gif' ) }
Option #2 - Use CSS3 Animation
You could keep the animated image as a strip of frames (of a known length) and then use something like:
<div class="filmstrip"></div>
With CSS
.filmstrip { background:transparent url('static_image.jpg') no-repeat 0 0 }
#keyframes animate-bg {
0% { background-position: 0 0 }
100% { background-position: -1000px 0 }
/* where 1000px is the length of the strip */
}
.filmstrip:hover { animation: animate-bg 5s steps(50) infinite }
/* where 5s is the overall loop length time and 50 is the number of frames in the strip */
Option #3 - Use Spritely
Spritely is a jQuery plugin which seems to manage all elements of turning a filmstrip/sprite image into an animation, including being able to start/stop the animation, reset to the first frame, change FPS, etc.
Add a stop variable :
$('.filmstrip').mouseenter(function(){
var isStopped = false;
var $that = $(this),
w = $that.width(),
fr = $that.attr('data-framerate');
$that.css('background-image','url('+$that.attr('data-gifurl')+')');
for ( var i = 1, n = $that.attr('data-ticks'); i <= n && !isStopped; ++i )
{
(function(j){
setTimeout(function(){
if (!isStopped) {
$that.css('background-position-x','-'+(w*j)+'px');
}
}, j*fr);
})(i);
}
$that.bind('mouseleave',function(){
isStopped = true;
$that.css('background-image','url('+$that.attr('data-placeholder')+')').css('background-position-x','0');
});
});
If isStopped is not accessible (because not tested) from the timeout, then just create a new variable in a inner scope which you affect isStopped value.
You can use an interval based solution like
$('.filmstrip').mouseenter(function() {
var $that = $(this),
w = $that.width(),
fr = +$that.attr('data-framerate'),
ticks = +$that.attr('data-ticks');
$that.css('background-image', 'url(' + $that.attr('data-gifurl') + ')');
var counter = 0;
var interval = setInterval(function() {
$that.css('background-position-x', '-' + (w * ++counter) + 'px');
if (counter >= ticks) {
clearInterval(interval);
}
}, fr);
$(this).data('bg-interval', interval)
}).mouseleave(function() {
clearInterval($(this).data('bg-interval'));
$(this).css('background-image', 'url(' + $(this).attr('data-placeholder') + ')').css('background-position-x', '0');
});
.filmstrip {
height: 64px;
border: 1px solid grey;
background-position: right;
background-position-y: inherit;
display: inline-block;
width: 64px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="filmstrip" data-framerate="400" data-ticks="10" data-gifurl="//cdn.sstatic.net/Sites/stackoverflow/img/sprites.svg?v=bc7c2f3904bf">
</div>
U can use clearTimeout to stop setTimeOut.
Working Demo-
var myVar;
function myFunction() {
myVar = setTimeout(function(){ alert("Hello"); }, 3000);
}
function myStopFunction() {
clearTimeout(myVar);
}
<p>Click the first button to alert "Hello" after waiting 3 seconds.</p>
<p>Click the second button to prevent the first function to execute. (You must click it before the 3 seconds are up.)</p>
<button onclick="myFunction()">Try it</button>
<button onclick="myStopFunction()">Stop the alert</button>
More-
Mozilla Developer Network
and
W3School

Javascript animation with variable speed based on cursors position

What I want to achieve is a javascript animation with variable speed based on cursor position.
For that porpouse I'm using jquery's animate function and mousever event and javascript's setInterval function, but those aren't required, so if there is a better way to achieve it I would be more than happy to hear it (the only requeriment would be javascript).
The problem I'm facing is that I can't change speed dinamicly, for some reason the speed keeps adding to the one it already had instead of set what I wanted and even if it would change as spected it just doesn't happen in a smoothly way because of an unknown reason for me.
Here is the javascript that I have so far:
//settings for container_slider. Are used in startSlider() which handles the animation
var steps_animation_speed = 1000;
var steps_interval = 1500;
var steps_speed_factor = 1; // 100%
var amount_sliders = 3;
//cache DOM elements
var $container_slider = $('#container_slider');
var $shown_slides = $('.shown_slides', $container_slider);
var $slide = $(".slide");
// Just making sure sizing (widths) fits as they should.
var slides_width = $container_slider.width()/amount_sliders;
var slides_margin = parseInt($slide.css('marginLeft').replace('px', '')) + parseInt($slide.css('marginRight').replace('px', ''));
var steps_width = slides_width + slides_margin;
$shown_slides.css('width', steps_width*(amount_sliders+1) + 'px');
$slide.css('width', slides_width);
var interval;
// This function is responsible of the animation
function startSlider() {
$shown_slides.stop(false);
interval = setInterval(function() {
$shown_slides.animate({'margin-left': '-='+steps_width}, steps_animation_speed*steps_speed_factor, function() {
$('.shown_slides > li:last').after($('.shown_slides > li:first'));
$('.shown_slides').css('margin-left', '0');
});
}, steps_interval);
}
function pauseSlider() {
clearInterval(interval);
}
$container_slider.mouseleave(function(){
steps_interval = 3000;
$shown_slides.stop(true);
pauseSlider();
startSlider();
});
// $container_slider.mouseenter(function(){
// pauseSlider();
// });
$container_slider.mousemove(function(event){
pauseSlider();
var cursor_location = '';
if(event.pageX > 0 && event.pageX < 165){
cursor_location = "Cursor is on the left side";
// This is where i'm doing the tests that should work of changing animation's speed based on cursor position
if(steps_speed_factor !== (event.pageX / 165)){
steps_speed_factor = event.pageX / 165;
steps_speed_factor = (steps_speed_factor < 0.15 ? 0.15 : steps_speed_factor);
steps_interval = 0;
startSlider();
}
} else if(event.pageX > 165 && event.pageX < ($container_slider.width()-165)){
cursor_location = "Cursor is in the center (paused)";
// This stops animation, it could be achieved way better but i'm focusing on above's block of code.
steps_speed_factor = 1;
steps_interval = 3000;
$shown_slides.stop(true);
pauseSlider();
} else if(event.pageX > ($container_slider.width()-165) && event.pageX < $container_slider.width()) {
cursor_location = "Cursor is on the right side";
// This would be an exact copy (almost) of the left side, but since it doesn't work yet, this is pretty much a "blank" block of code
steps_interval = 0;
steps_speed_factor = ( event.pageX - ($container_slider.width() - 165) ) / 165;
}
$(".coordinates").html("X: " + event.pageX + " Y: " + event.pageY );
$(".cursor_location").html(cursor_location);
$(".speed_factor").html("Speed Factor is: "+steps_speed_factor);
});
startSlider();
Here is a codepen showing this javascript code "working".
--- EDIT
I forgot to explain propperly what happens in the codepen , since it is just an example didnt give it to much importance. Mainly what should happen is that the furthier the cursor is from the center, the tinier/faster the invervals of the animation should be without losing fluidness.
In this case i'm using a "speed factor" which I calculate by taking cursor's X position and then comparing it with a predefined area, converting it in a percentage (decimal) from 15% to 99%. But it isn't actually the important part. I'm clueless about how to achieve this and the more I try the messier my code gets, so as long as you can give me an example of changing animation's speed (in "real" time, i mean, smoothly/fluid) based on cursor's position as an example it would be perfect.

Setting setInterval function is running and crashing the browser.

I have a function which is almost working.
It finds an element with the class of .hex and then runs a random number between 5 and 13 and then appends those .hex items to the body of the document.
Then I have a delay and the hexagons are faded in and out slowly.
What I want to do is to create this on a loop so they constantly fade in and out.
I have commented out the code where this should work to my understanding, but currently doesn't.
All I did was to wrap all my code in a new function called initHex() and then after the code I use setInterval to run the function every 12 seconds (at the moment).
I can see in the console the function runs, and then starts looping very fast, the numbers incrementing dramatically, and quite quickly it stops the browser responding.
I am wondering if the function is wrapped around too much of the code.
The idea is that hopefully the current function is still running (fading out hexagons) as new ones are fading in.
I am assuming at the end of the function I will also require something to remove the existing hex elements so they don't keep getting added to the document. I did try this.
Here is the function:
//function initHex() {
$rndNum = Math.floor(Math.random() * 8) + 5;
var e = $('.hex');
for (var i = 0; i < $rndNum ; i++) {
$docHeight = $(window).height();
$docHeight = Math.random() * $docHeight * 2;
$docWidth = $(window).width();
$docWidth = Math.random() * $docWidth;
$rndOpacity = Math.random();
$rndSpeed = Math.floor(Math.random() * 2000) + 2000;
e.each(function(){
$(this).css({
position: 'absolute',
top: $docHeight,
left: $docWidth - 195,
opacity: $rndOpacity
});
e.clone().prependTo('body').delay(e.length*800).fadeIn($rndSpeed).delay(1000).fadeOut($rndSpeed*2);
console.log($rndNum, $rndOpacity, $rndSpeed);
});
}
//}
//setInterval(initHex, 12000);
In terms of removing the hexagons once they have run I tried:
e.clone().prependTo('body').delay(e.length*800).fadeIn($rndSpeed).delay(1000).fadeOut($rndSpeed*2.5, function(){
e.remove();
});
Here is the fiddle: http://jsfiddle.net/lharby/j5bSz/
Fork it to your hearts content.
TIA
I have reworked your code. You made a few mistakes and yes you have tried to clone the whole bunch of elements with class hex each time, but the array have no method clone.
Try this:
function initHex() {
var $rndNum = Math.floor(Math.random() * 8) + 5;
for (var i = 0; i < $rndNum ; i++) {
var $docHeight = $(window).height()
, $top = Math.random() * $docHeight * 2
, $docWidth = $(window).width()
, $left = Math.random() * $docWidth - 195
, $rndOpacity = Math.random()
, $rndSpeed = Math.floor(Math.random() * 2000) + 2000;
$('<div></div>').addClass('hex').css({
position: 'absolute',
top: $top,
left: $left,
opacity: $rndOpacity
}).prependTo('body').delay(1000).fadeIn($rndSpeed).delay(1000).fadeOut($rndSpeed*2, function(){
this.remove();
});
}
}
setInterval(initHex, 12000);
One more thing, this code is still not optimized, but I have tried to save your code as much as possible to be more clear for you.
http://jsfiddle.net/andbas/DZSJT/1/

Web technique to resize an image over a given time interval

Is there a technique to resize an image over a given time interval?
What I want to do is have an image and when the mouse rolls overs it, it should resize the image making it larger. All I can find are simple rollover scripts that instantly resize the image. I want to do it over a period of about a second.
And as a must it cannot lag and destroy the visual experience. I am looking for an approach in javascript, jQuery, or HTML5 if it's possible; other suggestions appreciated but no flash.
It's very easy with CSS3 Transitions:
.myImg
{
width: 200px;
transition-duration: 1s;
-webkit-transition-duration: 1s;
}
.myImg:hover
{
width: 300px;
}
Demo: jsfiddle.net/yyDd4
You can do it in jQuery in this way.
var factor = 2;
$('#foo').mouseover(function() {
$(this).animate({
top: '-=' + $(this).height() / factor,
left: '-=' + $(this).width() / factor,
width: $(this).width() * factor
});
});
and the other techniques are here.
You can do this in plain javascript, though animation is always surprisingly complicated, especially if you want the image to shrink back after the mouse moves off it. Making an object to store the state is possibly the best solution and is also quite adaptable (other images, other types of animation).
http://jsfiddle.net/VceD9/6/
new GrowingImage('myImage', 2, 1000);
function GrowingImage(id, factor, duration) {
var el = document.getElementById(id),
originalWidth = el.offsetWidth,
originalHeight = el.offsetHeight,
timer,
stage = 0,
frameRate = 17,
maxStage = duration / frameRate;
el.onmouseover = function () {
animate(1);
};
el.onmouseout = function () {
animate(-1);
};
function animate(direction) {
clearInterval(timer);
timer = setInterval(function() {
stage += direction;
if (stage <= 0) {
stage = 0;
clearInterval(timer);
} else if (stage >= maxStage) {
stage = maxStage;
clearInterval(timer);
}
var scale = 1 + (factor - 1) * stage / maxStage;
el.style.width = originalWidth * scale + 'px';
el.style.height = originalHeight * scale + 'px';
}, frameRate);
}
}
If exact timing is important to you, you may need to adjust this so that it keeps track of the amount of time that the current animation has been running.

Moving a picture around slowly

I have a picture of an Air balloon. I need it to fly around my page randomly (it is kind of small). I need it only to fly in the top half of my page. I found the following code:
$("#Friends").animate({
top: "-=30px",
}, duration );
But I'm not sure how I could loop it and have it go both on the x axis and the y axis. Thanks if you can! I do have jQuery enabled :)
How about something like this.... LIVE FIDDLE
HTML
<img src="http://www.birdsnways.com/imgs/blbd48rt.gif" id="picture" />
CSS
#picture{
position:absolute;
}
JS
doNextPoint();
function doNextPoint(){
var maxX = $(window).width() - $('#picture').width();
var newX = rand(0, maxX);
var maxY = ($(window).height()/2) - $('#picture').height();
var newY = rand(0, maxY);
var speed = rand (1000, 3000);
$('#picture').animate({
'top': newY + 'px',
'left': newX + 'px'
}, speed, function(){
doNextPoint();
});
}
function rand (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
CSS
#friends { position: absolute; }
Markup
<img src="http://jsfiddle.net/img/logo.png"
id="friends"/>
JS
function moveit() {
var newTop = Math.floor(Math.random()*350);
var newLeft = Math.floor(Math.random()*1024);
var newDuration = Math.floor(Math.random()*5000);
$('#friends').animate({
top: newTop,
left: newLeft,
}, newDuration, function() {
moveit();
});
}
$(document).ready(function() {
moveit();
});
Live demo:
http://jsfiddle.net/W69s6/embedded/result/
More updated Live Demo: http://jsfiddle.net/9cN4C/
(old demo is obsolete and has a broken link, however the code is still correct so it is left for reference, not for demonstration)
You can stick that code into a named function, and then add that function as the callback parameter for the animation, so it will call itself again after it finishes.
var flying;
flying = function() {
$("#Friends").animate({
top: "-=30px", // you'll need to change this
},
duration,
flying
);
}
flying();
As is, it will just keep flying upward because the animation is always set to go up by 30 px. You'll have to change the flying function to randomize the motions a bit. For more realism, save the previous movement, and just change it by a little (small acceleration) so it doesn't have very jerky motions.
To loop it: use SetTimeout: https://developer.mozilla.org/en/window.setTimeout
For the x-axis, use the CSS property left: (top: will get you y-axis)

Categories