Implementing jQuery's shake effect with animate - javascript

I've been given a cut down subset of the jQuery lib one of the key features I'm missing is the .effect functions. I do however have .animate. I was wondering if anyone would have any ideas how I could go about reproducing the animation functions.
I am particularly consious of making this only a few lines as I need to keep the code size down. Which is why the jquery lib is as small as it is and doesnt have the effects functions.
TLDR - I'm trying to replace
$("#"+id_string).effect( "shake", {}, "fast" );
With something using .animate within jQuery.

So far I have something like this ..
jQuery.fn.shake = function(intShakes, intDistance, intDuration) {
this.each(function() {
$(this).css("position","relative");
for (var x=1; x<=intShakes; x++) {
$(this).animate({left:(intDistance*-1)}, (((intDuration/intShakes)/4)))
.animate({left:intDistance}, ((intDuration/intShakes)/2))
.animate({left:0}, (((intDuration/intShakes)/4)));
}
});
return this;
};

I like #phpslightly solution so much, I keep using it. So here it is updated to basic jquery plugin form which will return your element
jQuery.fn.shake = function(interval,distance,times){
interval = typeof interval == "undefined" ? 100 : interval;
distance = typeof distance == "undefined" ? 10 : distance;
times = typeof times == "undefined" ? 3 : times;
var jTarget = $(this);
jTarget.css('position','relative');
for(var iter=0;iter<(times+1);iter++){
jTarget.animate({ left: ((iter%2==0 ? distance : distance*-1))}, interval);
}
return jTarget.animate({ left: 0},interval);
}
You would then use it like a regular plugin:
$("#your-element").shake(100,10,3);
Or use the default values (100, 10, 3):
$("#your-element").shake();

It's actually already implemented this way under the covers, you can see exactly how in jquery.effects.shake.js, if you wanted to copy only that functionality you can.
Another approach to think about: if you're using multiple effects, I'd recommend downloading jQuery UI with only the effects you want. For this effect, without copying the functionality yourself, you would just need jquery.effects.core.js and jquery.effects.shake.js.

This is probably irrelevant now but I've ported jQ UI's shake effect as a standalone jQuery plugin. All you need is jQuery and it will work exactly like the one provided in jQ UI.
For those who want to use the effect without actually bloating their project with unnecessary jQ UI core files.
$('#element').shake({...});
It can be found here with instruction: https://github.com/ninty9notout/jquery-shake
Thought I'd leave this here for future reference.

This is a more clean and smooth way to do the animation.
jQuery.fn.shake = function(shakes, distance, duration) {
if(shakes > 0) {
this.each(function() {
var $el = $(this);
var left = $el.css('left');
$el.animate({left: "-=" + distance}, duration, function(){
$el.animate({left: "+=" + distance * 2}, duration, function() {
$el.animate({left: left}, duration, function() {
$el.shake(shakes-1, distance, duration); });});
});
});
}
return this;
};

I don't understand all the complexity being thrown into reproducing the shake effect with solely animate. Here's my solution in just a couple lines.
function shake(div,interval=100,distance=10,times=4){
$(div).css('position','relative');
for(var iter=0;iter<(times+1);iter++){
$(div).animate({ left: ((iter%2==0 ? distance : distance*-1))}, interval);
}//for
$(div).animate({ left: 0},interval);
}//shake
EDIT: Updated code to return element to original position. Still believe this is the lightest and best solution to the problem.

I wrote some time ago a few simple jquery animations:
https://github.com/yckart/jquery-custom-animations
/**
* #param {number} times - The number of shakes
* #param {number} duration - The speed amount
* #param {string} easing - The easing method
* #param {function} complete - A callback function
*/
jQuery.fn.shake =
jQuery.fn.wiggle = function (times, duration, easing, complete) {
var self = this;
if (times > 0) {
this.animate({
marginLeft: times-- % 2 === 0 ? -15 : 15
}, duration, easing, function () {
self.wiggle(times, duration, easing, complete);
});
} else {
this.animate({
marginLeft: 0
}, duration, easing, function () {
if (jQuery.isFunction(complete)) {
complete();
}
});
}
return this;
};

This is not perfect, but functional
// Example: $('#<% =ButtonTest.ClientID %>').myshake(3, 120, 3, false);
jQuery.fn.myshake = function (steps, duration, amount, vertical) {
var s = steps || 3;
var d = duration || 120;
var a = amount || 3;
var v = vertical || false;
this.css('position', 'relative');
var cur = parseInt(this.css(v ? "top" : "left"), 10);
if (isNaN(cur))
cur = 0;
var ds = d / s;
if (v) {
for (i = 0; i < s; i++)
this.animate({ "top": cur + a + "px" }, ds).animate({ "top": cur - a + "px" }, ds);
this.animate({ "top": cur }, 20);
}
else {
for (i = 0; i < s; i++)
this.animate({ "left": cur + a }, ds).animate({ "left": cur - a + "px" }, ds);
this.animate({ "left": cur }, 20);
}
return this;
}

