如何方便得获取一整块dom的样式

很多时候看到网页上的某些dom结构和其样式,想要将其一起复制下来,这样以后想要什么样式的时候就不用自己去写了 。直接copy非常的方便。但苦于没有找到合适的工具,只能自己尝试写代码去实现。

方法1

第一次尝试的方法是使用getComputedStyle这个api,遍历dom,获取每个节点的样式,拼接出字符串,代码如下

const tags = ['br', 'hr', 'img', 'input', 'param', 'meta', 'link'];
let dom = document.createElement('div')
document.body.appendChild(dom)
let obj = window.getComputedStyle(dom)
let attrsListCamel = Object.keys(obj).filter(key=>!/\d/.test(key)&&!/webkit/.test(key))

let defaultValue = {}
let lineMap = {}
attrsListCamel.forEach(key => {
    defaultValue[key] = obj[key]
    lineMap[key] = key.replace(/[A-Z]/g,s=>'-'+s.toLowerCase()) 
})

/* 分隔符转驼峰命名法 */
function separatorToCamelNaming(name) {
    const nameArr = name.split(/-/g);
    let newName = '';
    for (let i = 0, j = nameArr.length; i < j; i++) {
        const item = nameArr[i];
        if (i === 0) {
            newName += item;
        } else {
            newName += `${item[0].toLocaleUpperCase()}${item.substr(1)}`;
        }
    }
    return newName;
}
/* 将style转换成字符串 */
function style2String(node, styleNames) {
    const css = window.getComputedStyle(node);
    const styleArr = [];

    for (const name of styleNames) {
        const fName = separatorToCamelNaming(name);
        let value = css[fName];
        if (value !== defaultValue[fName]) {
                value = value.replace(/"/g, '');

            styleArr.push(`${lineMap[fName]}: ${value};`);
        }

    }

    return styleArr.join(' ');
}
let id = 1
/* dom转html */
function dom2html(node,obj,mode = 1) {
    let htmltxt = '';
    if (node instanceof Comment) return ''
    else if (node.nodeName !== '#text') {
        const nodeName = node.nodeName.toLowerCase()
        
        const styleStr = style2String(node, attrsListCamel);
       
        if(mode == 1){
            if (node instanceof HTMLImageElement) {
                htmltxt += `<${nodeName} src="${node.src}" style="${styleStr}">`;
            } else {
                htmltxt += `<${nodeName} style="${styleStr}">`;
            }
        }else if(mode == 2){
            obj.csstxt += `#id${id}{${styleStr}}`
            if (node instanceof HTMLImageElement) {
                htmltxt += `<${nodeName} id="id${id++}" src="${node.src}">`;
            } else {
                htmltxt += `<${nodeName} id="id${id++}">`;
            }
        }
        //处理子节点
        if (!tags.includes(nodeName)) {
            // 子节点
            const childNodes = node.childNodes;
            for (let i = 0, j = childNodes.length; i < j; i++) {
                htmltxt += dom2html(childNodes[i],obj,mode);
            }
            htmltxt += `</${nodeName}>`;
        }
    } else {
        htmltxt += node.data;
    }
    return htmltxt
}

function styleToNumber(value) {
    return Number(value.replace('px', ''));
} 
function getHtml(node,mode){
    let obj = {
        csstxt:''
    }
    let htmltxt = dom2html(node,obj,mode)
    return{
        csstxt:obj.csstxt,
        htmltxt
    }
}

使用getHtml方法,第一个参数是dom,第二个参数为1标签内联style,为2提取单独的css

这个方法有很大缺陷

1.上下级dom的很多样式会有重复,因为有的样式可以继承

所有元素可继承:visibility和cursor。

内联元素可继承:letter-spacing(字符间距)、word-spacing(增加或减少单词间的空白(即字间隔))、white-space、line-height、color、font、font-family、font-size、font-style、font-variant、font-weight、text-decoration、text-transform、direction。

终端块状元素可继承:text-indent和text-align。

列表元素可继承:list-style、list-style-type、list-style-position、list-style-image

2.获取的width,height单位都是px,而它可能原本是百分比的,这样dom就不会随着页面的resize而发送改变

方法2

使用document.styleSheets,这个对象的每个属性都是CSSStyleSheet类型的对象

image-20221002165650781

CSSStyleSheet类型的对象中的cssRules和rules是同一个对象,包含了一条一条的样式规则,选择器以及样式

image-20221002165939356

首先把dom结构打平成一层的数组flatDoms,把所有的cssRule收集到一个数组中,遍历这个数组,用document.querySelectorAll获取该条规则对应的dom数组,若该数组和flatDoms有交集,则把该条规则记录下来。

但是有的styleSheet是有跨域的限制,不能读取rules

image-20221002170629753

对此可以尝试通过fetch(sheet.href)去获取该样式表,创建style标签插入到页面中,之后再进行获取dom和样式的操作

完整代码如下

function getStyleRules(){
    let obj = {},arr = []
    for(sheet of document.styleSheets ) {
        try{
            sheet.cssRules
            for(rule of sheet.cssRules )  {
                obj[rule.selectorText] = rule.cssText
                arr.push(rule.selectorText)
            }
        }catch(err){
            console.log(sheet,"不能读取");
        }
    }
    return {
        obj,arr
    }
}
function getFlatArray(root){
    let arr = []
    function flatdom(dom){
        dom.childNodes.forEach(dom=>{
            arr.push(dom)
            flatdom(dom)
        })
    }
    arr.push(root)
    flatdom(root)
    return arr
}
function hasTheSameDom(flatDoms,selectedDoms){
    for(dom of selectedDoms){
        if(flatDoms.includes(dom)){
            return true
        }
    }
    return false
}
function getUsedCss(dom){
    let flatDoms = getFlatArray(dom),cssArr = [],fakeCssArr=[]
    let {arr:selectors,obj:ruleMap} = getStyleRules()

    selectors.forEach(selector=>{
        if(selector&&selector.includes(':')){
            let newselector = selector.split(":")[0]
            if(!newselector){//直接以:开头的选择器
                fakeCssArr.unshift(ruleMap[selector])
            }else{
                let selectedDoms =  document.querySelectorAll(newselector)
                if(hasTheSameDom(flatDoms,selectedDoms)){
                    fakeCssArr.push(ruleMap[selector])
                }
            }
          
        }else{
            let selectedDoms =  document.querySelectorAll(selector)
            if(hasTheSameDom(flatDoms,selectedDoms)){
                cssArr.push(ruleMap[selector])
            }
        }
    })
    return {
        cssArr:cssArr.join(''),
        fakeCssArr:fakeCssArr.join('')
    }
}
function getHtml(dom){
    let html = dom.outerHTML
    html = html.replace(/<style.*?>(.|\n)*?<\/style>/g,'')
                .replace(/<script.*?>(.|\n)*?<\/script>/g,'')
    return html
}
function getHtmlAndCss(dom,mode){
    if(mode == 1){
        let promiseArr = []
        for(let sheet of document.styleSheets ) {
            try{
                sheet.cssRules
            }catch(err){
                let p = fetch(sheet.href)
                .then(response => response.text())
                .then(response => {
                    const st = document.createElement('style');
                    st.textContent = response;
                    document.body.append(st);
                    sheet.ownerNode.parentNode.removeChild(sheet.ownerNode)
                });
                promiseArr.push(p)
            }
        }

        Promise.all(promiseArr).then(()=>{
            console.log({
                html:getHtml(dom),
                ...getUsedCss(dom)
            });
        }).catch(err=>{
            console.log(err)
        })

    }else{
        return{
            html:getHtml(dom),
            ...getUsedCss(dom)
        }
    }
}

使用getHtmlAndCss方法,第一个参数是dom,第二个参数,如果设置1,则会先尝试去获取所有的rule,如果因为跨域而出现获取不到的情况,则通过fetch实现

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值