I try to make a rotationg block, which rotation speed is controlled by <input type="range". The problem is that I can't find solution that doesn't restart animation when speed updates.
I tried three variants:
Directly set CSS animation speed by JS. — restarts animation;
jQuery's animate — doesn't work with transforms;
Library anime.js — it has method for speed changing but it also
restarts animation (or just makes block jump, it's unclear)
What method allows to create smoothly changing by JS animation?
let block = anime({
targets: '#transforms .block',
rotateY: '360',
easing: 'linear',
loop: true,
duration: 1000,
});
var el = document.querySelectorAll('#range')[0];
el.addEventListener('change', function() {
// console.log(value);
// console.log(pos);
var value = this.value;
anime.speed = value/20;
// console.log(block);
})
.block {
width: 500px;
height: 300px;
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.js"></script>
<div id="transforms">
<div class="block"></div>
</div>
<input id="range" type="range" min="0" max="20" step="1" value="10">
anime.js example code On Codepen
With velocity-animate library you can do a trick this way:
Velocity.animate(this.yourElement, {
rotateZ: `${angle}deg`,
duration: yourSpeed
})
I guess triggering the speed change at the end of the animation cycle is what you need. However with this package i was unable to attach a transitionend event listener to any of the two divs. It won't trigger. So digging into this library i suppose you may achieve a similar task by attaching a run property to the anime options object which takes a function as value and invokes it multiple times by passing a progress argument. Once this progress argument is 100 we can make our speed change operation if the speed has changed (newSpeed !== void 0).
Don' let void 0 confuse you. It's just a perfect undefined value which i prefer to use when comparing undefined values.
let block = anime({
targets : '#transforms .block',
rotateY : '360',
easing : 'linear',
loop : true,
duration: 1000,
run : anim => anim.progress === 100 && newSpeed !== void 0 && (anime.speed = newSpeed, newSpeed = void 0)
});
var range = document.getElementById('range'),
newSpeed = void 0; // perfect undefined
anime.speed = range.value/20;
range.addEventListener('change', function(e) {
newSpeed = e.target.value/20;
});
.block {
width: 500px;
height: 300px;
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.js"></script>
<div id="transforms">
<div class="block"></div>
</div>
<input id="range" type="range" min="0" max="20" step="1" value="10">
Related
I'm using anime.js to animate several divs from the right side of the screen to where they are defined by the CSS styling. I'm also creating a function to do the reverse, but I'm having several issues.
I understand how to animate an object from it's location to a destination by changing translateX, but I can't figure out how to animate moving to it's original location, like with GSAP.from(). I've tried doing this to set the div off the screen while not visible so I could move them back to the original location, but instead it seems to ignore the negative value and just continues moving in one direction.
showAnswerButtons.set(answer_button_array, {translateX: offset_num})
showAnswerButtons.add({
targets: answer_button_array,
translateX: negative_offset_num,
translateY: negative_offset_num,
duration: 5000,
delay: anime.stagger(200)
});
As an alternative to the last approach, I've also tried playing an animation to move the divs off the screen, so I could reverse the animation to bring them back. I have 4 divs to animate and sometimes I need to take some of them out of the DOM (Or at least make them invisible). So my plan was to animate them off the screen, change the visibility as needed, then bring back only the ones that I want. Unfortunately using timeline.reverse() doesn't do anything and I've read several stories of people with similar issues. I've also tried setting direction: 'reverse' and then timeline.play() with no success.
Could anyone help me with this code please?
<div id="container">
<button id="answer_button_1" class="button"></button>
<button id="answer_button_2" class="button"></button>
<button id="answer_button_3" class="button"></button>
<button id="answer_button_4" class="button"></button>
</div>
.button {
display: flex;
position: relative;
top: 200px;
left: 100px;
height: 50px;
width: 400px;
background-color: black; /*Button Color*/
color: #f5f5f5;
}
#container {
position: absolute;
right: 50%;
left: 50%;
}
var hideAnswerButtons;
var showAnswerButtons;
let hidden = true;
document.addEventListener("click", () => {
hidden = !hidden;
if (hidden) {
showAnswerButtons.play();
} else {
hideAnswerButtons.play();
}
});
defineAnimations();
function defineAnimations() {
let offset_num = 1000;
let negative_offset_num = -100;
let answer_button_array = [
answer_button_4,
answer_button_3,
answer_button_2,
answer_button_1
];
hideAnswerButtons = anime.timeline({ autoplay: false });
hideAnswerButtons.add({
targets: answer_button_array,
translateX: offset_num,
translateY: offset_num,
duration: 5000,
delay: anime.stagger(200),
easing: 'linear'
});
showAnswerButtons = anime.timeline({ autoplay: false });
//showAnswerButtons.add({
//targets: answer_button_array,
//translateX: offset_num,
//translateY: offset_num,
//duration: 0,
//easing: "linear"
//});
showAnswerButtons.set(answer_button_array, {translateX: offset_num})
showAnswerButtons.add({
targets: answer_button_array,
translateX: negative_offset_num,
translateY: negative_offset_num,
duration: 5000,
delay: anime.stagger(200),
easing: 'linear'
});
}
If you want to reverse the animation and bring the div to its orginal place you can just use "direction: 'alternate'". Theres more about it in the anime.js documentation:
https://animejs.com/documentation/
I am trying to make a code learning app (like scratch but not block based) where users write code to create games and animations. I have given them a series of commands which they can use in any desired order to create an animation sequence on multiple characters.
So far, I have no issues applying animations on a single character. However when two or more characters (two divs) come in a picture, the animations are executed parallelly instead of sequentially
example:
$("#div1").turnLeft();
$("#div1").moveForward()
$("#div2").moveForward () // this should run when first two are complete
$("#div3").moveForward() // this should run when first three are complete
The challenge is the order of commands is not fixed. User may use commands in any order.
How do I make sure the commands entered by users are run one after another?
Thanks
you must do it in call back function of each animation
$("#div1").animate({...},()=>{
$("#div1").animate({...},()=>{
$("#div2").animate({...},()=>{
$("#div3").animate({...})
})
})
});
this is a simple sample for more explain:
$("button").click(function(){
$("div").animate({
left: '250px',
opacity: '0.5',
height: '150px',
width: '150px'
},()=>{alert("finish")});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button>Start Animation</button>
<p>By default, all HTML elements have a static position, and cannot be moved. To manipulate the position, remember to first set the CSS position property of the element to relative, fixed, or absolute!</p>
<div style="background:#98bf21;height:100px;width:100px;position:absolute;"></div>
You can use jquery's queue to queue animations against a different element, then they all queue in a single queue.
Adapting this answer and the info on the jquery help applying this to "body" gives you:
$.fn.moveToX = function(pos) {
var el = $(this);
$("body").queue(
function() {
el.animate({
left: pos + "px"
}, 500, () => {
$.dequeue(this);
});
});
}
$.fn.moveToY = function(pos) {
var el = $(this);
$("body").queue(
function() {
el.animate({
top: pos + "px"
}, 500, () => {
$.dequeue(this);
});
});
}
$("#d1").moveToX(100);
$("#d2").moveToX(200);
$("#d1").moveToY(50);
$("#d2").moveToY(100);
$("#d1").moveToX(0);
$("#d2").moveToX(0);
$("#d1").moveToY(0);
$("#d2").moveToY(0);
div {
position: absolute;
width: 50px;
height: 50px;
border: 1px solid magenta;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='d1'>1</div>
<div id='d2'>2</div>
To have multiple "single" queues (ie multiple queues that can have different elements applied to them (to give parallel animations)) you can supply the "queue[name]" parameter, eg:
$("body").queue("q1", function() { ...
$("body").queue("q2", function() { ...
so your move methods can be easily extended to provide parallel animations
$.fn.moveToX = function(pos, q) {
var el = $(this);
$("body").queue(
q || "fx",
function() {
el.animate({
left: pos + "px"
}, 500, () => {
$.dequeue(this, q);
});
});
}
$.fn.moveToY = function(pos, q) {
var el = $(this);
$("body").queue(
q || "fx",
function() {
el.animate({
top: pos + "px"
}, 500, () => {
$.dequeue(this, q);
});
});
}
$("#d1").moveToX(100);
$("#d2").moveToX(200);
$("#d1").moveToY(50);
$("#d2").moveToY(100);
$("#d1").moveToX(0);
$("#d2").moveToX(0);
$("#d1").moveToY(0);
$("#d2").moveToY(0);
$("#d3").moveToX(250, "q3");
$("#d3").moveToY(50, "q3");
$("#d3").moveToX(300, "q3");
$("#d3").moveToY(0, "q3");
$("body").dequeue("q3"); // queue "fx" auto dequeues
div {
position: absolute;
width: 50px;
height: 50px;
border: 1px solid magenta;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='d1'>1</div>
<div id='d2'>2</div>
<div id='d3' style='left:300px;'>3</div>
I'm trying to use input range moving one by one. For exemple here is my slider :
<input id="replay-input" type="range" min="0" max="100" step="1" value="0">
Really basic. But the problem is that you can freely move from 5 to 89 by one click. What I would like to achieve is to increment or decrements only by one so my value will look like this is if I move the cursor :
0 -> 1 -> 2 -> 3 -> 4
Then if I go back :
4 -> 3 -> 2 -> 1 -> 0
With big max like 5000 we have missing steps because there is too many values on a small area :
4800 -> 4799 -> 4790 -> 4785
Is there a way to avoid this ? One idea may be to use JS and store value and only allow change of one but maybe there is something more simple in HTML ?
https://jsfiddle.net/vcoyxg4v/5/
Thanks !
I would not suggest limiting the default functionality of the slider control, as people expect it to work in a certain way: think also of dragging the slider, using the arrow/page/home/end keys when the control has focus, ...
But for the sake of the exercise, here is code that will make the slider value only increase/decrease with 1. So if you drag, the value will slowly catch up with the mouse position. This solution uses a data property to keep track of the previous value:
$('#replay-input').data({
was: $('#replay-input').val()
}).on('input', function () {
var data = $(this).data();
$(this).data({
was: this.value = +data.was + (this.value > data.was || -1)
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="replay-input" type="range" min="0" max="100" step="1" value="0">
Using animation
As an extension to this idea, you could use the animate method to take care of the progression towards the target value:
$('#replay-input').data({
current: $('#replay-input').val()
}).on('input', function () {
var data = $(this).data(),
value = this.value;
$(this).data(data = {
current: this.value = +data.current, // restore previous value
}).stop().animate({ value }, { // ... and animate towards the target
duration: 500, // Experiment with other durations...
easing: 'swing', // Experiment with other easing values...
step: function(val) {
$(this).data({current: this.value}) // accept change
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="replay-input" type="range" min="0" max="100" step="1" value="0">
As indicated in the comments, you can experiment a bit with the animation options duration and ease to tailor it to your needs.
There is a slight visual glitch just after a click because the slider also receives an input event on mouseup.
I am trying to achieve a situation where an animation is triggered if an element has a particular background = url(" "). I am using velocity.js as the animation plugin and am able to achieve this no problem on all browser except IE, in which it fails to recognise the background url in the if statement.
I have two examples in the code below.
The first one (the orange square) uses -
if (box.style.background == "orange")
{ run(); }
to determine whether to run the animation. IE is fine with this.
However the second (the pink triangle) uses -
if (triangle.style.background == "url(http://garyvoss.co.uk/images/triangle.svg) no-repeat")
{ runTriangle(); }
to determine whether to run the animation. IE is the only browser not to run this.
Does anyone have any idea where I'm going wrong here.
JS Fiddle example
HTML
<div id="box"></div>
<div id="triangle"></div>
CSS
#box {
width: 100px;
height: 100px;
position: absolute; }
#triangle {
width: 200px;
height: 100px;
top: 120px;
position: absolute;
z-index: 4; }
JS
window.setInterval(function runAll() {
var box = document.getElementById('box');
box.style.background = "orange";
function run() {
Velocity(box, {left: 300}, {easing: "easeInOutQuad", duration: 1000, delay: 0});
Velocity(box, {left: 10}, {easing: "easeInOutQuad", duration: 1000, delay: 0});
}
if (box.style.background == "orange")
{ run(); }
var triangle = document.getElementById('triangle');
triangle.style.background = "url(http://garyvoss.co.uk/images/triangle.svg) no-repeat";
function runTriangle() {
Velocity(triangle, {left: 300}, {easing: "easeInOutQuad", duration: 1000, delay: 200});
Velocity(triangle, {left: 10}, {easing: "easeInOutQuad", duration: 1000, delay: 0});
}
if (triangle.style.background == "url(http://garyvoss.co.uk/images/triangle.svg) no-repeat")
{ runTriangle(); }
}, 1000);
runAll();
The URLs in IE are different from webkit and/or Gecko, it is not safe to base your logic on the url paths.
if (triangle.style.background == "url(http://garyvoss.co.uk/images/triangle.svg) no-repeat" || triangle.style.background == 'url("http://garyvoss.co.uk/images/triangle.svg") no-repeat' )
{ runTriangle(); }
I've managed to find a convoluted way around the issue.
Using .match I can identify a section of the url path and apply that to the if statement so that if that section is present then the animation runs.
I've create a third example (the green circle) in this JS Fiddle that shows how it works.
var str = circle.style.background;
var res = str.match(/circle/g);
document.getElementById("check").innerHTML = res;
if (document.getElementById("check").innerHTML === "circle")
{ runCircle(); }
What did I do wrong? My skill bars are dancing. After loading I want to fixed them.
Check my pen here : http://codepen.io/anon/pen/KdEeWW
$(window).scroll(function() {
if ($(this).scrollTop() > 300) {
$('progress').each(function() {
var max = $(this).val();
$(this).val(0).animate({
value: max
}, {
duration: 2000,
easing: 'easeOutCirc'
});
});
};
});
Your skillbars were dancing because you have written the event on scroll. So if you scroll twice before completion of animation, then it will begin again and also you are not checking whether your animation has been performed.
Just .stop the animation before animating as below:
$(this).stop().val(0).animate({
value: max
}, {
duration: 2000,
easing: 'easeOutCirc'
});
Fiddle DEMO
Update
There is a bug in the way you are doing it right now. Your original value gets overridden every time you scroll thus giving space for less progress display every time. So what you need to do is, store original value in some data-* attribute of each element and set value every time from original value and thus you can also make you value=0 each time. Below changes will accommodate you with the above effect.
HTML
<progress value="80" data-originalvalue="80" max="100"></progress>
//^^^^To store original value
<span>JavaScript/jQuery</span>
<progress value="70" data-originalvalue="70" max="100"></progress>
<span>HTML5/CSS3</span>
<progress value="60" data-originalvalue="60" max="100"></progress>
<span>NodeJS</span>
<progress value="70" data-originalvalue="70" max="100"></progress>
<span>Java/PHP</span>
<progress value="60" data-originalvalue="60" max="100"></progress>
<span>MySQL</span>
<progress value="80" data-originalvalue="80" max="100"></progress>
<span>Photoshop</span>
You JS now would be
$(window).scroll(function() {
if ($(this).scrollTop() > 300) {
$('progress').each(function() {
var max = $(this).data('originalvalue'); //get from data-originalvalue attribute
$(this).val('0').stop().animate({
value: max
}, {
duration: 2000,
easing: 'easeOutCirc'
});
});
};
});
Updated DEMO
Now if you want it to animate only once when it hits users view, you just need to set a global variable value and add one more condition, as below:
var isAnimated=false; //global variable
$(window).scroll(function() {
if ($(this).scrollTop() > 300 && !isAnimated) {//Also check if animated
$('progress').each(function() {
var max = $(this).data('originalvalue');
$(this).val('0');
$(this).stop().animate({
value: max
}, {
duration: 2000,
easing: 'easeOutCirc'
});
});
isAnimated=true;//once done set it to true so that animation will not repeat.
};
});
One time animation demo
It is because you set your value 0 everytime. you can try this. It is easy to implement and you don't need to maintain all this.
http://mynameismatthieu.com/WOW/