简体   繁体   English

如何使用客户端 javascript 将外部/内部 css 样式转换为内联样式属性

[英]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:例如,如果我有包含在文档中的 CSS:

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

and html tag within document body:和文档正文中的 html 标签:

<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:我想将#demo中的所有内容都转换为一个字符串,其中包含它使用的所有样式的标签,例如:

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.我需要的是convertExternalInternalStylesToInline函数的内容,它自动获取所有后代元素,应用计算/使用的样式,然后将 css 样式添加到这些元素,然后将其全部作为 html 字符串返回。

Can it be done using only client side javascript ?可以仅使用客户端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 . 编辑:感谢Winchestro,我查找了window.getMatchedCSSRules函数,该函数实际上仅在Webkit中存在,并且有一个讨论,建议不要使用它

What you should be using is actually the window.getComputedStyle() Read the docs at MDN . 您实际上应该使用的是window.getComputedStyle() 阅读MDN上的文档

Another very useful resource you may look into is the CSSUtilities set of libraries . 您可能要研究的另一个非常有用的资源是CSSUtilities库集


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. 您需要做的是两件事,一个库来解析CSS对象模态(CSSOM),然后将相关CSS应用于DOM元素。

I know of a good nodejs library which does this called Juice . 我知道一个很好的nodejs库,它叫做Juice

There are is a library I found which would probably work on the front-end, called inlineresources , which has a browserified build 我发现有一个库可能在前端工作,称为inlineresources ,它具有浏览器化的版本

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... 再三考虑,我想您也许可以将Juice和browserify一起使用...但是您必须手动评估这种可能性...

assuming you have the HTML in an array (lines: string[]):假设你有一个数组中的 HTML(行: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 编辑:感谢Kumar,我意识到我建议的方法是非标准的,并且

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). 应该改用,这会更难使用和过滤,但是具有的优点是,仅提供评估CSS后实际应用于元素的最终规则(还包括默认样式,而不是显式声明的样式,从而使其在这种情况下使用起来有点棘手)。


getMatchedCSSRules( element );

just use this vanilla javascript function. 只需使用此香草javascript函数即可。 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. 如果有任何要解释的功能名称未暗示的内容,我将进行解释。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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