简体   繁体   中英

How to convert external/internal css styles into inline style-attribute using client-side javascript

For example, if I have CSS that included within document:

div {
  color: red;
}
div.test {
  border: 1px solid blue;
}

and html tag within document body:

<div id='demo'>
  <div class='test'>123</div>
  <div>456</div>
</div>

I want to convert everything within #demo as a string containing tag with all styles it was using, such as:

var parent = $('#demo');
var result = convertExternalInternalStylesToInline(parent);

// the desired result:
result = '<div style="color: red; border: 1px solid blue;">123</div>' +
         '<div style="color: red">456</div>'

What I need is the content of convertExternalInternalStylesToInline function, that automatically fetch all descendant elements, apply the calculated/used styles then add css styles to those elements, then return it all as html string.

Can it be done using only client side javascript ? If yes, how?

(I need to know how to get all calculated/used styles for a tag)

minimal example:

function convertExternalInternalStylesToInline(parent) {
  var parent = $(parent).clone(); // clone it before modify, is this deep clone?
  parent.find('*').each(function(idx,el){ // fetch all children
     var el = $(el); // convert to jquery object
     // get all applied styles on this element
     // el.?                        // --> don't know how
     // apply css for each styles
     el.css( css_prop, css_style );
  });
  // return as string, maybe:
  return parent.html();
}

EDIT: Thanks to Winchestro, I looked up the window.getMatchedCSSRules function, which is actually only in Webkit, and there is a discussion that it should be deprecated .

What you should be using is actually the window.getComputedStyle() Read the docs at MDN .

Another very useful resource you may look into is the CSSUtilities set of libraries .


What you need are two separate things, a library to parse out your CSS Object Modal (CSSOM), and apply the relevant CSS to your DOM elements.

I know of a good nodejs library which does this called Juice .

There are is a library I found which would probably work on the front-end, called inlineresources , which has a browserified build

On second thoughts, I think you may be able to use Juice along with browserify for this... But you'll have to evaluate that possibility manually...

assuming you have the HTML in an array (lines: string[]):

I'm not sure why I spent so much time on this

let styleLines = this.getLinesInsideStyleTag(lines)
let htmlLinesSeparatedByTag = this.getLinesNotInsideStyleTag(lines)
let mapOfCSSrules = this.getMapOfCSSRuleNamesAndPropertiesFromCSSLines(styleLines)
let linesWithInlineRulesApplied = this.applyInlineStylesToLines(htmlLinesSeparatedByTag, mapOfCSSrules)

let finalString = ""
for(let v = 0; v < linesWithInlineRulesApplied.length; v++ ) {
    finalString = finalString + linesWithInlineRulesApplied[v]   
}

