资讯专栏INFORMATION COLUMN

聊一聊前端换肤

CocoaChina / 2264人阅读

摘要:之前在做网站换肤,所以今天想聊聊网站换肤的实现。一般实现如上图,我们会看到在某些网站的右上角会出现这么几个颜色块,点击不同的颜色块,网站的整体颜色就被替换了。

之前在做网站换肤,所以今天想聊聊网站换肤的实现。网页换肤就是修改颜色值,因此重点就在于怎么来替换。

一般实现


如上图,我们会看到在某些网站的右上角会出现这么几个颜色块,点击不同的颜色块,网站的整体颜色就被替换了。要实现它,我们考虑最简单的方式:点击不同的按钮切换不同的样式表 ,如:

theme-green.css

theme-red.css

theme-yellow.css

可以看出,我们需要为每个颜色块编写样式表,那如果我要实现几百种或者让用户自定义呢,显而易见这种方式十本笨拙,且拓展性并不高,另外,如果考虑加载的成本,那其实这种方式并不可取。

ElementUI 的实现

ElementUI 的实现比上面的实现高了好几个level,它能让用户自定义颜色值,而且展示效果也更加优雅。当前我的实现就是基于它的思路来实现。
我们来看看他是怎么实现的(这里引用的是官方的实现解释):

先把默认主题文件中涉及到颜色的 CSS 值替换成关键词:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L250-L274

根据用户选择的主题色生成一系列对应的颜色值: https://github.com/ElementUI/theme-preview/blob/master/src/utils/formula.json

把关键词再换回刚刚生成的相应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/color.js

直接在页面上加 style 标签,把生成的样式填进去:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L198-L211

下面我具体讲下我参考它的原理的实现过程 (我们的css 编写是基于 postcss 来编写的):

先确定一个主题色,其他需在在换肤过程中随主题色一起修改的颜色值就根据主题色来调用例如(上面已经说到了我们是基于postcss来编写的,所以就使用了如下函数来计算颜色值): tint(var(--color-primary), 20%)darken(var(--color-primary), 15%)shade(var(--color-primary), 5%) 等。这也类似就实现了上面的第一步

然后根据用户选择的颜色值来生成新的一轮对应的一系列颜色值:

这里我先把全部css文件中可以通过主题色来计算出其他颜色的颜色值汇总在一起,如下:

// formula.js
const formula = [
    {
        name: "hoverPrimary",
        exp: "color(primary l(66%))",
    },
    {
        name: "clickPrimary",
        exp: "color(primary l(15%))",
    },
    {
        name: "treeBg",
        exp: "color(primary l(95%))",
    },
    {
        name: "treeHoverBg",
        exp: "color(primary h(+1) l(94%))",
    },
    {
        name: "treeNodeContent",
        exp: "color(primary tint(90%))",
    },
    {
        name: "navBar",
        exp: "color(primary h(-1) s(87%) l(82%))",
    }  
];

export default formula;

这里的color函数 是后面我们调用了 css-color-function 这个包,其api使然。

既然对应关系汇总好了,那我们就来进行颜色值的替换。在一开始进入网页的时候,我就先根据默认的主题色根据 formula.js 中的 计算颜色汇总表 生成对应的颜色,以便后面的替换,在这过程中使用了css-color-function 这个包,

import Color from "css-color-function";

componentDidMount(){
this.initColorCluster = ["#ff571a", ...this.generateColors("#ff571a")];
        // 拿到所有初始值之后,因为我们要做的是字符串替换,所以这里利用了正则,结果值如图2:
        this.initStyleReg = this.initColorCluster  
            .join("|")
            .replace(/(/g, "(") // 括号的转义
            .replace(/)/g, ")")
            .replace(/0./g, ".");  // 这里替换是因为默认的css中计算出来的值透明度会缺省0,所以索性就直接全部去掉0
}

generateColors = primary => {
        return formula.map(f => {
            const value = f.exp.replace(/primary/g, primary);  // 将字符串中的primary 关键字替换为实际值,以便下一步调用 `Color.convert`
            return Color.convert(value);     // 生成一连串的颜色值,见下图1,可以看见计算值全部变为了`rgb/rgba` 值
        });
    };

