I want to increase the left margin by -190px with each click a maximum of 6 times. I currently have
$$('#goright').addEvent('click', function(){
$$('#buttons').tween('marginLeft', -190);
})
But this only fires once. I need to run on every click (jQuery would use something like ++) and then only to a maximum of 6 times (I assume this would also use something like < 6).
Any help???
This is one way to do it. Basically create a global var count and increment it every time by 1. On click even basically check to see if click is less than 6 and if it is grab all the buttons and do an each loop on them and then tween them individually. The trick is to get the current el's margin-left style and then convert it to int in order to drop the px and then add or subtract depends on where you want the element's margin-left to go. Here is the JSFIDDLE demoing it in action, but instead of going -190 i went with +10 so you can see the effect. Please let me know if this is what you are looking for. By the way this is using Mootools core 1.3 fully compatible:
var count = 0;
$('goright').addEvent('click', function(){
if(count < 6){
$$('.btn').each(function(el){
el.tween('margin-left', el.getStyle('margin-left').toInt()-190);
});
}
count++;
});
HTML i used:
<button class='btn'>1</button>
<button class='btn'>2</button>
<button class='btn'>3</button>
<button class='btn'>4</button>
<div id='goright'>Click To Go Right</div>
This is a funny thing to do, actually! If you can use MooTools More, there's the possibility to define pseudo events. Events, that "do something" with themselves after executing. So insted of myEl.addEvent('click', fn) you would use myEl.addEvent('click:once', fn). In MooTools, :once is defined, it fires once, then deattaces, that means, after a second click on myEl, nothing will happen (the EventListener has deatached).
I've created a :times(n) pseudo event, no big deal, but it makes clear how to use these so-called pseudos. Here is a link to it.
Maybe this can help you or someone else.
Cheers and happy coding!
Related
I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).
So, the following works fine:
document.getElementById('animationDiv').addEventListener('mouseenter', function(){
animation.play();
})
(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.
I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.
The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.
If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.
So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.
document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){
firstAnimation.play();
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){
secondAnimation.play();
})
At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.
I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.
Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?
WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)
I was easily able to make the simple, directly responsive answer by Baoo work.
I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.
Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.
//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');
if (home) home.addEventListener('mouseenter', function() {
lets_talk_home.play();
});
if (home) home.addEventListener('mouseleave', function() {
lets_talk_home.stop();
});
let snug = document.getElementById('myBtn_bm_snug');
if (snug) snug.addEventListener('mouseenter', function() {
lets_talk_snug.play();
});
if (snug) snug.addEventListener('mouseleave', function() {
lets_talk_snug.stop();
});
let fixed = document.getElementById('myBtn_bm_fixed');
if (fixed) fixed.addEventListener('mouseenter', function() {
lets_talk_fixed.play();
});
if (fixed) fixed.addEventListener('mouseleave', function() {
lets_talk_fixed.stop();
});
At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.
<div id="letsTalk" class="lets-talk">
<a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal -->
<div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
</a>
</div>
Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!
Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...
Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.
let first = document.getElementById('firstAnimationDiv');
if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});
You could approach this slightly differently using delegated event handling. mouseover, unlike mouseenter, bubbles to its ancestor elements, so you could add a single event listener to an ancestor element where every #animationDiv is contained, and switch on event.target.id to call the correct play() method:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
switch (event.target.id) {
case 'firstAnimationDiv':
return firstAnimation.play();
case 'secondAnimationDiv':
return secondAnimation.play();
// and so on
}
});
You could also avoid using id and use a more semantically correct attribute like data-animation as a compromise between this approach and #CrazyTrain's:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
// assuming <div data-animation="...">
// instead of <div id="...">
switch (event.target.dataset.animation) {
case 'first':
return firstAnimation.play();
case 'second':
return secondAnimation.play();
// and so on
}
});
First, refactor your HTML to add a common class to all of the placeholder divs instead of using unique IDs. Also add a data-animation attribute to reference the desired animation.
<div class="animation" data-animation="first"></div>
<div class="animation" data-animation="second"></div>
The data- attribute should have a value that targets the appropriate animation.
(As #PatrickRobers noted, the DOM selection can be based on the data-animation attribute, so the class isn't really needed.)
Since your animations are held as global variables, you can use the value of data-animation to look up that variable. However, it would be better if they weren't global, but were rather in a common object.
const animations = {
first: null, // your first animation
second: null, // your second animation
};
Then select the placeholder elements by class, and use the data attribute to see if the animation exists, and if so, play it.
const divs = document.querySelectorAll("div.animation");
divs.forEach(div => {
const anim = animations[div.dataset.animation];
if (anim) {
anim.play(); // Found the animation for this div, so play it
}
});
This way you're guaranteed only to work with placeholder divs that exist and animations that exist.
(And as noted above, selection using the data attribute can be done const divs = document.querySelectorAll("div[data-animation]"); so the class becomes unnecessary.)
previous question
I was looking at this question but I can't get it to apply to my problem.
Here's what I want to do:
loop through a set of html tables using a button to start
pause the animation using another pause button
resume the animation where it left off
this is something similar to what I'm working on: fiddle
My current version is too cumbersome to make an updated fiddle, but the concept is the same. I'll just be changing the content of the table like the right-most slider does.
Here's the code:
$('#Animate1').click(function(e){ //controls the tabR animation (top small table)
for(i = 1; i < 37; i++){ //number of frames in the animation
(function(i){
setTimeout(function(){
$('#amount1').val(i); //this changes the number corresponding to the slider position
updateTable2(i); //this updates the contents of the html table
updateHighlightedCell(); //this controls the "highlighted cells"
$('#hSlider').slider({ animate:true,value: i});}, 1000 ); //this animates the slider
}(i));
}
});
I'm also having trouble with the delay. I was trying to work the delay into the loop, but it seems it's just the starting delay. I'd like to be able to control the frame rate a bit.
Many of the examples I've seen stop infinite loops, but mine is finite. How can I stop this loop and start it again? My ultimate loop will have 365 "frames" in it so it won't be quite as fast.
Any help would be appreciated. Thanks!
I made a simple version in a JSFiddle of what you may be interested in, and hopefully extract the components that will be helpful to you. Also threw in a ton of comments to help understand the pieces.
The basic idea behind my method is having the setInterval() act as your for loop, and once per loop it will check to see if clearInterval() has been called on it. You will have a global counter to keep track of the position.
I'm using iosSlider to display artwork here : http://artiris.clients.twi.tl/galeries/galerie-bleue
I wish to add a blur filter to works that are outside the center. Although the plugin does not have an 'active' class setting, I could easily add one with a simple function to do this.
Here's the tricky part. I want the blur to transition in and out. Not with a time value, but rather I wish for the transition to be linked to the slider movement. For example, as an artwork approaches the center, the blur value will be connected to the movement and will be 0 when the artwork is in the center. I want this because the slider supports touch.
The slider object does provide it's real time position so I can figure out what blur value to set. However, write a do/while loop for this, the browser just crashes. Also there are onSlideStart and onSlideComplete events but I can't get my head around how to execute a function on one event and stop it on an other.
All in all, I just need someone to steer me in the right direction as to how I can do this cleanly and effectively.
Thanks
Unless you have some kind of "onDrag" event going off constantly, there are only two ways I can think of handling this.
First: a setTimeout that goes every millisecond or so, which adjusts the blur. This is almost certainly not a good option, but it is technically an option. This could cause performance issues, particularly on low-speed devices. It would also mean that the blur could be inconstant, as the timeout would probably not actually get run every millisecond.
You can't use a loop for it and enable/disable the loop according to events, as you suggested in the comments, because javascript is single-threaded: once the loop started, no other javascript will ever be run, and you'll always lock up your browser.
Second: A CSS only solution. If the movement is CSS only, then with any luck the blur can be done in the same way. Sadly I'm not a CSS expert, so I'm not sure of the best way of doing this.
Sorry, this isn't a very good answer to your question, but I wanted to explain the javascript option for you, and the reason for why you probably shouldn't do it.
iosSlider dev reporting in with some insight.
You could add your own function inside the touchmove event within the iosSlider plugin. This function would then fire alongside the iosSlider dragging. I recommend placing it somewhere after line 1806. If I was to code it, that's how I would do it.
Well here it is. http://artiris.clients.twi.tl/galeries/galerie-bleue
Not sure if it's the best way of doing it but it seems to work!
When the slider starts moving, I start a timeout which runs a function every 100ms. The timeout clears when the slider stops moving.
Here is my function :
function adjustFilter() {
var nodes = $gallerySlider.data('iosslider').slideNodes,
stage = $gallerySlider.data('iosslider').stageWidth,
matrix = $slider.css("-webkit-transform") ||
$slider.css("-moz-transform") ||
$slider.css("-ms-transform") ||
$slider.css("-o-transform") ||
$slider.css("transform"),
matrixArray = matrixToArray(matrix),
position = Math.abs(matrixArray[4]);
for(var i = 0; i < nodes.length; i++) {
var $node = $(nodes[i]),
nodeMatrix = $node.css("-webkit-transform") ||
$node.css("-moz-transform") ||
$node.css("-ms-transform") ||
$node.css("-o-transform") ||
$node.css("transform"),
nodeMatrixArray = matrixToArray(nodeMatrix),
nodePosition = Math.abs(nodeMatrixArray[4]),
distance = Math.abs(Math.min(Math.max(parseInt(nodePosition-position), stage*(-1)), stage)),
tx = distance/stage;
$node.css({ '-webkit-filter' : 'blur('+tx*10/2+'px) grayscale('+tx+')' })
}
}
Basically it gets the position of the slider. Then for each node I get it's current position and calculates the distance from the center. This distance is than normalised so that it's never greater than the width of a slide. I then use that number to set the blur and grayscale for every node in real time. This way the effect is continuous.
So far, performance seems to be good. In any case, I'm going to run the function only when the filter property is supported by the browser.
If anyone can suggest on ways I can improve this code, I'm all ears.
Essentially I have 4 divs that take turns sliding in and sliding out with delays and then it recalls the function. Like so:
$(document).ready (function bradslide(){
$("#slide1").delay('1000').slideDown('1000').delay('6000').slideUp('1000');
$("#slide2").delay('9000').slideDown('1000').delay('6000').slideUp('1000');
$("#slide3").delay('17000').slideDown('1000').delay('6000').slideUp('1000');
$("#slide4").delay('25000').slideDown('1000').delay('6000').slideUp('1000', 'swing', bradslide);
}
);
Let me say that this works fine, but that I am open to cleaning it up or making it easier or more up to standard if suggestions are made.
However my question is this: How can I arrange this so that the end user can manipulate the animation. This slides through the divs on its own, but ideally I would like to have a couple buttons to click to go backward or forwards (I think you get the idea).
Any suggestions of how or where to begin would be greatly appreciated. I imagine I might have to scrap this little piece of code as it stands. Thanks in advance guys.
Despite my own comment, I do have some general advice:
Look into using classes instead of IDs, and then use jQuery's DOM-traversal methods to identify what the next slider candidate is. Tracking the "currentSlide" and then targeting the "nextSlide" (identified with a .next() perhaps?) means that you can add any number of slider divs (with a class instead of ID, remember?) and still have it work.
The user controls (next, prev, or selecting a specific slide) simply interrupt the timer (probably a setTimeout instead of .delay()) and then invoke the exact same function that brings the next slide into place.
To make code more reusable and flexible, you should use some variables. For example, if your slide duration is going to be 1000, you would have var duration = 1000 scoped to an appropriate place (the document ready function is fine... or the sliding function) and then in your function call (whatever it ends up looking like), you would use .slideDown(duration). Then you can set that value to whatever you want and update it easily later.
Extending on the above, you could even build an API allowing you to pass values into your custom slider function:
var bradslide = function(container, delay, duration) {
// do stuff with a parent container, some delay value, and a duration value
};
bradslide('sliderParent', 6000, 1000);
I have some jQuery code, which attempts to show the first 6 divs on page load and hide all of the others. It is littered with errors, but ideally I am trying to create a function that shows the next six divs on an event, ultimately when the user scrolls to the bottom. I know my code is not great, but I have done my best to make it as easy to follow as possible.
The code is here, and any help would be much appreciated! Thanks in advance
I think this is what you wanted:
http://jsfiddle.net/gRzPF/8/
If I understand correctly every time you get to the bottom of the window you want to show the next 6 divs. My edit achieves that.
You just needed to use semi-colons in your for statement, wrap a function around it and move your constraintNumber variable inside that function.
replace
for (i = contentNumber, i < constraintNumber, i++;) {
by
for (i = contentNumber; i < constraintNumber; i++) {
in javascript (and C), ; must separate the 3 elements of a for statement
in jsfiddle, you have 'JSLint' button to verify code error !! Use it !
Here http://jsfiddle.net/gRzPF/7/ I modified your code, now it seems to work :)