为何要使用CSS自定义属性?
因为css preprocessor在变量的使用上有局限性:
-
不能动态修改变量值
-
在渲染时无法参考DOM的结构
-
变量值不能被javascript引用
如何声明CSS自定义属性?
类似于css preprocessor 的variables, $ in Sass, @ in Less, css custom propertiy 使用 -- 声明一个属性
.box{ --box-color: #4d4e53; --box-padding: 0 10px;}
如何使用CSS自定义属性?
使用var()函数
boxbox div
.box{ --box-color:rgba(200, 10, 100, .5); --box-padding: 0 10px; padding: var(--box-padding); } .box div{ color: var(--box-color); }
如何设置默认值?作为var()函数的第二个参数
.box{ --box-color:#4d4e53; --box-padding: 0 10px; /* 10px is used because --box-margin is not defined. */ margin: var(--box-margin, 10px);}
CSS自定义属性的计算
使用calc()函数
:root{ --indent-size: 10px; --indent-xl: calc(2*var(--indent-size)); --indent-l: calc(var(--indent-size) + 2px); --indent-s: calc(var(--indent-size) - 2px); --indent-xs: calc(var(--indent-size)/2);}
这里的:root代表html标签
如果使用了非单位数值(非px, rem等等),需要结合使用calc(),看下面的例子
:root{ --gap: 10;}.box{ padding: var(--spacer)px 0; /* DOESN'T work */ padding: calc(var(--spacer)*1px) 0; /* WORKS */}
CSS自定义属性的作用域和继承性
先来看看Sass变量的作用域
图片来源:smashingmagazine
可见,Sass变量的作用域依赖于代码的结构,然而css 自定义属性和css其他属性是一样的,是默认继承的(根据DOM的结构)
那么css 自定义变量的全局作用域在哪里?是:root
看个css 自定义属性的例子
html:
globalenclosingclosure
css:
Tip: .closure 继承了.enclosing的属性,但是font-size又是自己定义的,而且使用了.enclosing中定义的属性--enclosingVar
:root { --globalVar: 10px;}.enclosing { --enclosingVar: 20px;}.enclosing .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* 60px for now */}
其实css写成下面这样效果也是一样的,因为class 是天然 cascading (根据DOM的结构)
:root { --globalVar: 10px; } .enclosing { --enclosingVar: 20px; } .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* 60px for now */ }
到这里我们再来看看前面说到的css preprocessor 变量的三个问题
-
不能动态修改变量值
-
在渲染时无法参考DOM的结构
-
变量值不能被javascript引用
第一个,动态修改变量值,个人感觉这个没啥用,但css 自定义属性可以解决这个问题
:root { --globalVar: 10px;}.enclosing { --enclosingVar: 20px;}.enclosing .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* 80px for now, --closureVar: 50px is used */ --closureVar: 50px;}
一旦修改了自定义的属性值,浏览器就会重新计算相关变量值
第二个问题,渲染时没有参考DOM结构
还是看例子吧,比如有这么个需求,默认情况下,字体大小为10px, 在“highlighted”class时字体大小为30px
html:
defaultdefault highlighted
sass:
.highlighted { $highlighted-size: 30px;}.default { $default-size: 10px; @if variable-exists(highlighted-size) { font-size: $highlighted-size; } @else { font-size: $default-size; }}
结果:
分析:sass的结果是class=highlighted的文字的font-size还是为10px, 变量highlighted-size: 30px并没有起作用,这是为什么?
前面说过,css preprocessor变量的作用域是依赖于code的结构的,在.default{}的作用域中,访问.highlighted类中的变量,当然是未定义,所以font-size走else分支,使用default-size。为什么没有参考DOM结构呢?其实是因为sass变量的计算和处理都是在编译过程中,此时她完全不知道也无法知道DOM结果,所以,变量的作用域只能依赖于code的结构。
一个解决方法是.default{}加上.highlighted class: .default.highlighted {}
但是css 自定义属性在作用域和属性的层级结构上却有天然的优势,她可以像css其他的属性一样,参考页面的DOM结构
css:
.highlighted { --highlighted-size: 30px;}.default { --default-size: 10px; /* use the "default-size" except the "highlighted-size" is provided */ font-size: var(--highlighted-size, var(--default-size));}
结果:
第三个问题,css preprocessor 变量值不能被javascript引用
但css 自定义变量却可以使用getPropertyValue和setProperty()读、写变量值
/*** Gives a CSS custom property value applied at the element* element {Element}* varName {String} without '--'** For example:* readCssVar(document.querySelector('.box'), 'color');*/function readCssVar(element, varName){ const elementStyles = getComputedStyle(element); return elementStyles.getPropertyValue(`--${varName}`).trim();}/*** Writes a CSS custom property value at the element* element {Element}* varName {String} without '--'** For example:* readCssVar(document.querySelector('.box'), 'color', 'white');*/function writeCssVar(element, varName, value){ return element.style.setProperty(`--${varName}`, value);}
CSS 属性4个常用属性值和all属性
css的属性有4个通用的属性值:initial, inherit, unset, revert, 它们也可以使用在css自定义属性上
- initial: 缺省值,使用官方的css specification, 比如<p>元素,text-align为left;display为inline;
- inherit: 使用父元素的属性值,如果父元素的该属性没有定义,就相当于revert的效果
- unset: initial + inherti, 表示没定义时怎么办,有些属性会使用inherit,继承父元素的属性定义,比如color, 有些属性会使用initial,比如border
- revert: 以前叫做“default”, 当任何属性值在作者的stylesheet都没有定义时,采用下面的顺序寻找属性值
- 用户定义的stylesheet
- useragent stylesheet
- unset
详情参考这篇文章,里面的例子很详细:http://126kr.com/article/7ssx9rd04t6
现在,假如有个需求,某个组件要使用模块化的样式,可以使用all属性
.my-wonderful-clean-component{ all: initial;}
上面的代码表示重置了my-wonderful-clean-component类的样式,所有元素的属性值都使用官方css specification中的定义,排除了inherit; 但是all对于css 自定义属性并不起作用
未来理想的样子:
.my-wonderful-clean-component{ --: initial; /* reset all CSS custom properties */ all: initial; /* reset all other CSS styles */}
浏览器支持情况
http://caniuse.com/#search=custom%20properties
CSS自定义属性使用javascript动态改变三维视图例子
核心css:
#world{ --translateZ:0; --rotateX:65; --rotateY:0; transform-style:preserve-3d; transform:translateZ(calc(var(--translateZ) * 1px)) rotateX(calc(var(--rotateX) * 1deg)) rotateY(calc(var(--rotateY) * 1deg));}
定义了三个变量,分别控制视图的缩放,x轴、y轴的旋转,通过监听鼠标的mousewheel和mouseover事件,动态改变这三个变量,实现视图的三维动态响应
核心js:
class css3dCube { constructor() { this.worldEl = document.querySelector('#world'); this.worldZ = 0; this.worldXAngle = 0; this.worldYAngle = 0; this.bindEvents(); } // CSS updateView() { this.worldEl.style.setProperty('--translateZ', this.worldZ); this.worldEl.style.setProperty('--rotateX', this.worldXAngle); this.worldEl.style.setProperty('--rotateY', this.worldYAngle); } // EVENTS onMouseWheel(e) { let delta; if (e.detail) { delta = e.detail * -5; } else if (e.wheelDelta) { delta = e.wheelDelta / 8; } else { delta = e.deltaY; } if (!delta) return; this.worldZ += delta * 5; // scroll/perspective check if (this.worldZ > 300) { this.worldZ = 300; } else if (this.worldZ < -3000) { this.worldZ = -3000; } else { e.preventDefault(); } this.updateView(); }; onMouseMove(e) { this.worldXAngle = (.5 - (e.clientY / window.innerHeight)) * 180; this.worldYAngle = -(.5 - (e.clientX / window.innerWidth)) * 180; this.updateView(); }; bindEvents() { window.addEventListener('mousewheel', this.onMouseWheel.bind(this)); window.addEventListener('DOMMouseScroll', this.onMouseWheel.bind(this)); window.addEventListener('mousemove', this.onMouseMove.bind(this)); };}new css3dCube();
监听鼠标的变化,动态改变worldZ, worldXAngel, worldYAngel三个成员变量的值,然后通过updateView()方法修改DOM中元素的css 自定义属性值
详见:http://codepen.io/malyw/pen/xgdEQp
参考资料:
https://www.smashingmagazine.com/2017/04/start-using-css-custom-properties/?utm_source=frontendfocus&utm_medium=email
http://126kr.com/article/7ssx9rd04t6