How to make elements interact with each other and position with jquery - javascript

$(document).ready(function() {
//ENTRANCE
$("#first").css("top", -1000);
setTimeout(function() {
$("#first").animate({
top: 10
}, 400);
}, 200);
$("#second").css("top", -1000);
setTimeout(function() {
$("#second").animate({
top: 10 + 45 + 15
}, 400);
}, 400);
$("#third").css("top", -1000);
setTimeout(function() {
$("#third").animate({
top: 10 + 45 + 45 + 30
}, 400);
}, 600);
$("#four").css("top", -1000);
setTimeout(function() {
$("#four").animate({
top: 10 + 45 + 45 + 45 + 45
}, 400);
}, 800);
//EXIT
$('#first').on('click', function() {
$('#first').toggle();
$('#second').animate({top: 5}, 400);
});
$('#second').on('click', function() {
$('#second').toggle();
$('#third').animate({top: 5}, 400);
});
$('#third').on('click', function() {
$('#third').toggle();
$('#four').animate({top: 5}, 400);
});
$('#four').on('click', function() {
window.location.reload();
});
});
`
I have been trying for a while to make elements interact with each other using jquery, Here is a
Fiddle of my code.
I have although been having a few hiccups.
In a real world environment, elements may not be called in ascending or logical order.
Items do not animate properly when closed, there are gaps and in some cases, some items do not move depending on which is clicked.
There may be more than 4 items.
Here is my question: How can i make the elements animate and cover properly regardless of which item is clicked and what order the items are sorted.

please see this fiddle
var elements = $('.menu');// Here you can write any selector to get list of elements
elements.on('click', function() {
$(this).toggle();
var nextEleemnts = $(this).nextAll('.menu'); // Same selector will follow here
for (var i = 0; i < nextEleemnts.length; i++) {
var topPos = $(nextEleemnts[i]).position().top - 60; //little bit of counting
$(nextEleemnts[i]).animate({
top: topPos
}, 400);
}
});
There is also a good solution and straight forward solution provided to you by a guy in comment, For this you need to do a bit of change in CSS aswell, so if you don't want to do it, then you can take my approach aswell
Here I am talking an alternate approach, here what I am doing whenever you click on any element I am finding it's next siblings and position them up by 60 pixels

If I were you, I would consider using jqueryUI
But maybe you have some restrictions of some kind.
I came up with a solution, in which I use jquery gt selector to select elements after the one clicked.
Please note that html is almost empty, which allows to add as many elements as you like.
(By the way I wouldn't make elements position absolute as well, but that's another story.
$(document).ready(function() {
"use strict";
var childCount = 12;
// some templating library would make a better job
for (var i = 0; i < childCount; ++i) {
var child = $("<div>" + i + "th div </div>");
child.css("background-color", "#" + ((1 << 24) * Math.random() | 0).toString(16));
child.css("top", i * 50);
$("#parent").append(child); // add any transition here
}
var reset = $("<div id='reset'>Reset</div>")
.css("background-color", "black")
.css("color", "white")
.css("top", childCount * 50);
$("#parent").append(reset);
$("#parent > div").on("click", function() {
var clicked = $(this);
var index = $("#parent > div").index(clicked);
$("#parent > div:gt(" + (index - 1) + ")").add(reset).animate({
top: "-=50"
}, 100, function() {
clicked.remove();
});
childCount -= 1;
});
});
#parent > div {
width: 100px;
height: 40px;
margin-bottom: 10px;
position: absolute;
text-align: center;
-webkit-transition: all .5s;
-moz-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
<!-- some data-bind attribute would be better than an id -->
</div>

Related

Animate element using Javascript vanilla

How can I animate an element with Javascript vanilla?
Similar with jquery. Example:
$( "button.continue" ).animate({left: "100px", top: "200px"}, 5000);
Where we pass the attribute, the desired value and the time.
In my case I need the left and top position to animate and slide.
Solution
I've done as #IceMetalPunk sugested and added the animation by css only when I need to animate.
document.getElementById("animate").addEventListener("click", function(){
let e = document.getElementById('elementToAnimate');
e.classList.add("animate");
setTimeout(()=> e.classList.remove("animate"), 500);
e.style.top = Math.floor(Math.random() * window.innerHeight) + 'px';
e.style.left = Math.floor(Math.random() * window.innerWidth) + 'px';
});
document.getElementById("dontAnimate").addEventListener("click", function(){
let e = document.getElementById('elementToAnimate');
e.style.top = Math.floor(Math.random() * window.innerHeight) + 'px';
e.style.left = Math.floor(Math.random() * window.innerWidth) + 'px';
});
#elementToAnimate {
width: 20px;
height: 20px;
background: red;
position: absolute;
top: 0;
left: 0;
}
#elementToAnimate.animate {
transition: left 500ms ease-in-out, top 500ms ease-in-out;
}
<div id="elementToAnimate"></div>
<button id="animate">Animate</button>
<button id="dontAnimate">Dont Animate</button>
Try using CSS transitions. In CSS, do something like this:
button.continue {
transition: 5s;
}
Then in JS, you can just set the left/top values:
document.querySelectorAll('button.continue').forEach(button => {
button.style.left = '100px';
button.style.top = '200px';
});
Tested in Firefox.
document
.querySelector(".text.thank-you")
.animate({ opacity: [0, 1] }, { duration: 5000, iterations: 1, easing: "ease-out" })
.onfinish = (e) => {
e.target.effect.target.style.opacity = 1;
};
Take a look at the Web Animation API. Its an experimental feature so Browser Support (mainly Safari) might be an issue for you. Otherwise you can do it the way others have already pointed out.
Try using WebComponents, you can perform any kind of animation using only VanillaJS, there are predefined animations like Animate.css but you can create custom animations by using keyFrames:
<!-- Add Web Animations Polyfill :) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/web-animations/2.3.2/web-animations.min.js"></script>
<script type="module" src="https://unpkg.com/#proyecto26/animatable-component#1.0.0/dist/animatable-component/animatable-component.esm.js"></script>
<script nomodule="" src="https://unpkg.com/#proyecto26/animatable-component#1.0.0/dist/animatable-component/animatable-component.js"></script>
<animatable-component
autoplay
easing="ease-in-out"
duration="1200"
delay="300"
animation="heartBeat"
iterations="Infinity"
style="display: flex;align-self: center;"
>
<h1>Hello World</h1>
</animatable-component>
For more details check here: https://github.com/proyecto26/animatable-component

generate animation as if it were a marquee

I do not know much about css, but I think this code could help me generate a marquee. basically I want the animation that is done with the boxes, be done with the texts.
My main problem occurs with the animation, it is not very fluid, I want it to be more fluid and it starts from the end of the container to the left. How can I do it? I would be very grateful.
http://jsfiddle.net/joof5dhx/
<div id="horizontalScroller">
<div>it is a message a little more of 100 characteres</div>
<div>it is a message a little more of 110 characteres</div>
<div>it is a message a little more of 120 characteres</div>
<div>it is a message a little more of 130 characteres</div>
</div>
window.horizontalScroller = function($elem) {
var left = parseInt($elem.css("left"));
var temp = -1 * $('#horizontalScroller > div').height();
if(left < temp) {
left = $('#horizontalScroller').height()
$elem.css("left", left);
}
$elem.animate({ left: (parseInt(left)-60) }, 900, function () {
window.horizontalScroller($(this))
});
}
$(document).ready(function() {
var i = 0;
$("#horizontalScroller > div").each(function () {
$(this).css("left", i);
i += 60;
window.horizontalScroller($(this));
});
});
http://jsfiddle.net/hhcbtyyg/
You could just:
window.horizontalScroller = function($elem)
{
var left = parseInt($elem.css("left"));
$elem.animate({ left: (parseInt(left)-60) }, 900, function ()
{
// get the current left of the element
var currentLeft = parseInt($(this).css("left"));
// get the width of the element
var width = $(this).width();
// get the container
var container = $(this).parent("#horizontalScroller");
// get the width of the container
var containerWidth = $(container).width();
// check if the element goes out of the view item X + item w < 0
if ((currentLeft + width) <= 0)
{
// put it on the opposite side: simply container w + item w
$(this).css("left", (containerWidth + width) + "px");
}
window.horizontalScroller($(this))
});
}
I just don't understand why you use height in your code above. If there is something I don't know let me know.
UPDATED:
To make the items appear on the leftmost by default:
$(document).ready(function() {
var container = $("#horizontalScroller");
var children = $(container).children();
var containerW = $(container).width();
// Loop through each item of container
for (var i = 0; i < children.length; i++)
{
var item = children[i];
var itemW = $(item).width();
// this is simply the space between them, remove if you don't need it
var padding = 10 * (i + 1);
// simply: padding + Container Width + (Item Width * (i + 1))
// (Item Width * (i + 1)) because you need to position each element beside each other.
$(item).css("left", (padding + containerW + itemW * (i + 1)) + "px");
window.horizontalScroller($(item));
}
});
your updated fiddle
hope that helps
Hi checked this version of your jsfiddle, i did some modificaitons, since your animation starts from whatever the value of height is your div had. check this I tried to match the height of your css and width in your css, i just noticed that the "left" var in your js gets the height of your element.
CSS:
#horizontalScroller {
position: absolute;
width:300px;
height: 300px;
border: 1px solid red;
overflow: hidden;
}
Maybe you can get some tips how to accomplish it in responsive way.
JSFIDDLE

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

Looping css style with setTimeout: "in the blink of an eye"

I want to make a bar (#innerBar) to decrease 1% in width per second.
The loop doesn't seem to work. My bar drops from 100% to 0% in the blink of an eye.
function timer(){
var timer;
for(i=100;i>=0;i--){
timer = i.toString() + "%";
setTimeout(function({$('#innerBar').css("width", timer)}, ((100-i)*1000));
}
}
Note : #innerBar is a DIV with a css property (height:10px). ** + the width from timer(); **
As already said in the comments, you need to put it in the closure. Here's an example:
function timer() {
for (i = 100; i >= 0; i--) {
setTimeout(function(t) {
return function() {
var timer = t.toString() + "%";
$('#innerBar').css("width", timer);
};
}(i), ((100 - i) * 1000));
}
}
timer();
#innerBar {height: 50px; background: green; transition: width 0.2s linear}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="innerBar"></div>
EXPLANATION
So my question are: what is going throught function(t)? and why and how does }(i) work? Is it a multiplication of the fu?
Let's take the function body we're passing in to setTimeout:
function(t) {
return function() {
var timer = t.toString() + "%";
$('#innerBar').css("width", timer);
};
}(i)
Let's omit the inside part:
function(t) {
// do some stuff with t
}(i)
Looks familiar? It's like the function body is called right away and is called an IIFE, just like, say:
(function(a, b) {
return a + b;
})(2, 3) // returns 5
So back to the original function, it accepts one parameter, t, and when we're calling the function we're passing in the iterator i as an argument (so the value of i becomes t inside the function). As I said in the comment, this is necessary in order to "fetch" the current value of i instead of getting the post-loop value.
As #Shomz already posted. That is good solution. I simply want to add my solution because it does not create 100 functions. So it's slightly lighter on the memory. Also you don't have to look through the DOM for #innerBar over and over again. And I removed jQuery as a dependency.
var size = 100;
var bar = document.getElementById( "innerBar" );
function setSize() {
bar.style.width = size-- + "%";
if ( size > 0 ) setTimeout( setSize, 1000 );
}
setSize();
#innerBar {
width: 100%;
height: 50px;
background: green;
transition: width 0.2s linear;
}
<div id="innerBar"></div>
I think the following code does what you want. the input time should be 1000, which will decrease you width by 1% every second
var width = $('#innerBar').width();
function timeLoop(time){
width = width*0.99;
$('#innerBar').css("width", width);
if (width <= 0.01){
return;
}
else {
setTimeout(function() {
timeLoop(time);
}, time);
}
}

Slide Up Linear Easing First Step Does Nothing

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...

Categories