Polyfill吊炸天的CSS attr()新语法

这篇文章发布于 2020年10月10日,星期六,23:56,归类于 CSS相关, JS实例。 阅读 24001 次, 今日 8 次 8 条评论

 

poly小狗 attr()函数 CSS

一、CSS attr()指定值类型的新语法简介

传统的attr()语法只能让HTML属性作为字符串使用,且只能使用在伪元素中,例如:

<span data-title="提示">按钮</span>
span:hover::after {
    content: attr(data-title);
}

但是全新的attr()语法那可就完全不得了了,可以让HTML属性值转换成任意的CSS数据类型。

例如如下所示的HTML和CSS语句:

<button bgcolor="skyblue" radius="4">按钮</button>
<button bgcolor="#00000040" radius="1rem">按钮</button>
<button bgcolor="red" radius="50%">按钮</button>
<button bgcolor="orange" radius="100% / 50%" title="by zhangxinxu(.com)">按钮</button>
button {
    background-color: attr(bgcolor color);
    border-radius: attr(radius px, 4px);
}

也就是几个按钮的样式使用了新的attr()语法进行设置,平常的按钮颜色、圆角都是在CSS中设定好的,这个例子中则不一样,颜色和圆角全部都是外部的HTML属性控制的。

理论上,上面的代码会有如下图所示的效果。

attr()新语法下的按钮理论效果

可以看出,如果浏览器支持了attr()函数的值类型新语法,那么我们日常的组件开发就会迎来巨大的颠覆,许多组件的接口可以直接交给CSS完成,以及我们的开发会更灵活,可以节省大量的CSS代码和书写CSS的时间,例如:

[ml] { margin-left: attr(ml px, 0); }
[mt] { margin-top: attr(ml px, 0); }
[mr] { margin-right: attr(mr px, 0); }
[mb] { margin-bottom: attr(mb px, 0); }
[pl] { padding-left: attr(pl px, 0); }
[pt] { padding-top: attr(pt px, 0); }
[pr] { padding-right: attr(pr px, 0); }
[pb] { padding-bottom: attr(pb px, 0); }

那么元素的marginpadding设置就不需要专门写在CSS样式中,直接在HTML中设置即可,支持任意的marginpadding大小,例如:

<div mt="10">上间距10px</div>

喔噢,看上去很厉害哦,HTML和CSS会一下子变得非常灵活与高效。

只可惜,attr()函数的新语法目前没有任何浏览器支持,而且在我看来很长一段时间浏览器都不会支持,安全、性能等方面的影响太深远了。

attr()新语法兼容性

不死心的我就琢磨着有没有什么办法让浏览器可以支持CSS attr()函数的这个新语法呢?

然后想到了可以借助CSS变量作为信使实现Polyfill实现。

二、Polyfill attr()新语法

CSS变量有一个特性,那就是CSS自定义属性值支持各种表达式和函数值,哪怕这个表达式是不知所云的东西。

例如随便自定义一个名叫keyword()的函数,然后使用如下所示的CSS调用:

body {
    --keyword: keyword(red, 50%); /* 合法 */
    color: var(--keyword);
}

结果浏览器认为语法是合法的(比方说下图语句没有出现删除线,说明语句是合法的):

CSS变量都是合法的

于是我们就可以利用CSS变量的这个特性Polyfill attr()函数,原理如下。

实现原理

  1. CSS自定义属性作为信使传递attr()函数,保证语法的合法性,例如:
    button {
        --attr-bg: attr(bgcolor color);
        background-color: var(--attr-bg);
    }
  2. 获取⻚⾯所有的包含attr()函数的⾃定义属性;
  3. 遍历并观察所有DOM,如果设置了对应的⾃定义属性,将attr()语法转换成浏览器识别的常规自定义属性语法。

基于上述原理,我写了个JavaScript脚本,大家只需要在页面中引入下面这段HTML代码,就可以畅快自如地使用attr()函数的新语法了。

如何使用

页面头部引入下面下面这个JS文件。

<script src="./css-attr.js"></script>

css-attr.js这个JS文件下载可以访问这里:css-attr.js

然后正常使用CSS变量来实现attr()效果即可,例如——

Polyfill测试

假设已经引入了css-attr.js,然后页面中有如下所示的HTML和CSS代码:

<button bgcolor="skyblue" radius="4">按钮</button>
<button bgcolor="#00000040" radius="1rem">按钮</button>
<button bgcolor="red" radius="50%">按钮</button>
<button bgcolor="orange" radius="100% / 50%">按钮</button>
button {
    border: 0;
    padding: .5em 1em;
}
button {
    --attr-bg: attr(bgcolor color);
    background-color: var(--attr-bg);
    --attr-radius: attr(radius px, 4px);
    border-radius: var(--attr-radius);
}

结果就有如下图所示的效果,按钮表现出了符合预期的效果。

attr()函数Polyfill后的生效示意

同时,如果我们手动修改按钮元素的bgcolorradius属性值,按钮的样式也会同步变化,眼见为实,您可以狠狠地点击这里:attr() polyfill后的按钮demo

实际开发中,按钮元素往往需要一个默认的主样式,此时可以通过属性选择器进行区分,例如:

button {
    color: #fff;
    background-color: deepskyblue;
}
button[bgcolor] {
    --attr-bg: attr(bgcolor color);
    background-color: var(--attr-bg);
}
button[radius] {
    --attr-radius: attr(radius px, 4px);
    border-radius: var(--attr-radius);
}

这样自定义的HTML属性无论设置还是不设置都不会影响按钮的正常显示了。

其他

然后这里还有个测试页面,风格粗犷的那种,您可以狠狠地点击这里:CSS attr() polyfill测试demo

点击按钮会修改HTML属性,大家可以看到样式同步变化了,如下图所示:

按钮颜色变化

补充于2020-12-01

相关JavaScript代码已经在gitee上开源了,地址是:https://gitee.com/zhangxinxu/css-attr-polyfill

也欢迎大家关注我的gitee账号。

三、结束语

CSS var()函数的这种任意表达式特性还适用于CSS自定义语法支持的实现上,关于这个,我会下一篇文章专门介绍,敬请期待。

以上就是自己研究出来的一点小东西,旨在抛砖引玉,欢迎交流!

另外,Polyfill实现的attr()语法比原生的语法要更强大,因此支持复合的HTML属性值,例如:

<div m="10px 20px 30px 40px">上间距10px</div>
[m] {
    --m: attr(m);
    margin: var(--m);
}

可以同时指定多个分享的margin属性。

然后兼容性这块,凡是支持CSS变量的浏览器都支持这里的attr()新语法。

好,就说这么多,

如果你觉得本文的内容还算有趣,欢迎分享。

(本篇完)

分享到:


发表评论(目前8 条评论)

  1. jsoncode说道:

    非常棒,不过有一个缺陷: 在vue/react这种动态渲染中,好像不生效。必须每次在ui发生变化后,重新执行css-attr这个js才行。

  2. 小一说道:

    这个特性可以干掉tailwind之类的原子库吗?

  3. 阿龙说道:

    挺好的,就是自定义属性不能同名。

  4. 阿币说道:

    [mt] { margin-top: attr(ml px, 0); }
    大神,ml px字母错了

  5. 景夏说道:

    单感觉这样的话dom上面定义的属性值太多了,还要记住自己定义的属性值名称

  6. 田伟说道:

    确实不错, 不知道老师有没有试过能否支持背景图片路径的url函数呢?