Retrieve CSS styles from an element and all of its descendants in a recursive manner

I am looking to achieve the following functionality:

getRecursiveCSS(document.getElementById('#menubar'))

When called, this function should return a string of CSS for the main element and all its child elements.

I have attempted the following implementation (which is not functioning as expected):

function fullPath(el){
  var names = [];
  while (el.parentNode){
    if (el.id){
      names.unshift('#'+el.id);
      break;
    }else{
      if (el==el.ownerDocument.documentElement) names.unshift(el.tagName);
      else{
        for (var c=1,e=el;e.previousElementSibling;e=e.previousElementSibling,c++);
        names.unshift(el.tagName+":nth-child("+c+")");
      }
      el=el.parentNode;
    }
  }
  return names.join(" > ");
}

function styleRecursive(elements, css) {
    elements = Object.prototype.toString.call(elements) === '[object Array]' ? elements: [elements];
    if (elements.length == 0 || typeof elements[0] == 'undefined')
        return css;

    if (typeof elements[0].querySelector == 'undefined')
        return css

    if (typeof css == 'undefined')
        css = fullPath(elements[0]) + '{' + getComputedStyle(elements[0]).cssText + '}';
    else
        css += fullPath(elements[0]) + '{' + getComputedStyle(elements[0]).cssText + '}';

    _elements = [];

    for (var i = 0; i < elements.length; i++) {
        for (var ii = 0; ii < elements[i].childNodes.length; ii++)
            _elements.push(elements[i].childNodes[ii]);
    }

    return styleRecursive(_elements, css);
};

Answer №1

I have devised a solution that may spark ideas on how to enhance your code. To test this code, I created an element with children at varying depths and utilized a recursive approach to traverse all the children based on their depth to extract their CSS properties. Subsequently, all discovered CSS properties along with the element names are stored in an object structure (resembling JSON) for future reference.

Important Notes:
1) This code is not entirely foolproof; additional conditions and checks must be incorporated to ensure seamless functionality across various scenarios.
2) The code has been tested in Google Chrome browser.
3) Currently supports classes only for locating elements and their children, but can be easily extended to include IDs and tags as well.

Output:

one: {
  display: "block",
  position: "relative"
}
two: {
  display: "inline-block",
  font-family: "Montserrat"
}
three_1: {
  display: "table",
  position: "absolute",
  left: "0px"
}
four_1: {
  display: "table-cell",
  position: "relative"
}
three_2: {
  display: "table",
  position: "absolute",
  right: "0px"
}
four_2: {
  display: "table-cell",
  position: "relative"
}

HTML(Sample):

<div class="one">
    <div class="two">
        <div class="three_1">
            <div class="four_1"></div>
        </div>
        <div class="three_2">
            <div class="four_2"></div>
        </div>
    </div>
</div>

CSS(Sample):

.one {display:block;position:relative;}
.two {display:inline-block;font-family:'Montserrat';}
.three_1 {display:table;position:absolute;left:0;}
.three_2 {display:table;position:absolute;right:0;}
.four_1 {display:table-cell;position:relative;}
.four_2 {display:table-cell;position:relative;}

JS:

function convertObjlike(css) {
    var obj = {};
    if (!css) return obj;
    css = css.split("; ");
    for (var i in css) {
        var prop = css[i].split(": ");
        obj[prop[0].toLowerCase()] = prop[1];
    }
    return obj;
}

function getCss(selector) {
    var stylesheets = document.styleSheets, result = {};
    for (var i in stylesheets) {
        var rules = stylesheets[i].rules || stylesheets[i].cssRules;
        for (var r in rules) {
            if (selector === rules[r].selectorText) {
                result = convertObjlike(rules[r].style.cssText);
            }
        }
    }
    return result;
}

var styleObject = {};
function processStyles(element){
  styleObject[element.className] = (getCss('.'+element.className));
  var childNodes = element.children;

  for (var i = 0; i < childNodes.length; i++) {
    processStyles(childNodes[i])
  }
}

processStyles( document.querySelector('.one') );
console.log(styleObject);

Jsfiddle Link

Answer №2

Note: Please be aware that the solution provided here generates an HTML copy and not a CSS 'file'.

Below is my approach. It retrieves all computed styles of the element and saves them in the style attribute. The class attribute is also removed since it is generally used for setting those styles only (you can keep the removeAttribute call if needed). Additionally, it recursively computes the resulting HTML by iterating through its children.

The resulting HTML may appear large because many styles are default values, and it does not consider optimized inheritance of styles. Therefore, each child ends up receiving all its styles again. Importing/registering font faces separately might be necessary.

Hover effects and media queries are not copied over as getComputedStyle captures the current state of the node only. Relative units like vw, vh, %, etc., are converted to their equivalent absolute values. Similarly, variables are not retained; instead, their values are utilized.

function getElemHtml(elem) {
    let style = [], computed = window.getComputedStyle(elem)
    for (const attr of computed) style.push(`${attr}:${computed[attr]}`)
    let clone = elem.cloneNode()
    clone.setAttribute('style', style.join(";"))
    clone.removeAttribute('class')
    let childrenHTML = ''
    for (const child of elem.childNodes) childrenHTML += child.nodeType === Element.ELEMENT_NODE ? getElemHtml(child) : child.nodeType === Element.TEXT_NODE ? child.nodeValue : ''
    clone.innerHTML = childrenHTML
    return clone.outerHTML
}