Based on #el producer solution, I added some multiply logic and make it look like a random shake.
jQuery.fn.shake = function (interval, distance, times) {
interval = typeof interval == "undefined" ? 100 : interval;
distance = typeof distance == "undefined" ? 10 : distance;
times = typeof times == "undefined" ? 3 : times;
var jTarget = $(this);
jTarget.css('position', 'relative');
for (var iter = 0; iter < (times + 1) ; iter++) {
jTarget.animate({ top: ((iter % 2 == 0 ? distance * Math.random() : distance * Math.random() * -1)), left: ((iter % 2 == 0 ? distance * Math.random() : distance * Math.random() * -1)) }, interval);
}
return jTarget.animate({ top: 0 , left: 0 }, interval);
}

Position had to be absolute on my side, so I changed it to:
jQuery.fn.shake = function(interval, distance, times) {
interval = typeof interval == "undefined" ? 100 : interval;
distance = typeof distance == "undefined" ? 10 : distance;
times = typeof times == "undefined" ? 3 : times;
var jTarget = $(this);
for (var iter=0;iter<(times+1);iter++) {
jTarget.animate({ 'padding-left': ((iter%2==0 ? distance : distance*-1))}, interval);
}
return jTarget.animate({ 'padding-left': 0 } ,interval);
}

Related

understanding safari SVG redraw bug fix.

I was just going through THIS snap sbg demo and i came across the following lines of code:
var flag,
len = Snap.path.getTotalLength(pth.attr("d"));
Snap.animate(0, len, function (l) {
// Safari bug workaround: forcing redraw
g.attr({width: 100 + (flag = !flag ? 1e-5 : 0) + "%"});
//
var dot = pth.getPointAtLength(l);
flight.attr({
d: pth.getSubpath(0, l)
});
pln.attr({
transform: "t" + [dot.x, dot.y] +
"r" + (dot.alpha - 90)
});
gr.attr({
transform: getShift(dot)
});
}, 10000);
Now i am not quite understanding the below line of code:
g.attr({width: 100 + (flag = !flag ? 1e-5 : 0) + "%"});
What exactly is 1e-5 ? can somebody explian ?
flag = (!flag ? 1e-5 : 0) + "%"
is the same as:
if(!flag) {
flag = 0.00001; //1e-5 is the scientific notation for 1^-5
} else {
flag = 0;
}
flag = flag + "%";
I am not familiar with Snap, but it seems like the code is changing the width property every frame from 100% to 100.00001%, causing a redraw. 1e-5, as mentoined in comments is number written using scientific notation for real numbers and is equal to 10^-5 = 0.00001.

Detect end of animation of last element in each()

