How get all computed css properties of element and its children in Javascript
Asked Answered
A

2

17

I'm trying to set up a service in python using pdfKit to create a pdf file from html files.

So basically I will send my element as string and expect the server to return a pdf version of it, but to be an accurate representation I also need to send a css file of the element.

How can I do this? Generate JSON / object with only the relevant style properties and selectors of an element and all its children. Respecting hierarchy and no duplicates. There are similar questions but they are outdated and tend to not consider children elements.

I was thinking maybe there is a way to create a new DOM from this element and then get the root css?

Audet answered 4/12, 2018 at 12:29 Comment(12)
Have you looked into window.getComputedStyle developer.mozilla.org/en-US/docs/Web/API/Window/… ? It returns an object with all CSS properties applied to an element.Edwardedwardian
@Edwardedwardian Sure, but how do I get all children's style while respecting the hierarchyAudet
@Audet the computed style does respect the hierarchyLogician
#755107Circumfluent
@Logician But does it gets all children styles too?Audet
See the top answer Original (ES 2017) here: #19784564 but AFAIK it doesn't apply children styles. You'd have to run the function again for each child I thinkEdwardedwardian
@Audet no, you have to ask for the style of each individual element, but those styles take into account those inherited from the parent.Logician
@Logician exactly the point of my question, how to traverse all children, output a single css properties object while respecting the hierarchy because you will eventually overwrite properties and the overwriting needs to follow the tree orderAudet
@Audet right - you can't do that. You need to collect the computed properties of each element from the bottom up, and keep that set of properties associated with each individual element.Logician
Create a loop, and the stop conditional will be an "id" you define in last child you want to get css property.Jellybean
Maybe not even a loop, but something recursive. Might be a goo use case for itFontanel
What about media queries and such? Why not just include the CSS files that are referenced in the HTML?Instability
O
6

Here is something I came up with, basically pass the element you want to extract the styles of and ones of its children, and it will return you the stylesheet as a string. Open your console before running the snippet and you will see the output from the console.log.

Because I wanted to support the extraction of every element even those without a selector, I had to replace each element id by a unique uuid specifically generated for them in order to facilitate the styling of your output. The problem with this approach is in case you are using ids for styling or for user interaction, you are going to loose such functionality on concerned elements after calling extractCSS.

However, it is pretty trivial to use the oldId I'm passing to change back once your pdfKit process finished the generation. Simply call swapBackIds passing the elements returned by the function. You can see the difference of behavior if you uncomment the call in my snippet: the #root pink background would disappear because the styling targets an element id.

All in all, you need to:

  1. Call extractCSS with the element you want to extract
  2. Generate your pdf using res.stylesheet
  3. Call swapBackIds with res.elements

// Generate an unique id for your element
// From https://mcmap.net/q/22175/-how-do-i-create-a-guid-uuid
function uuidv4 () {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

// Flatten an array
// https://mcmap.net/q/36813/-merge-flatten-an-array-of-arrays
function flatten(arr) {
  return arr.reduce(function (flat, toFlatten) {
    return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
  }, []);
}

function recursiveExtract (element) {
  var id = uuidv4()
  var oldId = element.id

  var computed = window.getComputedStyle(element)
  var style = computed.cssText

  // Now that we get the style, we can swap the id
  element.setAttribute('id', id)

  // The children are not a real array but a NodeList, we need to convert them
  // so we can map over them easily
  var children = Array.prototype.slice.call(element.children)
  return [{ id: id, style: style, oldId: oldId }].concat(children.map(recursiveExtract))
}

function extractCSS (element) {
  if (!element) { return { elements: [], stylesheet: '' } }

  var raw = recursiveExtract(element)
  var flat = flatten(raw)
  
  return {
    elements: flat,
    stylesheet: flat.reduce(function (acc, cur) {
      var style = '#' + cur.id + ' {\n' + cur.style + '\n}\n\n'
      return acc + style
    }, '')
  }
}

var pdfElement = document.querySelector('#root')
var res = extractCSS(pdfElement)

console.log(res.stylesheet)

function swapBackIds (elements) {
  elements.forEach(function (e) {
    var element = document.getElementById(e.id)
    element.setAttribute('id', e.oldId)
  })
}

swapBackIds(res.elements)
#root {
  background-color: pink;
}

.style-from-class {
  background-color: red;
  width: 200px;
  height: 200px;
}

.style-from-id {
  background-color: green;
  width: 100px;
  height: 100px;
}
<div id="root">
  <span>normal</span>
  <span style="background: blue">inline</span>
  <div class="style-from-class">
    style-class
  </div>
  <div class="style-from-id">
    style-id
    <div style="font-size: 10px">a very nested</div>
    <div style="font-size: 12px; color: white">and another</div>
  </div>
</div>

<div id="ignored-sibling">
</div>
Oneman answered 11/12, 2018 at 12:47 Comment(5)
@Audet any concern with this method?Oneman
@AshishRanjan Don't do these kinds of edits, they are unecessary.Oneman
they aren't unnecessary IMO, it was to improve the quality of code. #1 - inconsistent use of semicolons, #2 - var -> const, let. Your answer, your rules :)Aetna
The 5 semis are only here for the part of the code that was taken from elsewhere, you could have removed those instead but yes I'm surprised I wasn't using const at the time alreadyOneman
@Oneman not sure, but the snippet is returning empty ID styles?Chellman
D
2

	
let para = document.querySelector('p');
let compStyles = window.getComputedStyle(para);
para.textContent = 'My computed font-size is ' + compStyles.getPropertyValue('font-size') + ',\nand my computed background is ' +  compStyles.getPropertyValue('background') + '.';
p {
  width: 400px;
  margin: 0 auto;
  padding: 20px;
  font: 2rem/2 sans-serif;
  text-align: center;
  background: purple;
  color: white;
}
<p>Hello</p>

you can use getComputedStyle method to get computed value of style property

Debt answered 13/12, 2018 at 12:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.