为何要使用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()函数
<div class="box">
box
<div>
box div
</div>
</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:
global
<div class="enclosing">
enclosing
<div class="closure">
closure
</div>
</div>
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:
<div class="default">
default
</div>
<div class="default highlighted">
default highlighted
</div>
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