const elem = document.querySelector("p")
const elemHtml = getElemHtml(elem)
document.querySelector("code").innerText = elemHtml
p {
  width: 400px;
  margin: 0 auto;
  padding: 20px;
  font: 2rem/2 sans-serif;
  text-align: center;
  background: purple;
  color: white;
}

pre, code {
  width: 90vw;
  white-space: pre-wrap;
}
<p>Hello</p>

<pre><code></code></pre>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The array is not receiving the objects as intended

I'm facing an issue with my event listener that is supposed to push new messages to an array, but it seems to only be pushing the strings. Here is the code snippet: async function consume() { try { let result = []; const connection = await a ...

In angular.js, repeating elements must be unique and duplicates are not permitted

My view controller includes this code snippet for fetching data from an API server: $scope.recent_news_posts = localStorageService.get('recent_news_posts') || []; $http({method: 'GET', url: 'http://myapi.com/posts'} ...

Is there a way to ensure that the code only runs if the promise has been successfully fulfilled

In my NodeJS project, I am leveraging promises to ensure that the server stops running if certain functions do not meet the required conditions. Currently, the server halts as intended, but I also want to include a console log message when the promises are ...

Stop :hovering on the link for iPad

My webpage contains sublinks with CSS properties as follows: #leftNav .searchBySub {...} #leftNav a.searchBySub:hover {...} #leftNav .searchBySubClicked {...} However, I have observed that on the iPad, the :hover styles are being applied. Is there a wa ...

A seamless border encircling an inline span accompanied by consistent line spacing

I am trying to achieve a single contiguous outline around a <span> element nested within a <p> and <div>. I came across a solution here: CSS/Javascript: How to draw minimal border around an inline element? which works well except when the ...

Insert the GET object into an empty object within the data and then send it back

Here is the Vue application I am working with: new Vue({ name: 'o365-edit-modal-wrapper', el: '#o365-modal-edit-wrapper', data: function() { return { list: {}, } }, created() { thi ...

Accessing specific values from a JSON object in JavaScript

I'm fairly new to JSON and have a JSON String that I'd like to access and print to a DIV. I've managed to get it working using the following code snippet: document.getElementById("product_content").innerHTML=javascript_array[5].name; The s ...

How come modifying the css of one component impacts all the other components?

Issue: I am struggling to make CSS changes only affect the specific component I want, without impacting other elements on the page. My goal is to style my dropdown menu without affecting other text input fields: https://i.sstatic.net/Caidv.png Background ...

The recursive JavaScript function is not returning as expected when using defer

Having an issue with a recursive function. It appears to loop through effectively when the data return is null, but it fails to return the promise correctly when the data is not null after completing the recursive task. It seems like the promise gets los ...

Display the console.log output of NodeJs on an HTML webpage

I've set up a server named app.js and have multiple clients connecting to it. Currently, I am displaying the server's messages to the client in an HTML page called index.html. However, I also want to display the messages from the clients to the s ...

node.js is reporting that it cannot locate some of the modules

Attempting to execute a file using node run index.js within the flashloan test folder results in an error that keeps occurring... node:internal/modules/cjs/loader:1042 throw err; ^ Error: Cannot find module '/home/schette/Documents/flashloan test ...

Unable to successfully implement the CSS not selector

My webpage's code was created through the use of Wordpress. <div class="header-main"> <h1 class="site-title"><a href="http://wp-themes.com/" rel="home">Theme Preview</a></h1> <div class="searc ...

jquery ajax not ready - responsetext empty - status code 0 - statustext error occurred

I encountered an error message stating: jquery ajax readystate 0 responsetext status 0 statustext error when providing this URL: url(http://www.tutorialspoint.com/prototype/prototype_ajax_response.htm), however, the same code works perfectly fine with thi ...

Using an external script to modify or call a Vue.js method

My Vue app is constructed using Webpack and includes a few basic computed properties, such as calculating the sum amount from input values. However, I now require the capability to replace the summation function with one stored in a separate file that is n ...

The gaps separating rows

I'm struggling with getting my divs to look like a table, especially when it comes to highlighting a selected row. I've tried adjusting padding and margins without success. Does anyone have any suggestions on how I can achieve this effect? .ta ...

Troubleshooting Blade Template HTML Coloration Problem in Atom

When I include Laravel code inside the HTML element itself, it causes all subsequent HTML elements to appear discolored. For example, <body @php language_attributes() @endphp> will cause </body> to be discolored. However, if I use plain PHP i ...

Can the manifest.json file be edited dynamically?

I'm in the process of developing a React PWA and I'm exploring the possibility of dynamically adding icon URLs to the manifest.json file. My aim is to display a generic app icon until the user logs in. Once logged in, I plan to fetch a new icon f ...

Is there a way to reverse the confirmation of a sweet alert?

Hey there, I'm currently using Sweet Alert to remove a product from my website. I want to implement it with two options - 'ok' and 'cancel'. However, I'm facing an issue where clicking anywhere on the page removes the product ...

(Angular) Employing *ngFor directive for populating a table

I am in need of a table structure that resembles the following: https://i.sstatic.net/rB2C6.png To achieve this, I am utilizing a basic Bootstrap table along with Angular's *ngFor. Each cell in the table should display a different name from an array ...

Send data containing special characters through a GET request in PHP

I am looking for a way to send any text as a GET parameter to a PHP script. Currently, I am simply appending the text like this: action.php?text=Hello+my+name+is+bob This URL is generated using JavaScript and then used in an AJAX request. In action.php, ...