console.log(finalString)

    getLinesInsideStyleTag(lines: any[]) {
        let styleLines: any[] = []
        let foundStylesStartTag = false
        let foundStylesEndTag = false
        for(let i = 0; i < lines.length; i++ ) {
            if(lines[i].indexOf("<style ") != -1) {
                foundStylesStartTag=true
            } else if(lines[i].indexOf("</style>") != -1) {
                foundStylesEndTag = true
            }

            if(foundStylesStartTag == true && foundStylesEndTag == false) {
                if(lines[i].indexOf("<style") == -1 && lines[i].indexOf("</style") == -1) {
                    styleLines.push(lines[i])
                }
            } 
            // console.log("lines[i] after: ")
            // console.log(lines[i])
        }
        return styleLines
    }



    getLinesNotInsideStyleTag(lines: any[]) {
                                    
        let foundStylesStartTag = false
        let foundStylesEndTag = false

        let linesToKeep: any[] = []
        for(let i = 0; i < lines.length; i++ ) {
            if(lines[i].indexOf("<style ") != -1) {
                foundStylesStartTag=true
            } else if(lines[i].indexOf("</style>") != -1) {
                foundStylesEndTag = true
            }

            if(foundStylesStartTag == false && foundStylesEndTag == false) {
                linesToKeep.push(lines[i])
            } else if(foundStylesStartTag == true && foundStylesEndTag == true) {
                if(lines[i].indexOf("<style") == -1 && lines[i].indexOf("</style") == -1) {
                    linesToKeep.push(lines[i])
                }
            }
        }

        let actualLinesToKeep: any[] = []
        for(let i = 0; i < linesToKeep.length; i++ ){ 
            let thisLineSplitOnOpeningTag = linesToKeep[i].split("<")
            let pushFullLine = false
            let modifiedLine = ""
            for(let y = 0; y < thisLineSplitOnOpeningTag.length; y++) {
                if(thisLineSplitOnOpeningTag[0] !== "") {    
                    pushFullLine = true
                } else {
                    if(thisLineSplitOnOpeningTag.length > 2) {
                        //then the line contains nested tags (oof)
                        if(thisLineSplitOnOpeningTag[y].length > 0) {
                            if( y != thisLineSplitOnOpeningTag.length - 1) {
                                modifiedLine = modifiedLine + "<" + thisLineSplitOnOpeningTag[y]+"%*#"
                            } else {
                                modifiedLine = modifiedLine + "<" + thisLineSplitOnOpeningTag[y]
                            }
                        }
                    } else {
                        pushFullLine = true
                    }
                }
            }

            if(pushFullLine == true) {
                // console.log("3pushing full line because it doesn't have nested tags: "+linesToKeep[i])
                actualLinesToKeep.push(linesToKeep[i])
            } else {
                actualLinesToKeep.push(modifiedLine)
            }
        }
        // console.log("actualLinesToKeep: ")
        // console.log(actualLinesToKeep)
        return actualLinesToKeep
    }



    //e.g. you pass it 
    // myRule {
    //    color: blue;
    //    text-align: left;
    // }
    // you get back: a dictionary / map where "myRule" is the key, and "color: blue;" and "text-align: left;" are the values for that key.
    getMapOfCSSRuleNamesAndPropertiesFromCSSLines(styleLines: any[]) {
        // console.log("styleLines: ")
        // console.log(styleLines)

                        //rule, properties
        let CSSrules: Map<string,any[]> = new Map();

        let rulesSplitOnClosingBracket = styleLines.toString().split("}")

        for(let i = 0; i < rulesSplitOnClosingBracket.length; i++) {
            let indexOfOpeningBracket = rulesSplitOnClosingBracket[i].indexOf("{")
            let ruleName = rulesSplitOnClosingBracket[i].substring(0,indexOfOpeningBracket).trim()
            ruleName = this.replaceAll(ruleName,",","").toLowerCase()
            if(ruleName[0] === ".") {
                ruleName = ruleName.substring(1)
            }
            //replace dots with a space
            ruleName = ruleName.replace(/\./g,' ')
            let propertiesOfThisRule = rulesSplitOnClosingBracket[i].substring(indexOfOpeningBracket+1).split(",")
            let propertiesToKeep: any[] = []
            for(let j = 0; j < propertiesOfThisRule.length; j++) {
                propertiesOfThisRule[j] = propertiesOfThisRule[j].trim()
                if(propertiesOfThisRule[j] !== undefined && propertiesOfThisRule[j].length > 0) {
                    propertiesToKeep.push(propertiesOfThisRule[j])
                }
            }

            if(ruleName !== undefined && ruleName.length > 0 && propertiesToKeep !== undefined && propertiesToKeep.length > 0) {
                CSSrules.set(ruleName, propertiesToKeep)
            }
        }
        return CSSrules
    }