图1:

图2,黑色字即为颜色正则表达式:

好了,当我们拿到了原始值之后,就可以开始进行替换了,这里的替换源是什么?由于我们的网页是通过如下 内嵌style标签 的,所以替换原就是所有的style标签,而 element 是直接去请求网页 打包好的的css文件

注:并不是每次都需要查找所有的 style 标签,只需要一次,然后,后面的替换只要在前一次的替换而生成的 style 标签(使用so-ui-react-theme来做标记)中做替换

下面是核心代码:

changeTheme = color => {
        // 这里防止两次替换颜色值相同,省的造成不必要的替换,同时验证颜色值的合法性
        if (color !== this.state.themeColor && (ABBRRE.test(color) || HEXRE.test(color))) {
            const styles =
                document.querySelectorAll(".so-ui-react-theme").length > 0
                    ? Array.from(document.querySelectorAll(".so-ui-react-theme")) // 这里就是上说到的
                    : Array.from(document.querySelectorAll("style")).filter(style => {  // 找到需要进行替换的style标签
                          const text = style.innerText;
                          const re = new RegExp(`${this.initStyleReg}`, "i");
                          return re.test(text);
                      });

            const oldColorCluster = this.initColorCluster.slice();
            const re = new RegExp(`${this.initStyleReg}`, "ig");  // 老的颜色簇正则,全局替换,且不区分大小写

            this.clusterDeal(color);  // 此时 initColorCluster 已是新的颜色簇

            styles.forEach(style => {
                const { innerText } = style;
                style.innerHTML = innerText.replace(re, match => {
                    let index = oldColorCluster.indexOf(match.toLowerCase().replace(".", "0."));

                    if (index === -1) index = oldColorCluster.indexOf(match.toUpperCase().replace(".", "0."));
                    // 进行替换
                    return this.initColorCluster[index].toLowerCase().replace(/0./g, ".");
                });

                style.setAttribute("class", "so-ui-react-theme");
            });
          

            this.setState({
                themeColor: color,
            });
        }
    };

效果如下:

至此,我们的颜色值替换已经完成了。正如官方所说,实现原理十分暴力

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102779.html

相关文章

  • [一聊系列]一聊WEB前端安全那些事儿

    摘要:所以今天,就和大家一起聊一聊前端的安全那些事儿。我们就聊一聊前端工程师们需要注意的那些安全知识。殊不知,这不仅仅是违反了的标准而已,也同样会被黑客所利用。 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/blog... 随着互联网的发达,各种WEB应用也变得越来越复杂,满足了用户的各种需求...

    AZmake 评论0 收藏0
  • [一聊系列]一聊前端模板与渲染那些事儿

    摘要:欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面不仅仅是代码作为现代应用,的大量使用,使得前端工程师们日常的开发少不了拼装模板,渲染模板。我们今天就来聊聊,拼装与渲染模板的那些事儿。一改俱改,一板两用。 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/blog...

    UCloud 评论0 收藏0
  • [一聊系列]一聊前端模板与渲染那些事儿

    摘要:欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面不仅仅是代码作为现代应用,的大量使用,使得前端工程师们日常的开发少不了拼装模板,渲染模板。我们今天就来聊聊,拼装与渲染模板的那些事儿。一改俱改,一板两用。 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/blog...

    Yangder 评论0 收藏0
  • [一聊系列]一聊前端模板与渲染那些事儿

    摘要:欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面不仅仅是代码作为现代应用,的大量使用,使得前端工程师们日常的开发少不了拼装模板,渲染模板。我们今天就来聊聊,拼装与渲染模板的那些事儿。一改俱改,一板两用。 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/blog...

    褰辩话 评论0 收藏0
  • [一聊系列]一聊前端功能统计那些事儿

    摘要:欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面不仅仅是代码什么是功能统计作为一名开发,我们的产品发布出去之后,无论是产品还是运营,其实都是想及时了解产品对用户产生的影响的。下一章,我们将继续聊聊速度统计。 欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/bl...

    PAMPANG 评论0 收藏0

发表评论

0条评论

CocoaChina

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<