List only rendered styles, not arbitrary ones that aren't applied
I've tried many things to get the styles applied to an element but have come up blank.
Please do not cite getComputedStyle as being a solution unless you can solve the junk returns issue.
The primary problem is that window.getComputedStyle(document.querySelector('ANY ELEMENT')).fill will return "rgb(0, 0, 0)", which is not the correct style in almost any instances, and has no apparent way to destinguish if its actually being applied or not.
The above example is not the only problem case; there are tons of rules returned by getComputedStyle which are wrong and will drastically change the look of the page if they are applied.
Static parsing is not an option as there are cases where the .css files are on another server with no cross-origin headers; which also hides styles usually found in document.styleSheets.
Is there any way to get a list of the applied styles and nothing else?
As requested this code will demonstrate the problem (on Chrome):
var all = document.getElementsByTagName('*');
for(var i in all)
if (all[i].style) all[i].style.cssText = window.getComputedStyle(all[i]).cssText;
EDIT: My answer has code which works on all browsers. I keep above to preserve comment thread.
Here are the version that don't need to check depth.
The problem in your code is the assign of inline style in the previous element will affect the getComputedStyle result of the next result. It mean the value of getComputedStyle is always changing in the loop. You can first store it in an array like this.
var all = document.getElementsByTagName('*');
tmpArr = []
for(var i in all) {
if (all[i].style) {
tmpArr[i] = window.getComputedStyle(all[i]).cssText;
}
}
for(var i in all) {
if (all[i].style) {
all[i].style.cssText = tmpArr[i]; ;
}
}
console.log("finish");
You can change tmpArr[i] = window.getComputedStyle(all[i]).cssText; to tmpArr[i] = window.getComputedStyle(all[i]).cssText + "-webkit-text-fill-color:#691099!important"; to test whether it work
It will be slow if you open the inspector since there are too much inline style, but it will solve the problem if all you need is just put the style to be inline style.
Partial Answer (Updated):
It is possible to get only the active styles by calling my function getRenderedStyles:
getRenderedStyles now bypasses active stylesheets for more accurate output.
function getRenderedStyles(element) {
var tmpele, tmpstyle, elestyle, varstyle, elecolor, eletag;
var styles = {};
var defstyle = {};
elestyle = window.getComputedStyle(element);
elecolor = elestyle.color;
eletag = element.tagName;
var frag = document.createDocumentFragment();
frag.appendChild(document.documentElement);
tmpele = document.appendChild(document.createElement(eletag));
tmpstyle = window.getComputedStyle(tmpele);
styles['color'] = elecolor===tmpstyle.color?undefined:elecolor;
tmpele.style.color = elecolor; // workaround for color propagation on other styles
for (var i in tmpstyle)
defstyle[i] = tmpstyle[i];
tmpele.remove();
document.appendChild(frag);
varstyle = element.style;
for (var i in varstyle) {
if ((((typeof varstyle[i])==="string"))&&(i!=="cssText")) {
if ((defstyle[i]!==elestyle[i]))
styles[i] = elestyle[i];
}
}
return styles;
}
Sadly there's a caviat as the browser still seemingly returns invalid styles in some cases. Often shifting the locations of elements.
To verify this you may run the following code, which takes into account parent/child inheritance, in an attempt to properly apply the current styles to the page:
function DOMDepth(element) {
var cur = element;
var deep = 0;
while(cur.parentNode)
deep++, cur = cur.parentNode;
return deep;
}
function getElementsByDepth() {
var all = document.getElementsByTagName('*');
var depth_map = {};
var deepest = 0;
for(var i in all) {
var depth = DOMDepth(all[i]);
deepest = depth>deepest?depth:deepest;
depth_map[depth] = depth_map[depth] || [];
depth_map[depth].push(all[i]);
}
depth_map['deepest'] = deepest;
return depth_map;
}
function inlineComputedStyles() {
var depth_map = getElementsByDepth();
for (var i = depth_map.deepest; i>0; i--) {
var elements = depth_map[i];
for (var j in elements) {
var styles = getRenderedStyles(elements[j]);
for (var k in styles) {
elements[j].style[k] = styles[k];
}
}
}
}
I have tested the preceeding and can confirm it does not suffer the color problems of the snippet in the question. Sadly I am uncertain as to why some elements still shift or if there's a way to fix it.
Special thanks to Kit Fung for pointing out the inheritance problem.
Related
How in JavaScript do you retrieve the styles that have been applied to an element, excluding the default user agent styles (so inline + stylesheet styles only).
Basically, all the user styles you can see in the Computed tab of your favorite developer tool:
No framework please, IE8+, Edge, Chrome and Firefox.
I am expecting the answer to be the result of getComputedStyle minus getDefaultComputedStyle, but in a cross browser way. Seeing that all developer tools are capable of doing it, there must be a solution :)
There is a read only property of document called 'styleSheets'.
var styleSheetList = document.styleSheets;
https://developer.mozilla.org/en-US/docs/Web/API/Document/styleSheets
By using this, you can reach all the styles which are applied by the author.
There is a similar question about this but not a duplicate, in here:
Is it possible to check if certain CSS properties are defined inside the style tag with Javascript?
You can get the applied style from an element, excluding the default user agent styles using the accepted answer of that question i just mentioned.
That answer didn't supply the element's own style attribute content, so i have improved the code a bit:
var proto = Element.prototype;
var slice = Function.call.bind(Array.prototype.slice);
var matches = Function.call.bind(proto.matchesSelector ||
proto.mozMatchesSelector || proto.webkitMatchesSelector ||
proto.msMatchesSelector || proto.oMatchesSelector);
// Returns true if a DOM Element matches a cssRule
var elementMatchCSSRule = function(element, cssRule) {
return matches(element, cssRule.selectorText);
};
// Returns true if a property is defined in a cssRule
var propertyInCSSRule = function(prop, cssRule) {
return prop in cssRule.style && cssRule.style[prop] !== "";
};
// Here we get the cssRules across all the stylesheets in one array
var cssRules = slice(document.styleSheets).reduce(function(rules, styleSheet) {
return rules.concat(slice(styleSheet.cssRules));
}, []);
var getAppliedCss = function(elm) {
// get only the css rules that matches that element
var elementRules = cssRules.filter(elementMatchCSSRule.bind(null, elm));
var rules =[];
if(elementRules.length) {
for(i = 0; i < elementRules.length; i++) {
var e = elementRules[i];
rules.push({
order:i,
text:e.cssText
})
}
}
if(elm.getAttribute('style')) {
rules.push({
order:elementRules.length,
text:elm.getAttribute('style')
})
}
return rules;
}
function showStyle(){
var styleSheetList = document.styleSheets;
// get a reference to an element, then...
var div1 = document.getElementById("div1");
var rules = getAppliedCss(div1);
var str = '';
for(i = 0; i < rules.length; i++) {
var r = rules[i];
str += '<br/>Style Order: ' + r.order + ' | Style Text: ' + r.text;
}
document.getElementById("p1").innerHTML = str;
}
#div1 {
float:left;
width:100px;
}
div {
text-align:center;
}
<div id="div1" style="font-size:14px;">
Lorem ipsum
</div>
<br/>
<br/>
Show me the style.
<p id="p1"><p>
All developer tools can cheat, because they have access to the default rules the browser they are built into applies.
I thought that the following approach might work.
Construct an element of exactly the same type (say, a div or p) as the one we are interested in.
Append this element somewhere on the page so that only default browser rules are applied. We can do so by putting it in an iframe.
If you are sure that you do not have rules targeting any p element, for example, then appending to the body may be more efficient.
Check the difference in styles and only report values that differ.
Clean up temporary element(s).
It seems to work reasonably well in practice. I have only tested this in Firefox and Chrome, but I think that it should work in other browsers too - except maybe for the fact that I used for...in and for...of, but one could easily rewrite that. Note that not just the properties you specify are reported, but also some properties that are influenced by the properties you do specify. For example, the border color matches the text color by design and is hence reported as different even when you only set color: white.
To summarize, I have taken the example you posted in one of your comments and added a getNonDefaultStyles function to it that I think does what you want. It can of course be modified to cache default styles of say, div elements and thus be more efficient in repeated calls (because modifying the DOM is expensive), but it shows the gist.
The below snippet shows how the version can be implemented that appends an element to the body. Due to limitations on StackOverflow, it is not possible to show the iframe version in a snippet. It is possible on JSFiddle. The below snippet can also be found in a Fiddle.
var textarea = document.getElementById("textarea"),
paragraph = document.getElementById("paragraph");
/**
* Computes applied styles, assuming no rules targeting a specific element.
*/
function getNonDefaultStyles(el) {
var styles = {},
computed = window.getComputedStyle(el),
notTargetedContainer = document.createElement('div'),
elVanilla = document.createElement(el.tagName);
document.body.appendChild(notTargetedContainer);
notTargetedContainer.appendChild(elVanilla);
var vanilla = window.getComputedStyle(elVanilla);
for (let key of computed) {
if (vanilla[key] !== computed[key]) {
styles[key] = computed[key];
}
}
document.body.removeChild(notTargetedContainer);
return styles;
}
var paragraphStyles = getNonDefaultStyles(paragraph);
for (let style in paragraphStyles) {
textarea.value += style + ": " + paragraphStyles[style] + "\n";
}
#paragraph {
background: red;
}
textarea {
width: 300px;
height: 400px;
}
<p id="paragraph" style="color: white">
I am a DIV
</p>
<p>
User styles:
</p>
<textarea id="textarea"></textarea>
Here's a function that gets all the CSS rules that have been applied to an element from either inline styles (HTML style attribute) or stylesheets on the page. It also grabs relevant keyframes for CSS animations and the :active, :hover, ::before, and ::after selectors.
function getAppliedCssData(el) {
// we create a unique id so we can generate unique ids for renaming animations
let uniqueId = "id" + Math.random().toString().slice(2) + Math.random().toString().slice(2);
let allRules = [...document.styleSheets].map(s => {
let rules = [];
try { rules.push(...s.cssRules) } catch(e) {} // we ignore cross-domain stylesheets with restrictive CORs headers
return rules;
}).flat();
let styleRules = allRules.filter(rule => rule.type === CSSRule.STYLE_RULE)
let fontFaceRules = allRules.filter(rule => rule.type === CSSRule.FONT_FACE_RULE);
let keyframesRules = allRules.filter(rule => rule.type === CSSRule.KEYFRAMES_RULE);
let matchingDefaultRules = styleRules.filter(rule => el.matches(rule.selectorText));
let nonMatchingRules = styleRules.filter(rule => !el.matches(rule.selectorText));
let matchingHoverRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/([^(])(:hover)\b/g, "$1")));
let matchingActiveRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/([^(])(:active)\b/g, "$1")));
let matchingBeforeRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/::before\b/g, "")));
let matchingAfterRules = nonMatchingRules.filter(rule => el.matches(rule.selectorText.replace(/ :/g, " *:").replace(/::after\b/g, "")));
let allMatchingStyleRules = [...matchingActiveRules, ...matchingDefaultRules, ...matchingHoverRules, ...matchingBeforeRules, ...matchingAfterRules];
let matchingAnimationNames = allMatchingStyleRules.map(rule => rule.style.animationName).filter(n => n.trim());
let matchingKeyframeRules = keyframesRules.filter(rule => matchingAnimationNames.includes(rule.name));
// make name changes before actually grabbing the style text of each type
allMatchingStyleRules.forEach(rule => rule.style.animationName = rule.style.animationName+uniqueId);
matchingKeyframeRules.forEach(rule => rule.name = rule.name+uniqueId);
let matchingDefaultStyles = matchingDefaultRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ") + (el.getAttribute('style') || ""); // important to add these last because inline styles are meant to override stylesheet styles (unless !important is used)
let matchingHoverStyles = matchingHoverRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingActiveStyles = matchingActiveRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingBeforeStyles = matchingBeforeRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingAfterStyles = matchingAfterRules.map(rule => rule.cssText).map(r => r.split(/[{}]/g)[1].trim()).join(" ");
let matchingKeyframeStyles = matchingKeyframeRules.map(rule => rule.cssText).join(" ");
// undo the rule name changes because this actually affects the whole document:
matchingKeyframeRules.forEach(rule => rule.name = rule.name.replace(uniqueId, ""));
allMatchingStyleRules.forEach(rule => rule.style.animationName = rule.style.animationName.replace(uniqueId, ""));
let data = {
uniqueId,
defaultStyles: matchingDefaultStyles,
hoverStyles: matchingHoverStyles,
activeStyles: matchingActiveStyles,
keyframeStyles: matchingKeyframeStyles,
beforeStyles: matchingBeforeStyles,
afterStyles: matchingAfterStyles,
}
return data;
}
The :focus, :focus-within and :visited selectors are not included, but could be easily added.
You can calculate the user-applied (non-default) styles by comparing them against a "default" HTML element of the same tag name, which is rendered in an isolated <iframe> so no styles from the document "leak" into the default element.
This solution is the same as #Just a student, but adds these improvements:
the <iframe> is a hidden HTML element so the user won't see it
for performance, default styles are cached and we wait to clean up the <iframe> until the end when we call removeSandbox
it accounts for inheritance (i.e. if you provide parentElement it will list a style even when the parent sets it and the element overrides it back to the default)
it accounts for situations where a default style's initial value and computed value don't match (for more info, see note [1] in this PR)
// usage:
element = document.querySelector('div');
styles = getUserComputedStyles(element);
styles = getUserComputedStyles(element, parentElement);
// call this method when done to cleanup:
removeSandbox();
function getUserComputedStyles(element, parentElement = null) {
var defaultStyle = getDefaultStyle(element.tagName);
var computedStyle = window.getComputedStyle(element);
var parentStyle =
parentElement ? window.getComputedStyle(parentElement) : null;
var styles = {};
[...computedStyle].forEach(function(name) {
// If the style does not match the default, or it does not match the
// parent's, set it. We don't know which styles are inherited from the
// parent and which aren't, so we have to always check both.
// This results in some extra default styles being returned, so if you
// want to avoid this and aren't concerned about omitting styles that
// the parent set but the `element` overrides back to the default,
// call `getUserComputedStyles` without a `parentElement`.
const computedStyleValue = computedStyle[name];
if (computedStyleValue !== defaultStyle[name] ||
(parentStyle && computedStyleValue !== parentStyle[name])) {
styles[name] = computedStyleValue;
}
});
return styles;
}
var removeDefaultStylesTimeoutId = null;
var sandbox = null;
var tagNameDefaultStyles = {};
function getDefaultStyle(tagName) {
if (tagNameDefaultStyles[tagName]) {
return tagNameDefaultStyles[tagName];
}
if (!sandbox) {
// Create a hidden sandbox <iframe> element within we can create
// default HTML elements and query their computed styles. Elements
// must be rendered in order to query their computed styles. The
// <iframe> won't render at all with `display: none`, so we have to
// use `visibility: hidden` with `position: fixed`.
sandbox = document.createElement('iframe');
sandbox.style.visibility = 'hidden';
sandbox.style.position = 'fixed';
document.body.appendChild(sandbox);
// Ensure that the iframe is rendered in standard mode
sandbox.contentWindow.document.write(
'<!DOCTYPE html><meta charset="UTF-8"><title>sandbox</title><body>');
}
var defaultElement = document.createElement(tagName);
sandbox.contentWindow.document.body.appendChild(defaultElement);
// Ensure that there is some content, so properties like margin are applied
defaultElement.textContent = '.';
var defaultComputedStyle =
sandbox.contentWindow.getComputedStyle(defaultElement);
var defaultStyle = {};
// Copy styles to an object, making sure that 'width' and 'height' are
// given the default value of 'auto', since their initial value is always
// 'auto' despite that the default computed value is sometimes an absolute
// length.
[...defaultComputedStyle].forEach(function(name) {
defaultStyle[name] = (name === 'width' || name === 'height')
? 'auto' : defaultComputedStyle.getPropertyValue(name);
});
sandbox.contentWindow.document.body.removeChild(defaultElement);
tagNameDefaultStyles[tagName] = defaultStyle;
return defaultStyle;
}
function removeSandbox() {
if (!sandbox) {
return;
}
document.body.removeChild(sandbox);
sandbox = null;
if (removeDefaultStylesTimeoutId) {
clearTimeout(removeDefaultStylesTimeoutId);
}
removeDefaultStylesTimeoutId = setTimeout(() => {
removeDefaultStylesTimeoutId = null;
tagNameDefaultStyles = {};
}, 20 * 1000);
}
Even with these improvements, for block elements, some default styles are still listed because their initial and computed values don't match, namely width, height, block-size, inset-block, transform-origin, and perspective-origin (see note in #4). This solution in dom-to-image-more (the getUserComputedStyle function) is able to trim away even more of these, although the calculation is slower.
I've used this function in the past...
function get_style(obj,nam) { //obj = HTML element, nam = style property
var val = "";
if(document.defaultView && document.defaultView.getComputedStyle) {
nam = nam.replace(/[A-Z]/g,function(str) { //convert name into hypenated
return "-"+str.toLowerCase();
});
val = document.defaultView.getComputedStyle(obj,"").getPropertyValue(nam); //get current style
}
else if(obj.currentStyle) {
nam = nam.replace(/\-(\w)/g,function(str,p1) { //convert name into camel case
return p1.toUpperCase();
});
val = obj.currentStyle[nam]; //get current style
}
return val;
}
It allows you to pass in the style property as either hypenated (background-color) or camel case (backgroundColor) and replaces it depending on the method it uses.
This cover older browsers as well, even old IE!
I'm trying to separate two types of inputs into their own jQuery wrapped sets as they need to be processed differently depending on whether the id contain '-add-new-' or not. I know I could do this using filter twice as follows:
var seriesTabInputs = $msSeriesTabs.find('input').filter(function() {
return $(this).attr('id').indexOf('-add-new-') == -1;
});
var addNewTabInputs = $msSeriesTabs.find('input').filter(function() {
return $(this).attr('id').indexOf('-add-new-') >= 0;
});
However filtering twice seems inefficient to me as I know it will require a second loop. Is there a way to avoid this?
Try like below:
var addNewTabInputs = $msSeriesTabs.find('input[id*="-add-new-"]');
var seriesTabInputs = $msSeriesTabs.find('input[id]:not([id*="-add-new-"])');
OR
var addNewTabInputs = $msSeriesTabs.find('input[id*="-add-new-"]');
var seriesTabInputs = $msSeriesTabs.find('input[id]').not(addNewTabInputs);
Just to offer an alternative to using specific selectors, you could iterate through the jQuery set and build the two collections as you go. I don't know that this would be any faster due to the different operations applied to the collections.
var $inputs = $msSeriesTabs.find('input');
var seriesTabInputs = [];
var addNewTabInputs = [];
for (var i = 0; i < $inputs.length ; i += 1)
{
var input = $($inputs[i]);
if ( $(input).attr('id').indexOf('-add-new-') >= 0 )
{ addNewTabInputs.push(input); }
else
{ seriesTabInputs.push(input); }
}
seriesTabInputs = $(seriesTabInputs);
addNewTabInputs = $(addNewTabInputs);
Avoiding filtering twice may not be so crucial unless you are dealing with an enormous amount of elements. Furthermore there is something to be said for the consistency of the code when you filter twice.
That being said there is a way to avoid filtering twice and it may even be instructional; below is some code that can be used to achieve this.
First, we create an empty wrapped set that can be added to, this is achieved by var seriesTabInputs = $(false); Please see this write-up for more information.
Then inside of the filter, we conditionally add to seriesTabInputs but note the way in which we do it: we continually re-assign with seriesTabInputs = seriesTabInputs.add($(this)); If instead you merely call seriesTabInputs.add($(this)) without assigning to seriesTabInput you will wind up with an empty array in the end. Please see the jQuery docs for .add() which gives a similar incorrect example and states that such usage "will not save the added elements, because the .add() method creates a new set".
var seriesTabInputs = $(false);
var addNewTabInputs = $msSeriesTabs.find('input').filter(function() {
if ($(this).attr('id').indexOf('-add-new') >= 0) {
return true;
}
else {
seriesTabInputs = seriesTabInputs.add($(this));
}
});
I have an array of sequential dom element nodes which may or may not have inline styles. I need to end up with an object or array with only keys and values common to all the nodes. Needs to work in IE8+, chrome and FF.
I can't even get one nodes styles into an array without a bunch of other stuff being included as well.
I've tried to use node[x].style but it seems to return a lot of extraneous stuff and other problems.
//g is node array
s=[];
for(k in g)
{
if(g.hasOwnProperty(k) && g[k]) s[k]=g[k];
}
console.log(s);
gives me ["font-weight", cssText: "font-weight: bold;", fontWeight: "bold"] which is close but I only want fontWeight: "bold" in the array. In any case, this only works in chrome.
The only idea I have at the moment that might work is using the cssText and splitting on semi-colons and splitting again on colons but that seems an ugly and slow way to do it especially as I then need to compare to a bunch of nodes and do the same to their styles.
So, I'm hoping someone can come up with a simple elegant solution to the problem posed in the first paragraph.
If you truly want ONLY styles that are specified inline in the HTML for the object, then you will have to deal with text of the style attribute as you surmised.
The .style property will show you more styles than were specified on the object itself (showing you default values for some styles) so you can't use that.
Here's a function that takes a collection of DOM nodes and returns a map of common styles (styles that are specified inline and are the same property and value on every object):
function getCommonStyles(elems) {
var styles, styleItem, styleCollection = {}, commonStyles = {}, prop, val;
for (var i = 0; i < elems.length; i++) {
var styleText = elems[i].getAttribute("style");
if (styleText) {
// split into an array of individual style strings
styles = styleText.split(/\s*;\s*/);
for (var j = 0; j < styles.length; j++) {
// split into the two pieces of a style
styleItem = styles[j].split(/\s*:\s*/);
// only if we found exactly two pieces should we count this one
if (styleItem.length === 2) {
prop = styleItem[0];
val = styleItem[1];
// if we already have this style property in our collection
if (styleCollection[prop]) {
// if same value, then increment the cntr
if (styleCollection[prop].value === val) {
++styleCollection[prop].cntr;
}
} else {
// style tag didn't exist so add it
var newTag = {};
newTag.value = val;
newTag.cntr = 1;
styleCollection[prop] = newTag;
}
}
}
}
}
// now go through the styleCollection and put the ones in the common styles
// that were present for every element
for (var prop in styleCollection) {
if (styleCollection[prop].cntr === elems.length) {
commonStyles[prop] = styleCollection[prop].value;
}
}
return(commonStyles);
}
Working demo: http://jsfiddle.net/jfriend00/JW7CZ/
I'm still in the process of learning JavaScript. and I would like to complete the task using only JavaScript and no Jquery.
I have multiple div/images that I’m trying to manipulate using the z-index, and a button that randomize the images to come to the front.
I got the random image array to work but as you could see in image[1]…setting each changeZ index will be laborious. So I’m embarking on changing the class’s (as seen in image[0] so I could add current to the new image and send current to the background on the next go around and then removing the class attribute. I have got the element to work separate but having trouble putting it together in a array.
function changeZIndex(i,id) {
document.getElementById(id).style.zIndex=i;};
function changeClassZIndex(i,tagName){
document.getElementsByTagName("*").style.zIndex=i;};
function getImage(){
var whichImage=Math.floor(Math.random()*3);
var image=new Array()
var currentPhoto = div.current
image[0]=function() {
changeZIndex(5,"scene1");
changeClassZIndex(-5,"current");
currentPhoto.removeClass('current')
document.getElementById("scene1").className += "current"; };
image[1]=function() {
changeZIndex(5,"scene2");
changeZIndex(-5,"scene1");
changeZIndex(-5,"scene3");
changeZIndex(-5,"scene");
};
image[2]=function() {
changeZIndex(5,"scene3");
changeZIndex(-5,"scene");
changeZIndex(-5,"scene2");
changeZIndex(-5,"scene1");
};
image[whichImage].apply(undefined);};
It's because document.getElementsByTagName() returns an array of elements, which you can't do operations like that on. Instead, you need to enumerate through them and do the operations individually.
Here's a working jsfiddle which shows exactly how to do it: jsfiddle
As a side note: if there's one thing a lot of web programming will teach you, its this:
Dont ever, ever, rule out jQuery as an option.
JQuery is your best friend, and the use of it in this situation would cut down your lines of code by well over half.
Firstly, I believe your problem is probably in changeClassZIndex(i,tagName)
which should probably look something like this:
if (document.getElementsByClassName == undefined) {
document.getElementsByClassName = function(className)
{
var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
var allElements = document.getElementsByTagName("*");
var results = [];
var element;
for (var i = 0; (element = allElements[i]) != null; i++) {
var elementClass = element.className;
if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass))
results.push(element);
}
return results;
}
}
function changeClassZIndex(z,className) {
var e = document.getElementsByClassName(className);
for(var i = 0; i < e.length; i++) {
e[i].style.zIndex = z;
}
};
I am defining the getElementsByClassName function if it does not exist because some browsers may not support it.
I may suggest taking a different approach to your problem however:
var images = new Array("scene1", "scene2", "scene3");
var currentPhoto = div.current
var whichImage = Math.floor(Math.random()*images.length);
// change all images to the background
for(var i = 0; i < images.length; i++)
{
changeZIndex(-5, images[i]);
}
// change the one you want to the top
changeZIndex(5, images[whichImage]);
That way you do not have to write functions for each image, and adding images is as easy as adding to the array.
I mean an array of them. That is a chain from top HTML to destination element including the element itself.
for example for element <A> it would be:
[HTML, BODY, DIV, DIV, P, SPAN, A]
A little shorter (and safer, since target may not be found):
var a = document.getElementById("target");
var els = [];
while (a) {
els.unshift(a);
a = a.parentNode;
}
You can try something like:
var nodes = [];
var element = document.getElementById('yourelement');
nodes.push(element);
while(element.parentNode) {
nodes.unshift(element.parentNode);
element = element.parentNode;
}
I like this method:
[...(function*(e){do { yield e; } while (e = e.parentNode);})($0)]
... where $0 is your element.
An upside of this method is that it can be used as a value in expressions.
To get an array without the target element:
[...(function*(e){while (e = e.parentNode) { yield e; }})($0)]
You can walk the chain of element.parentNodes until you reach an falsey value, appending to an array as you go:
const getParents = el => {
for (var parents = []; el; el = el.parentNode) {
parents.push(el);
}
return parents;
};
const el = document.querySelector("b");
console.log(getParents(el).reverse().map(e => e.nodeName));
<div><p><span><b>Foo</b></span></div>
Note that reversing is done in the caller because it's not essential to the lineage algorithm. Mapping to e.nodeName is purely for presentation and also non-essential.
Note that this approach means you'll wind up with the document element as the last element in the chain. If you don't want that, you can add && el !== document to the loop stopping condition.
The overall time complexity of the code above is linear and reverse() is in-place, so it doesn't require an extra allocation. unshift in a loop, as some of the other answers recommend, is quadratic and may harm scalability on uncommonly-deep DOM trees in exchange for a negligible gain in elegance.
Another alternative (based on this):
for(var e = document.getElementById("target"),p = [];e && e !== document;e = e.parentNode)
p.push(e);
I believe this will likely be the most performant in the long run in the most scenarios if you are making frequent usage of this function. The reason for why t will be more performant is because it initially checks to see what kind of depths of ancestry it might encounter. Also, instead of creating a new array every time you call it, this function will instead efficiently reuse the same array, and slice it which is very optimized in some browsers. However, since there is no really efficient way I know of to check the maximum depth, I am left with a less efficient query-selector check.
// !IMPORTANT! When moving this coding snippet over to your production code,
// do not run the following depthtest more than once, it is not very performant
var kCurSelector="*|*", curDepth=3;
while (document.body.querySelector(kCurSelector += '>*|*')) curDepth++;
curDepth = Math.pow(2, Math.ceil(Math.log2(startDepth))),
var parentsTMP = new Array(curDepth);
function getAllParentNodes(Ele){
var curPos = curDepth;
if (Ele instanceof Node)
while (Ele !== document){
if (curPos === 0){
curPos += curDepth;
parentsTMP.length <<= 1;
parentsTMP.copyWithin(curDepth, 0, curDepth);
curDepth <<= 1;
}
parentsTMP[--curPos] = Ele;
Ele = Ele.parentNode;
}
return retArray.slice(curPos)
}
The browser compatibility for the above function is that it will work in Edge, but not in IE. If you want IE support, then you will need a Array.prototype.copyWithin polyfill.
get all parent nodes of child in javascript array
let selectedTxtElement = document.getElementById("target");
let els = [];
while (selectedTxtElement) {
els.unshift(selectedTxtElement);
selectedTxtElement = selectedTxtElement.parentNode;
}
know more