applyInlineStylesToLines(htmlLinesSeparatedByTag: any[], mapOfCSSrules: Map<string,any[]>) {
        let linesWithInlineRulesApplied: any[] = []
        let ruleNames = Array.from(mapOfCSSrules.keys())

        for(let r = 0; r < htmlLinesSeparatedByTag.length; r++) {
            let lineSplitOnContinuationCharacter = htmlLinesSeparatedByTag[r].split("%*#")
            let partsOfLineThatContainClosingTags: any[] = []
            let partsOfLineThatDoNotContainClosingTags: any[] = []
            for(let d = 0; d < lineSplitOnContinuationCharacter.length; d++) {
                if(lineSplitOnContinuationCharacter[d].indexOf("</") != -1) {
                    partsOfLineThatContainClosingTags.push({orderNumber: d, line: lineSplitOnContinuationCharacter[d]})
                } else if(lineSplitOnContinuationCharacter[d].indexOf("</") == -1) {
                    partsOfLineThatDoNotContainClosingTags.push({orderNumber: d, line: lineSplitOnContinuationCharacter[d]})
                }
            }

            let orderNumbers1: any[number] = partsOfLineThatDoNotContainClosingTags.map(val => val.orderNumber)
            let orderNumbers2: any[number] = partsOfLineThatContainClosingTags.map(val => val.orderNumber)
            let maxOrderNumberFor1 = Math.max.apply(Math,orderNumbers1)
            let maxOrderNumberFor2 = Math.max.apply(Math,orderNumbers2)

            let maxOrderNumber: number;
            if(maxOrderNumberFor1 > maxOrderNumberFor2) {
                maxOrderNumber = maxOrderNumberFor1
            } else {
                maxOrderNumber = maxOrderNumberFor2
            }

            let thisActualLineWithStylesApplied = ""
            for(let u = 0; u < maxOrderNumber+1; u++) {
                let partOfLineWithoutClosingTag = partsOfLineThatDoNotContainClosingTags.filter(val => val.orderNumber == u)[0]?.line
                let partOfLineWithClosingTag =  partsOfLineThatContainClosingTags.filter(val => val.orderNumber == u)[0]?.line

                if ( partOfLineWithoutClosingTag !== undefined ) {
                    let idxOfFirstSpace = partOfLineWithoutClosingTag.indexOf(" ")
                    for(let s = 0; s < ruleNames.length; s++) {
                        let applyThisRuleToThisLine = true
                        let textToCheckFor: any[] = ruleNames[s].split(" ")
                        for(let t = 0; t < textToCheckFor.length; t++) {
                            if(partOfLineWithoutClosingTag.indexOf(textToCheckFor[t]) == -1) {
                                applyThisRuleToThisLine = false
                            }
                        }

                        if(applyThisRuleToThisLine) {
                            let lineAfterApplyingStyle = partOfLineWithoutClosingTag.substring(0, idxOfFirstSpace) +" style=\""
                            for(let u = 0; u < mapOfCSSrules.get(ruleNames[s]).length; u++) {
                                let thisPropertyToApply = mapOfCSSrules.get(ruleNames[s])[u]
                                lineAfterApplyingStyle=lineAfterApplyingStyle+thisPropertyToApply
                            }
                            lineAfterApplyingStyle = lineAfterApplyingStyle +"\""
                            partOfLineWithoutClosingTag = lineAfterApplyingStyle + partOfLineWithoutClosingTag 

                            let lastIndexOfLessThan = partOfLineWithoutClosingTag.lastIndexOf("<")
                            let lastIndexOfGreaterThan = partOfLineWithoutClosingTag.lastIndexOf(">")
                            partOfLineWithoutClosingTag = partOfLineWithoutClosingTag.substring(0,lastIndexOfLessThan) + partOfLineWithoutClosingTag.substring(lastIndexOfGreaterThan)
                        }
                    } 
                    thisActualLineWithStylesApplied = thisActualLineWithStylesApplied + partOfLineWithoutClosingTag
                }
                if(partOfLineWithClosingTag !== undefined) {
                    thisActualLineWithStylesApplied = thisActualLineWithStylesApplied + partOfLineWithClosingTag
                } 
            }
            linesWithInlineRulesApplied.push(thisActualLineWithStylesApplied)
        }
        return linesWithInlineRulesApplied
    }





Edit: Thanks to Kumar I realized my suggested method is nonstandard, and

getComputedStyle( element );

should be used instead, which is a bit more difficult to use and filter but has the advantage of giving you only the final rules that actually apply to the element after the CSS was evaluated (which also includes default, not explicitly declared styles, making it a bit trickier to use in this case).


getMatchedCSSRules( element );

just use this vanilla javascript function. It does exactly what you want. I'd explain it if there were anything to explain that isn't implied by the name of the function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM