I'm iterating through a CSS keyframe in order to change the original to a new animation based on the keyframe that is closest the current behavior of the element being animated. I use the function below to obtain the keyframe rules by iterating through the stylesheets.
function findKeyframesRule(rule) {
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
for (var j = 0; j < ss[i].cssRules.length; ++j) {
if (ss[i].cssRules[j].type == window.CSSRule.WEBKIT_KEYFRAMES_RULE
&& ss[i].cssRules[j].name == rule) {
return ss[i].cssRules[j];
}
}
}
return null;
}
called like so: var keyframes = findKeyframesRule(anim);
My Problem: I need to get the length/size of ss[i].cssRules[j] (or the variable keyframes because they point to the same WebkitCSSKeyframesRule object) but have been unable to find a way to do so. I found what I think is the source for CSSKeyframesRules here, but it is in C, so I am unable to use the methods they use to find the length/size, i.e. ::length and such.
I know it has a length because I can call things like keyframes[3].keyText and get a value, so it acts like an array.
I have tried keyframes.size(), keyframes.forEach, keyframes.length, and some more attempts but they don't work because it's not an array, it's a CSSKeyframesRule object. I also attempted work arounds using the ¿parent? (I don't what to call it - I mean ss, ss[i], and ss[i].cssRules) including ss[i].cssRules.length which I thought would work but it doesn't.
Here is a fiddle to show my problem
Do any of you have any ideas?
Your code is working fine. Just try to alert
keyframes.cssRules.length
will get 5
alert(keyframes.cssRules.length);
Fiddle: http://jsfiddle.net/creativevilla/T83Nc/
Related
I want to change the style of items while scrolling.
My code is working if I target the ID, but I have to target many items.
So I changed it for class name and add a "for" loop to get through every items.
It ended with the error "Cannot read property 'style' of undefined".
Can someone explain me where I am wrong ?
var gear = document.getElementsByClassName("rotate-block");
for (var i = 0; i < gear.length; i++) {
window.addEventListener("scroll", function() {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
});
};
Your code is using a closure-based access to i inside the scroll listeners.
Because you defined your index using var rather than let, all these closures reference the same i, which is evaluated when the listener is executed, not when it is defined.
After your last iteration of the for-loop, i is equal to gear.length, which means any of the listeners is trying to access gear[gear.length]. The highest index available on any array is length - 1 though.
To fix your issue, simply switch from
for (var i = 0; i < gear.length; i++)
to
for (let i = 0; i < gear.length; i++)
So this is the basis of the error you are describing...
...but
Why are you adding more than one scroll listener in the first place?
You probably instead want to iterate over gear inside the listener, at which point using var is perfectly fine since it's no longer accessed as a closure.
var gear = document.getElementsByClassName("rotate-block");
window.addEventListener("scroll", function() {
for (var i = 0; i < gear.length; i++) {
gear[i].style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});
For the future, I highly recommend to switch to using for...of to iterate over iterables:
window.addEventListener("scroll", function() {
for (const gear of document.getElementsByClassName("rotate-block")) {
gear.style.transform = "rotate("+window.pageYOffset/2+"deg)";
}
});
EDIT 2 (to make the problem more understandable)
The effect I am trying to achieve is the following: everytime an element enters the viewport an 'is-visible' class is added to it and the same 'is-visible' class is removed from the previous element.
Now I've managed to make it work but I run a for loop to remove all is-visible classes before adding the is-visible class to the element in viewport.
It works but in terms of performance I think it would be better to just remove the class from element[i -1]. And this were I can't get it working.
Here is a simplified fiddle were I try to make the element[i-1] solution work: https://jsfiddle.net/epigeyre/vm36fpuo/11/.
EDIT 1 (to answer some of the questions asked)
I have corrected an issue raised by #Catalin Iancu (thanks a lot for your precious help) by using a modulus operator ((i+len-1)%len).
ORIGINAL QUESTION (not really clear)
I am trying to get the previous element in a for loop (to change its class) with following code :
for (var i = 0; i < array.length; i++) {
if(array[i-1] && my other conditions) {
array[i-1].classList.remove('is-visible');
array[i].classList.add('is-visible');
}
}
But it's not removing the class for [i-1] element.
Here is a more complete piece of code of my module (this is running within a scroll eventlistener):
var services = document.getElementsByClassName('services'),
contRect = servicesContainer.getBoundingClientRect();
for (var i = 0; i < services.length; i++) {
var serviceRect = services[i].getBoundingClientRect();
if ( !services[i].classList.contains('active') && Math.round(serviceRect.left) < contRect.right && services[i-1]) {
services[i-1].classList.remove('is-visible');
services[i].classList.add('is-visible');
}
}
Thanks for your help!
Your if(array[i-1] && my other conditions) is always true, except for the very first case where array[-1] doesn't exist. Therefore, it will remove and then add the active class for each element, which will make it seem as only the first element's class has been removed.
What you need is a better if condition or a break statement, when the loop is not needed anymore
for (var i = 0; i < array.length; i++) {
if(array[i] && i != array.length - 1) {
array[i].classList.remove('active');
}
}
array[array.length - 1].classList.add('active');
The problem probably is that based on your code: services[i-1].classList.remove('active'); and services[i].classList.add('active'); the 'active' class you add in current iteration will be removed in next iteration!
So your code has logical errors, array index does not return all prev items!
What if you create a variable that contain the previous element?
var previous = array[0];
for (var i = 0; i < array.length; i++) {
if(previous && my other conditions) {
previous.classList.remove('active');
array[i].classList.add('active');
break;
}
previous = array[i];
}
I want to be able to loop over a few different labels and hide their content based on if a radio button is check or not. This is the solution I came up with, but I keep getting an error in the console.
var hazardOptions = $(".js-hazardous-option");
var hazard = $("input[name=Hazardous]");
for (var i = 0, len = hazard.length; i < len; i++) {
if (hazard[i].id === "HazardousYes" && hazard[i].checked) {
for (var ii = 0, length = hazardOptions.length; ii < length; ii++) {
hazardOptions[ii].show();
}
} else if (hazard[i].id === "HazardousNo" && hazard[i].checked) {
for (var iii = 0, leng = hazardOptions.length; iii < leng; iii++) {
hazardOptions[iii].hide();
}
}
}
The error I get is:
hide() is not a function
Not sure what I'm missing, I've tried having a look online for a similar issue, but with no luck. I'm pretty sure that the problem is here: hazardOptions[iii].hide(); but not really sure why and/or how to fix it.
When you have a list of objects from a JQuery selector, if you try to access them via index you actually get the DOM element back and not the JQuery object. It's confusing for sure but it is in the documentation.
What you effectively need to do is turn it back into a JQuery object:
$(hazardOptions[iii]).hide();
Or you can use the eq() function with does provide the JQuery object ad thus still has the hide() function:
hazardOptions.eq(iii).hide();
Most probably you need to wrap it with $
$(hazardOptions[ii]).hide()
As you currently have it, if hazard.id === "HazardousYes", you are showing all hazardOptions, and if it is "HazardousNo"you are hiding all of them.
You can call .show() and .hide() on a jQuery collection and it will apply that to all elements in the collection. The below code will replicate the logic of your original code, however, the hazardOptions final show/hide state will be solely determined by the last hazard that is checked and has an id equal to "HazardousYes" and "HazardousNo". This may be what you want, but I would imagine it's not.
var hazardOptions = $(".js-hazardous-option");
var hazards = $("input[name=Hazardous]");
hazards.each(function (index, hazard) {
if (hazard.checked) {
if (hazard.id === "HazardousYes") {
hazardOptions.show();
} else if (hazard.id === "HazardousNo") {
hazardOptions.hide();
}
}
}
Edit - Come to think of it, if you don't have elements with duplicate IDs, You can make this really simple:
hazardOptions.show($("#HazardousYes").is(":checked"));
hazardOptions.hide($("#HazardousNo").is(":checked"));
I have a few different tables on the same page but unfortunately they were not assigned any unique id's. I want to remove a table using a JS command, but since id cannot be used, is it possible to delete a table based on a certain attribute it has? For example, is there a command to delete all tables on the page that have the attribute: width="25%" ?
You can use querySelectorAll to do that.
var x = document.querySelectorAll("table[width='25%']");
for (var i=0; i<x.length; i++) { //returns array of elements that match the attribute selector
x[i].remove(); //call prototype method defined below
}
Removing is tricky, I found this code that makes a nice remove method
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = 0, len = this.length; i < len; i++) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
This creates a prototype remove() function that iterates the node and deletes the children.
Please note that querySelectorAll will not work in IE8 or below, but the poster of the prototype method said that it should work in IE8 but not 7.
I know this already has some solutions, but I'll offer up one more alternative.
var tables = document.getElementsByTagName('table');
for(var i = 0; i < tables.length; i++){
if(tables[i].getAttribute('width') == "25%"){
tables[i].parentNode.removeChild(tables[i]);
}
}
Demo at http://codepen.io/michaelehead/pen/HfdKx.
Yes you can. The easiest way is to use JQuery.
In your javascript code you would just write:
$("[attribute=value]").remove()
So in your case it could be something like $("table[width='25%']").remove()
I'm trying to write a Photoshop script that will show all layers of a given name. I need to loop through all the possible nested layer sets and am using the following code:
function showBounds(layerNode)
{
for(var layer in layerNode.artLayers)
{
if (layer.name == "#bounds")
{
layer.visible = 1;
}
}
showBounds(layerNode.layerSets);
}
showBounds(app.activeDocument.doc.layerSets);
But when I run it, I get the following error:
Error 1302: No such element
Line: 5
-> for(var layer in layerNode.artLayers)
artLayers should be a property of LayerSets, so I'm confused.
This is also my first attempt at scripting PS (and using javascript), so there might be some fundamental concept I am not getting.
I think you need something more like:
function showBounds(layerNode) {
for (var i=0; i<layerNode.length; i++) {
showBounds(layerNode[i].layerSets);
for(var layerIndex=0; layerIndex < layerNode[i].artLayers.length; layerIndex++) {
var layer=layerNode[i].artLayers[layerIndex];
if (layer.name == "#bounds") {
layer.visible = 1;
}
}
}
}
showBounds(app.activeDocument.layerSets);
Also, javascripts for...in syntax doesn't work the way you think it does. It's not like a foreach loop. It loops over the property names of an object.