I have these divs that I can toggle onclick to scale larger one at a time. It works perfectly except that once one is enlarged, one is always enlarged. I am using toggleOpen for this. I am looking to be able to make it so that it can do what it already does, but then onclick of the enlarged div have it go back to its original size without having to toggle with another div. In other words, I need a way to make the page go back to a state where all the divs are in original size. I have tried else statements to no avail as well as adding another function to remove class. I only want a js solution - no jquery or anything else please. Here is the JS portion of it.
const event = document.querySelectorAll('.eventsBorder')
function toggleOpen() {
let opened = document.getElementsByClassName('large')[0];
if(opened!=undefined)
opened.classList.toggle('large');
this.classList.toggle('large');
}
event.forEach(eventsBorder => eventsBorder.addEventListener('click', toggleOpen));
Here is my codepen
Thanks in advance for any help!
The opened variable gives you back a list of all the HTML elements which have the large class, and when you click again on an already enlarged div that automatically satisfied this criteria. So, what happens is that if you click on the same item twice, your toggleOpen function first removes the large class from that item and then adds it again because of the following line in your code-
this.classList.toggle('large');
The best way to achieve what you want would be to make sure that in addition to opened not being undefined, you should also make sure opened is not the same item as the one you clicked on. You can accomplish that using-
if(opened != undefined && opened != this)
Here is a link to the updated codepen to see it in action.
So it looks like you are using querySelectorAll to select all elements with the class "large", then you're toggling the class. If you toggle the class, it will no longer be a part of that query selection, as it no longer has that class applied, so it will not be able to remove it.
const event = document.querySelectorAll('.eventsBorder')
event.forEach(eventsBorder =>
eventsBorder.onclick = () =>
eventsBorder.classList.toggle('large'));
This seems to accomplish what you'd like.
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.)
Here is my issue; I am trying to display "markers" and on mouse over/out/click give some actions.
The problem is that no event gets fired on over at all and when on click (down) in the console I get some feedback, but not of the element per say (target == undefined).
My different shapes are groups in a group called "marker".
And I group everything with the following:
marker.add(plateShape, plateLabel, line, indicator);
When using
marker.addWithUpdate(plateShape, plateLabel, line, indicator);
I get some feedback (over being finicky at best) but the layout get completely messed up.
You can comment/uncomment Line 86 to check the behaviour in the following fiddle.
https://jsfiddle.net/u7x7az1j/5/
Thank you for your help! :)
The problem comes from your use of add.
Replace add with addWithUpdate and be aware that addWithUpdate takes one parameter at time.
marker.addWithUpdate(plateShape);
marker.addWithUpdate(plateLabel);
marker.addWithUpdate(line);
marker.addWithUpdate(indicator);
i tried on your fiddle and it works.
Consider replacing rect + triangle + line with a simple parametric fabric.Path you can easily build.
if you open the code pen there is a fire button. it will launch a bunch of ellipses and then when it hits it will cause a burst. if you look the ellipses ,which there are two sets of, they are still there. I have tried using the below
d3.selectAll("ellipse").remove()
$("ellipse").remove()
$("ellipse").each(function(){this.remove()})
http://codepen.io/daniel667/pen/QwMWrm
the code pen above will help show what im talking about the second fire button to the far right is what ive been trying to use to kill the ellipses so I don't wait for the animation the functions at the very bottom.
I would create a Raphael set, or an array and store the elemets in that, so you can reference them later to remove. If they will be used repeatedly, it may be worth not removing them, but just hiding them rather than recreating each time.
var mySet;
...
mySet = paper.set();
mySet.push( circi );
....
function throwss() {
mySet.forEach( function( el ) { el.remove(); });
}
Example: codepen
For speed, you may also want to look into Velocity.js, also be aware for animation filters can be quite resource heavy.
I am making a real estate non interactive display for their shop window.
I have kicked jCarousel into doing what I want:
Add panels per AJAX
Towards the end of the current set, go and AJAX some new panels and insert them
This works fine, but it appears calling jQuery's remove() on the prior elements cause an ugly bump. I'm not sure if calling hide() will free up any resources, as the element will still exist (and the element will be off screen anyway).
I've seen this, and tried carousel.reset() from within a callback. It just clears out all the elements.
This will be running on Google Chrome on Windows XP, and will solely be displaying on LCD televisions.
I am wondering, if I can't find a reasonable solution to remove the extra DOM elements, will it bring my application to a crawl, or will Chrome do some clever garbage collecting?
Or, how would you solve this problem?
Thanks
Could you reuse old elements instead of removing them and adding new ones ?
I worked out a fix that ended up being very simple!
Simply pass this to the config for jCarousel
itemFirstOutCallback: {
onAfterAnimation: function(carousel, li, index, state) {
if (state === 'init') return;
carousel.remove(index);
}
}
Basically, this just removes the list element as soon as it becomes invisible (scrolled into negative overflow: hidden territory, if you will :) )