I have an animation of SVG paths and I would like to eg. addClass after last of element in paths is done with animation. I tried to add a callback for animate() but it doesnt work, it started when the first loop ended. then I tried .when(startAnimeDude()).then(doStuff()) but also it started at the beginning. Last thing what I tried is i == i.length -1 and also it started at the beginning( while the animation is still going). What I am missing ?
Thanks for reading.
DEMO: http://jsfiddle.net/18yunbhk/
JS
var anim = function () {
$.extend(jQuery.easing, {
easeInOutQuad: function (x, t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
});
var animeDude = function (parent, min, max, delayAnim) {
var paths = $('svg').find('path');
$.each(paths, function (i) {
var totalLength = this.getTotalLength();
$(this).css({
'stroke-dashoffset': totalLength,
'stroke-dasharray': totalLength + ' ' + totalLength
});
$(this).delay(delayAnim * i).animate({
'stroke-dashoffset': 0
}, {
duration: Math.floor(Math.random() * max) + min,
easing: 'easeInOutQuad'
});
if (i == 12) { //12 is last element
console.log('sad');
}
});
};
var startAnimeDude = function (parent) {
animeDude(parent, 0, 1000, 40);
};
startAnimeDude($('svg'));
};
anim();
Something like
if (i === (paths.length - 1)) {
console.log("sad");
}
JSFIDDLE
If you want the condition to be checked only when the animation is complete, then you can write that piece of code in the callback function for animation.
$(this).delay(delayAnim * i).animate({
'stroke-dashoffset': 0
}, {
duration: Math.floor(Math.random() * max) + min,
easing: 'easeInOutQuad',
complete: function() {
if (i === (paths.length - 1)) { //12 is last element
console.log('sad');
}
}
});

How to use "+=" assignment operator with variable inside animate?

I'm struggling here to solve this.
http://jsfiddle.net/yhcqfy44/
The animation is supposed to automaticaly scroll top relative to <span> height each time when the scroll bar appears.
I have write this but with no luck:
var hheight = $('<span>').height();
var i = 0;
var blackposition;
var square = $('<span></span>').first();
var endless = setInterval(function() {
if (i % 4 == 0) {
blackposition = Math.floor(4 * Math.random());
}
var math = (blackposition == (i % 4)) ? 0 : 1;
square.clone().addClass('color_' + math).text((math < 1) ? 'even' + i : 'odd' + i).appendTo('#container');
i++;
$('body,html').animate({
scrollTop: '+=' + hheight + 'px'
}, 1000, 'linear')
}, 500);
$(window).on('scroll', function() {
if ($("span").offset().top + $("span").height() < $(window).scrollTop()) {
$("span").slice(0, 4).remove();
};
});
Is there a solution for doing this ?
You are providing a string for the scrollTop property, and JavaScript is just going to treat that as a string and not an operator, apart from that I don't think you can have an operator there so I would try something like this scrollTop: $(document).height() + 'px' I think that does what you want it to, take a look at this http://jsfiddle.net/yxbj0fen/2/
Basically that just scrolls to the bottom of the document, but you can replace that with another container (e.g. a div)

Trigger Alert When Slider Moves Left A Certain Amount

So, I am going to actually kill two birds with one stone. First off, I am trying to trigger an alert once the slider moves left to a specific degree. For example, once it goes to, let’s say, the 3rd slide I would like for it to trigger an alert. Also, I need help with the cycling of the slider. I want the slider to cycle like a rotation (360), but instead at the end of the cycle it slides all the way back to the start. View the Codepen to have a better understanding of what I mean. Thank you for your time. Your help is much appreciated.
Live Codepen
var W = $('#image_rotator').width(),
N = $('#slider .slide').length,
C = 0,
intv;
if (N <= 1) $('#left, #right').hide();
$('#slider').width(W * N);
$('#left, #right').click(function() {
C = (this.id == 'right' ? ++C : --C) < 0 ? N - 1 : C % N;
$('#slider').stop().animate({
left: -C * W
}, 300);
});
function setResetInterval(e) {
var intv = $("#slider");
if (e) {
timer = setInterval(function() {
$('#right').click();
}, 1000);
} else {
clearInterval(timer);
}
$('#slider').click(function() {
var x = $('#slider').offset();
alert("Top: " + x.top + " Left: " + x.left);
});
}
$("#btn").click(function(e) {
e.preventDefault();
setResetInterval(true);
});
$("#btn2").click(function(e) {
e.preventDefault();
setResetInterval(false);
});
$('#slider').hover(function(e) {
return e.type == 'mouseenter' ? setResetInterval(false) : setResetInterval(true);
});
This accomplishes both of the things that you wanted using the optional callback parameter in the animate function. It checks first to see if the variable C (the 0-based index of the current slide) is equal to 2 (3 in 1-based indexing), and shows an alert if it is. Then it checks to see if C is equal to 10 (which would mean that the slide currently being shown is the 9th one; The 9th image is just a duplicate of the 1st one) If it is on the 9th one, then jump back to the first one and trigger $("#right").click();.
$('#left, #right').click(function() {
C = (this.id == 'right' ? ++C : --C) < 0 ? N - 1 : C % N;
$('#slider').stop().animate({
left: -C * W
}, 300, function() {
if (C == 2){alert("3rd");}
if (C == 10) {
$('#slider').css("left", "0");
$('#right').click();
}
});
});
JSFiddle (Because CodePen's layout is weird to me)

Is it possible to combine adding and subtracting into one function?

I have these two buttons, one that calls minus(val) and the other calls plus(val) function.
function minus(val) {
val <= 1 ? width = 1 : width = val - 1;
}
function plus(val) {
val >= 5 ? width = 5 : width = val + 1;
}
Is it possible to combine both these functions into one? So I can just call one function on each button, like changeValue(val);
Thanks in advance.
Is it possible to combine both these functions into one?
No. They do different things, e.g. when you call them with the value 3.
You can see that better if you simplify them to
function minus(val) {
width = Math.max(1, val - 1);
}
function plus(val) {
width = Math.min(5, val + 1);
}
Of course you might use a function with a second parameter that decides what to do. It could be
function changeValue(val, dir) {
width = Math.min(5, Math.max(1, val + dir));
}
that you could call both as changeValue(val, +1) and changeValue(val, -1).
Or right away, with a more descriptive name:
limitedWidth(val) {
width = Math.min(5, Math.max(1, val));
}
// limitedWidth(val + 1)
// limitedWidth(val - 1)
function changeValue(val){
if(val > 5){ };
else if(val < 1){ };
}
Can you get the rest?

Categories