随着前端技术的发展,对前端ui的个性化
体验要求也越来越高。国外google
、Twitter
、Facebook
等大厂早早已经开始支持白天/黑夜模式,主题定制。国内现在越来越多的系统也开始注重用户个性化视觉效果,越来越多的系统需要支持主题定制。
传统的主题定制需要依赖
less
等预处理器实现。会有流程繁琐
、逻辑复杂
、性能损耗大
(需要全局替换css样式)等缺点,很多前端研发都会望而却步,以技术无法达到为由拒绝此需求。
其实随着前端的不断进步,目前已经可以有非常成熟
、兼容度非常好
的方案,可以非常容易
的实现主题切换或定制。它就是css variable。(中文含义为css变量)
先让我们来看下它的神奇效果(关闭左侧源码区域效果更佳):
接下来我们看下他的兼容度:
可以看到,除了可怜的IE已经被抛弃,其余的主流浏览器都支持!
接下来,我们开始正式从0开始介绍怎么样简单快速的使用css variable搭建原生切换主题功能!
定义css变量
定义css变量和定义普通的css样式键值一样简单。唯一的区别就是在css键值前面加上--(比如: --main-color: #000000;
)。一般情况下我们会在html根元素下定义好全局的css变量,以后我们就可以在文档内部的任意位置使用该变量,再也不用每次改的时候一个个替换啦!
:root {
--main-color: #000000;
}
注意: 自定义属性名是大小写敏感的,
--my-color
和--My-color
会被认为是两个不同的自定义属性。
使用css变量
使用css变量和设置一个普通的css值一样简单。唯一的区别就是在css值前面加上var(比如: color: color: var(--main-color);
)。其实和js的理念一样,通过一个函数加变量入参形式,返回一个真实的结果。
div {
background-color: var(--main-bg-color);
}
更灵活的使用css变量
因为css已经用变量形式呈现,结合强大的clac
功能,我们可以设置一些基本的ui样式间距,通过计算动态得到标准的ui间距规范,再也不用辛辛苦苦测量ui高保真间距啦!
:root {
/*************************** 在根元素内定义初始的css变量 ****************************/
/*********** css变量和普通的css样式的key唯一的差别就是加一个前缀-- ************/
/* 基础的内间距单元 */
--spacing-unit: 6px;
/* 通过放大按倍数的基础值来获得更为标准的ui间距规范 */
--cell-padding: (4 * var(--spacing-unit));
/* 同样可以通过计算来动态获取页面可以容纳的容器个数 */
--cell-margin: (var(--grid-gutter) / 2);
}
还可以css变量的默认值
可以通过参数形式,向var传入默认值,可以防止变量为空的情况
.header {
/* 如果变量未定义,我们还可以使用默认值来规避样式无法展示问题 */
background-color: var(--primary-color, #5E35B1);
padding-left: calc(4 * var(--spacing-unit));
transition: background-color 1s;
}
主题切换实战详解(html+css+js)
在理解了定义、使用css变量之后,结合接下来的html和css和js每一行跟css variable有关的核心代码注释,就会变的很好理解如何实现主题切换:
js部分
// 辅助函数 用来获取css的key和value。注意:css变量页和普通的css键值(如padding、color等)一样可以获取和重新设置。css变量和普通的css样式的key唯一的差别就是加一个前缀--
var getVariable = function(styles, propertyName) {
return String(styles.getPropertyValue(propertyName)).trim();
};
// 辅助函数 用来设置root根元素的变量值,也就是用来动态生成主题的方法
var setDocumentVariable = function(propertyName, value) {
document.documentElement.style.setProperty(propertyName, value);
};
//设置默认主题色
var chooseDefaultColor = function(event) {
//此处的target就是 点击的某一个按钮,从event.target获取当前dom节点。
//然后用window.getComputedStyle来获取绑定在dom节点中的style。
var styles = window.getComputedStyle(event.target);
// 获取style中的个性化的背景色和文字颜色
var primary = getVariable(styles, '--primary-color');
var text = getVariable(styles, '--primary-color-text');
// 将各个不同的个性化色值,设置成root根元素的色值,从而达到变更全局css变量的目的,进而实现主题切换
setDocumentVariable('--primary-color', primary);
setDocumentVariable('--primary-color-text', text);
};
// 获取全局html根元素下的全局css变量对象
var styles = window.getComputedStyle(document.documentElement);
var quantum = document.getElementById('quantum');
var gutter = document.getElementById('gutter');
var columns = document.getElementById('columns');
// 点击不同的按钮通过chooseDefaultColor设置不同的主题色
var buttons = document.querySelectorAll('.picker-button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', chooseDefaultColor);
}
// 按照预设好的css变量。初始化input滑块的初始值
quantum.value = getVariable(styles, '--spacing-unit').replace('px', '');
gutter.value = getVariable(styles, '--margins');
columns.value = getVariable(styles, '--grid-columns');
// 滑动不同的色值,覆盖html根元素的css变量
quantum.addEventListener('input', function() {
setDocumentVariable('--spacing-unit', quantum.value + 'px');
});
gutter.addEventListener('input', function() {
setDocumentVariable('--margins', gutter.value);
});
columns.addEventListener('input', function() {
setDocumentVariable('--grid-columns', columns.value);
});
css部分
:root {
/*************************** 在根元素内定义初始的css变量 ****************************/
/*************************** css变量和普通的css样式的key唯一的差别就是加一个前缀-- ****************************/
/* 基础的内间距单元 */
--spacing-unit: 6px;
/* 基础外间距 */
--margins: 2;
/* 基础主题色,可以通过在style内部设置高优先级的--primary-color来覆盖此处的--primary-color */
--primary-color: #5E35B1;
--primary-color-text: #FFF;
/* 每行可以显示几列.通过动态计算列宽实现 */
--grid-columns: 3;
/***************************** css变量还可以通过计算得到更为灵活的值****************************/
/* 通过放大按倍数的基础值来获得更为标准的ui间距规范 */
--margin-size: (var(--margins) * 2);
/* 通过放大按倍数的基础值来获得更为标准的ui间距规范 */
--cell-padding: (4 * var(--spacing-unit));
/* 甚至可以通过变量与变量之间的计算来获取更为灵活的间距标准 */
--grid-gutter: (var(--margins) * var(--spacing-unit));
/* 甚至可以通过变量与变量之间的计算来获取更为灵活的间距标准 */
--grid-margin: (var(--margin-size) * var(--spacing-unit));
/* 同样可以通过计算来动态获取页面可以容纳的容器个数 */
--cell-margin: (var(--grid-gutter) / 2);
}
.header {
/* 省略部分非核心代码 */
/* 如果变量未定义,我们还可以使用默认值来规避样式无法展示问题 */
background-color: var(--primary-color, #5E35B1);
}
.title {
/* 省略部分非核心代码 */
/* 字体大小动态设置 */
font-size: calc(4 * var(--spacing-unit));
}
.shade {
/* 省略部分非核心代码 */
/* 高度大小动态设置 */
height: calc(8 * var(--spacing-unit));
}
.cell {
/* 动态获取可以显示几列卡片 */
width: calc(100% / var(--grid-columns) - var(--grid-gutter));
}
html部分
<div class="header">
<div class="title">主题切换</div>
<div class="shade"></div>
<div class="controls">
<p class="control">
<span class="control-key">内间距大小:</span>
<input class="control-value" type="range" id="quantum" min="4" max="8" step="1">
</p>
<p class="control">
<span class="control-key">单列宽度:</span>
<input class="control-value" type="range" id="columns" min="1" max="4" step="1">
</p>
<p class="control">
<span class="control-key">外间距大小:</span>
<input class="control-value" type="range" id="gutter" min="1" max="5" step="1">
</p>
</div>
</div>
<div class="grid">
<!-- 可以看到这里通过style="--primary-color: #F44336; --primary-color-text: #FFF;"重新设置了css的变量值,因为css变量也遵从权重准则 -->
<div class="cell" style="--primary-color: #F44336; --primary-color-text: #FFF;">
<header class="cell-header">
<div class="cell-title">
红色
</div>
</header>
<main class="cell-content">
单击卡片上的按钮可以设置整个示例中的默认配色方案。
</main>
<div class="cell-actions">
<button class="picker-button">
使用此颜色作为主题色
<div class="ripple"></div>
</button>
</div>
</div>
<div class="cell" style="--primary-color: #E91E63; --primary-color-text: #FFF;">
<header class="cell-header">
<div class="cell-title">
Pink
</div>
</header>
<main class="cell-content">
卡片上的颜色不会收到全局css变量的影响,因为它们是在卡片级别单独定义的,也和普通股的 CSS 规则一样遵循优先级排序。
</main>
<div class="cell-actions">
<button class="picker-button">
使用此颜色作为主题色
<div class="ripple"></div>
</button>
</div>
</div>
<div class="cell" style="--primary-color: #9C27B0; --primary-color-text: #FFF;">
<header class="cell-header">
<div class="cell-title">
紫色
</div>
</header>
<main class="cell-content">
使用上面的控件来调整影响整个页面的一些属性。
</main>
<div class="cell-actions">
<button class="picker-button">
使用此颜色作为主题色
<div class="ripple"></div>
</button>
</div>
</div>
<div class="cell" style="--primary-color: #00BCD4; --primary-color-text: #424242;">
<header class="cell-header">
<div class="cell-title">
Cyan
</div>
</header>
<main class="cell-content">
通过拖拽控件可以任意调整主题间距、列宽等
</main>
<div class="cell-actions">
<button class="picker-button">
使用此颜色作为主题色
<div class="ripple"></div>
</button>
</div>
</div>
<div class="cell" style="--primary-color: #009688; --primary-color-text: #FFF;">
<header class="cell-header">
<div class="cell-title">
Teal
</div>
</header>
<main class="cell-content">
通过重新计算网格上单元格的相对大小来更改可以显示的列数。
</main>
<div class="cell-actions">
<button class="picker-button">
使用此颜色作为主题色
<div class="ripple"></div>
</button>
</div>
</div>
<div class="cell" style="--primary-color: #4CAF50; --primary-color-text: #424242;">
<header class="cell-header">
<div class="cell-title">
Green
</div>
</header>
<main class="cell-content">
如果觉得视图太拥挤或者太空旷,可以通过调整外间距可以自由控制每个元素的空间占比。
</main>
<div class="cell-actions">
<button class="picker-button">
使用此颜色作为主题色
<div class="ripple"></div>
</button>
</div>
</div>
</div>
可以看到,除了基本的html和css样式设置外,我们通过45行js代码就实现了主题切换,如果需要定制更多的主题颜色,按需增加主题色即可,甚至可以通过颜色面板,自由切换所有用户想要的主题色!