现代化CSS
1、W3C标准中的CSS变量
借助SASS,LESS等兼容CSS的扩展语言,我们可以很自然的写出各种样式变量,但大家有没有用原生CSS写过变量呢?大部分的编程语言自出现开始就是支持变量的,但CSS一开始就是不支持的。不过实际上CSS目前是支持了原生变量了的并且有一段时间了,只是对兼容性还是有一定要求。
css中原生变量的定义语法:
--variableName: variableValue;
变量名称(variable-name)的规范:
-
以“
--
”双横杠开头,后面可以是数字[0-9]
、字母[a-zA-Z
]、下划线`_`
和短横线-
这些组合,甚至是中文也行,但不能包含$、[、^、(、%
等字符。 -
大小写是敏感的
-
定义只能在声明块
{}
里面:root{ --1: red; /有效的/ --background-color: blue; /有效的/ --font_size: 20px; /有效的/ --宽度: 100px; /有效的/ --$color: red; /无效的/ } --color: red; /无效的/
// 在:root根元素中定义的变量是全局的,你可以在任意的HTML元素中定义(相当于局部变量)。 // 当定义多个同名变量时,变量的覆盖规则会是由CSS选择器的权重来决定的。
变量使用语法:
cssPropertyName: var(--variableName [, declarationValue]);
// var()会返回--variableName的值,declarationValue表示默认值,也就是当--variableName没有定义的时候,取declarationValue的值。
这里需要注意两点:
-
var()
第二个参数不需要双引号
或单引号
括起,括起无效。 -
var()
的默认值起作用仅限于变量未定义,像上面例子中,--color-name等于20px,然后赋给color只会失效,而不会取默认值
变量之间还可以相互传递,也就是相互赋值,类似JavaScript中的变量一样:
:root{
--red: red;
--color: var(--red);
}
div{ color: var(--color);}
<div>我是红色的</div>
使用限制:
-
CSS自定义属性变量是不能用作CSS属性名称的,比如:
var(--color): red
; -
不能用作背景地址,比如:
url(var(--url))
; -
由于var()后面会默认跟随一个空格,因此在其后面加单位是无效的,比如:
--size:20; font-size: var(--size)px
会解析成font-size: 20 px
;
在使用css原生变量时,请注意检查设备兼容性:
两种方式检测浏览器是否支持
// 1、使用 @supports 方法
@supports ( (--size: 0)) { /* 支持 */}
@supports ( not (--size: 0)) { /* 不支持 */}
//2、使用JavaScript
if(window.CSS && window.CSS.supports && window.CSS.supports('--size', 0)) {
/* 支持 */
}else{
/* 不支持 */
}
使用JavaScript操作原生属性变量
// 1、获取
var rootStyles = getComputedStyle(document.documentElement);
var value = rootStyles.getPropertyValue('--variableName');
// 获取某个元素中定义的属性变量
value = element.style.getPropertyValue('--variableName');
// 2、修改
element.style.setProperty('--variableName', value);
document.documentElement.style.setProperty('--variableName', value);
//实例:
div{ color: var(--color);}
<div>我是红色的,由JavaScript设置</div>
<script> document.documentElement.style.setProperty('--color', 'red');</script>
2、CSS预处理器与PostCSS
预处理器是处理特定格式源文件到目标css[浏览器所认识的]的处理程序。比如sass、less、stylus 。功能强大的postcss则是一种对css编译的工具,类似babel对js的处理,常见的功能如:
- 使用下一代css语法
- 自动补全浏览器前缀
- 自动把px代为转换成rem/vw
- css代码压缩等等
不同与sass/less等css预处理器可以用来扩充css语法,postcss只是一个工具,本身并不会对css进行操作,它通过插件实现功能,可以和less/sass结合使用,在webpack的配置中,可以在根目录中新建postcss的配置文件postcss.config.js,安装所需插件后进行配置
// postcss.config.js:
module.exports = {
plugins: [
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
require('postcss-px-to-viewport')({
viewportWidth: 750,
viewportHeight: 1334,
unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数
viewportUnit: 'vw',
selectorBlackList: ['.ignore', '.hairlines'],
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位
mediaQuery: false // 允许在媒体查询中转换`px`
})
]
}
常用的postcss插件:
Autoprefixer。前缀补全,全自动的,无需多说
postcss-cssnext。使用下个版本的css语法
postcss-pxtorem。把px转换成rem
postcss-custom-properties 运行时变量
postcss-simple-vars 与scss一致的变量实现
postcss-import 实现类似sass的import
postCSS
利用自身的parser
可以将css
代码解析为AST
,再利用众多插件改写AST
,最终输出改写后的css
代码。postCSS
的输入与输出产物都是css
文件。
3、CSS in JS
Houdini API
在javascript中,对于一些浏览器不支持或者还未实现的特性,总有人做出对应的polyfills,让你可以无感知的快速将新特性应用到Production环境,更别提我们还有Babel等工具帮忙转译。
但在css方面,除了制定CSS标准规范所需的时间外,各家浏览器的版本、实战进度差异更是况日持久,顶多利用PostCSS、Sass等工具来帮我们转译出浏览器能接受的css,开发者们能操作的就是通过JS去控制DOM与CSSOM来影响页面的变化,但是对于接下来的Layout、Point与Composite就几乎没有控制权了。
为了解决上述问题,为了让CSS的魔力不再被浏览器把持,Houdini就诞生了。CSS Houdini是由一群来自Mozilla,Apple,Opera,Microsoft,HP,Intel,IBM,Adobe与Google的工程师所组成的工作小组,志在建立一系列的API,让开发者能够介入浏览器的CSS engine。通过CSS Houdini API,开发者可以直接触及CSSOM,告诉浏览器如何去解析CSS,从而影响浏览器的渲染过程。
盒模型也发生了变化,现在可以拆分的非常细致,可控制元素颗粒度更小。Box tree API目的就是希望让开发者能够取得这些fragments的信息,至于取得后要如何使用,基本上应该会跟后面介绍的Parser API、Layout API与Paint API有关联。
Parser、Paint、Layout API:CSS词法分析器
-
CSS Parser API还没有被写入规范,它的基本思想是:允许开发者自由扩展CSS词法分析器,引入新的结构,比如新的媒体规则、新的伪类、嵌套、@extends、@apply等等。只要新的词法分析器知道如何解析这些结构,CSSOM就不会直接忽略它们,会把这些结构放到正确的地方。
-
CSS Layout API允许开发者通过CSS Layout API实现自己的布局模块,这里的"布局模块"指的是display的属性值。
-
CSS Paint API 与Layout API非常相似,他提供了一个registerPaint方法,操作方式和registerLayout方法也很相似。当想要构建一个CSS图像时候,开发者随时可以调用paint()函数,也可以直接使用注册好的名字。
比如,可以自己扩充实现一个布局 瀑布流,点击去试试
.item-list {display: layout(waterfall)}
// 注册这个布局方式的方法,其中带*是一定要实现的
registerLayout('waterfall', class {
static get inputProperties()
static get childrenInputProperties()
static get childDisplay()
* intrinsicSizes(children, styleMap)
* layout(constraints, children, styleMap, edges, breakToken)
})
// ===> 实现:
registerLayout(waterfall, class {
static get inputProperties(){
return ['width', 'height']
}
static get childrenInputProperties() {
return ['x', 'y', 'position']
}
layout(constraints, children, styleMap, edges, breakToken){
//
}
})
Worklets实践
xxxWorklet.addModule('xxx.js').then(...)
registerLayout和registerPaint这些代码放在哪里呢?答案就是worklet脚本(工作流脚本文件)。
Worklets的概念和web worker类似,它们允许你引入脚本文件并执行特定的JS代码,这样的JS代码需要满足两个条件:第一可以在渲染流程中调用;第二和主线程独立。
Worklet脚本严格控制了开发者所能执行的操作类型,这就保证了性能,Worklets的特点就是轻量以及生命周期较短。
CSS.paintWorklet.addModule('xxx.js')
CSS.layoutWorklet.addModule('xxx.js')
// xxx.js
registerPaint('xxx', class{
static get inputProperties(){...}
static get inputArguments(){...}
paint (ctx, geom, props){...}
})
typed OM Object
可以把CSS Typed OM视为CSSOM2.0,目的是解决目前模型的一些问题,并实现CSS Parsing API 和 CSS属性与值API相关的特性。它将CSS值转化为有意义类型的JavaScript对象,而不是现在的字符串。现在读取CSS值增加了新的基类CSSStyleValue,有许多的子类可以更加精准的描述CSS值的类型:
使用 Typed OM
主要有两种方法:
- 通过
attributeStyleMap
设置和获取有类型的行间样式; - 通过
computedStyleMap
获取元素完整的Typed OM
样式。
CSS?.px?.(4) ?? "4px"
//1、demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS IN CSS</title>
<style>
.el {
--elUnit:500px;
--arcColor:#8266ff;
width: var(--elUnit);
height: var(--elUnit);
background: paint(background-canvas);
--background-canvas:(ctx,geom) => {
console.log(ctx);
ctx.strokeStyle = `var(--arcColor)`;
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(200,200,50,0,2*Math.PI);
ctx.stroke();
ctx.closePath();
}
}
</style>
</head>
<body>
<div class="el"></div>
<script>
// 必须要启动一个server 否则无法加载
CSS.paintWorklet.addModule('./arc.js')
</script>
</body>
</html>
// arc.js
registerPaint(
'background-canvas',
class {
static get inputProperties() {
return ['--background-canvas']
}
paint(ctx,geom,properties) {
eval(properties.get('--background-canvas').toString())(ctx,geom)
}
}
);
// 需要启一个本地服务启动html页面,否则会提示跨域
// demo2 星空
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS IN CSS</title>
<style>
body{
margin:0;
color: #fff;
font-size: 24px;
background: #000;
}
body::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
--star-density:0.8;
--star-opacity:1;
background-image: paint(yd-sky);
}
body::before {
animation: shine 1s linear alternate infinite;
}
@keyframes shine{
from {
opacity: 1;
}
to {
opacity: 0.4;
}
}
</style>
</head>
<body>
<div class="el"></div>
<script>
// 必须要启动一个server 否则无法加载
CSS?.paintWorklet?.addModule('./arc.js') ??
(() => {
console.log('很遗憾,您的浏览器暂不支持Houdini')
})()
</script>
</body>
</html>
// arc.js
class YdSky {
static get inputProperties() {
return ['--star-density','--star-opacity']
}
paint(ctx,geom,properties) {
const xMax = geom.width;
const yMax = geom.height;
ctx.fillRect(0,0,xMax,yMax)
let starDensity = properties.get("--star-density").toString() || 1
starDensity > 1 && (starDensity = 1)
const hmTimes = Math.round((xMax + yMax) * starDensity);
for(let i=0;i<hmTimes;i++){
let x = Math.floor(Math.random() * xMax + 1);
let y = Math.floor(Math.random() * yMax + 1)
const size = Math.floor(Math.random() * 2 + 1)
const hue = Math.floor(Math.random() * 360 +1)
const opacity1 = Math.floor(Math.random()*9 +1)
const opacity2 = Math.floor(Math.random()*9 +1)
const opacity = +('.'+(opacity1+opacity2))*starDensity;
ctx.fillStyle = `hsla(${hue},30%,80%,${opacity})`
ctx.fillRect(x,y,size,size)
}
}
}
registerPaint(
'yd-sky',
YdSky
);
参考文章:
转载自:https://juejin.cn/post/7178814952948367415