整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

用三角函数sin和cos画模拟雷达示意图的html代

用三角函数sin和cos画模拟雷达示意图的html代码解析

.说明:

1.1 推荐指数:★★★★

1.2 环境:谷歌浏览器、微软vscode编辑器

1.3 熟悉sin和cos的函数与圆(画圆弧的关系)深入理解


比较真实的雷达界面扫描图

2.本次的模拟效果图:


3.css和js文件的引入:

3.1 同一个文件夹或者目录下引入:./xxx.js或者./xxx.css

3.2 大型的html文件或者一般在html文件的同一个目录下,建css文件夹和js文件夹,放入相应文件夹下,那么导入就是:css/xxx.css和js/xxxx.js

3.3 外部引入法:找到网址:比如:网址在代码注释里query-3.4.1.min.js,引入即可。

但是如果没有网络,那么html文件就不可用了,怎么办呢?有办法。

用浏览器打开,复制:网址在代码注释里jquery-3.4.1.min.js,打开。

全选复制(ctrl+a),在本地文件夹新建一个js文件:jquery-3.4.1.min.js,将复制的内容,黏贴进入,保存即可。向上面的js文件和css文件一样,作为本地js文件引入。

4.本次html文件代码:index.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Canvas带坐标雷达扫描特效</title>
<!--在线引入地址:https://code.jquery.com/jquery-3.4.1.min.js-->
<!--script-- src="./jquery.min.js"></!--script-->
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<!--将style作为单独css文件,引入,否认就是style和/style的标签内-->
<!--采用下面格式:link法-->
<link type="text/css" href="./style.css" rel="stylesheet" />
</head>
<body>

<canvas id="myCanvas"></canvas>
<div class="info">
  <!--显示文字段落-->
  <h1>Warning!!Enemy is coming!!</h1>
  <!--初始化cs-xo,之后就是message弹出动态数据-->
  <p class="message">cs-xo</p>
</div>
<!--将radar.js作为单独js文件,引入,否认就是script和/script的标签内-->
<script src="./radar.js"></script>
</body>
</html>

注意:外部引入和本地引入基本没什么区别,如果css和js=JavaScript写在html内,可能有不同,上面注释里有提到。

5.jquery-3.4.1.min.js代码不写了,可以自己去下载,免费的,或者上面的有讲解如何操作,此处略。

6.新建一个radar.js文件,代码如下:

var c=$("#myCanvas")[0];
var ctx=c.getContext("2d");
/*雷达界面颜色=草地绿色*/
var color_gold="124,252,0"
var ww,wh;
var center={x: 0,y: 0};
// 定义函数
function getWindowSize(){
  ww=$(window).outerWidth();
  wh=$(window).outerHeight();
  c.width=ww;
  c.height=wh;
  // 中心坐标是窗口的一半
  center={x: ww/2,y: wh/2};
  ctx.restore();
  ctx.translate(center.x,center.y);
}
// 启动函数
getWindowSize();
$(window).resize(getWindowSize);
// 10=敌机数量,随机出现
var enemies=Array(10).fill({}).map(
  function(obj){
    return {
      r: Math.random()*200,
      deg: Math.random()*360,
      opacity: 0
    } 
  }
);
setInterval(draw,10);
var time=0;
var deg_to_pi=Math.PI/180;
// 画圆的函数,三角函数sin和cos画圆法
function Point(r,deg){
  return {
    x: r*Math.cos(deg_to_pi*deg),
    y: r*Math.sin(deg_to_pi*deg),
  };
}
/*定义颜色函数*/
function Color(op){
  return "rgba("+color_gold+","+op+")";
}
function draw(){
  time+=1;
  /*画长方形的背景颜色填充 "#111"=黑色;*/
  ctx.fillStyle="#111";
  ctx.beginPath();
  /*画长方形*/
  ctx.rect(-2000,-2000,4000,4000);
  ctx.fill();
  /*十字垂直水平坐标线,0.1~1代表粗细*/ 
  ctx.strokeStyle="rgba(255,125,64,0.5)";
  // ctx.strokeStyle="255,125,64";// 默认粗细值为1
  ctx.moveTo(-ww/2,0);
  ctx.lineTo(ww/2,0);
  ctx.moveTo(0,-wh/2);
  ctx.lineTo(0,wh/2);
  ctx.stroke();
  // 雷达扫面绿色和半径大小
  ctx.strokeStyle=Color(1);
  var r=300;
  var deg=time;
  var newpoint=Point(r,deg);
  var line_deg=(time/2) % 360;
  // 雷达动态转动的扫描扇形的弧度为100,最大360
  var line_deg_len=100;
  for(var i=0;i<line_deg_len;i++){
    var deg1=(line_deg-i-1) ;
    var deg2=(line_deg-i) ;
    var point1=Point(r,deg1);
    var point2=Point(r,deg2);
    // 代表随着雷达扫描扇形的移动,颜色逐渐变淡
    var opacity=1-(i/line_deg_len)-0.3;
    if (i==0) opacity=1;
    ctx.beginPath();
    // 雷达扇形绿色动态渐变淡色
    ctx.fillStyle=Color(opacity);
    ctx.moveTo(0,0);
    ctx.lineTo(point1.x,point1.y);
    ctx.lineTo(point2.x,point2.y);
    ctx.fill();
  }
  enemies.forEach(function(obj){
    // 敌机出现的原点
    ctx.fillStyle=Color(obj.opacity);
    var obj_point=Point(obj.r,obj.deg);
    ctx.beginPath();
    ctx.arc(
      obj_point.x,obj_point.y,
      // 绿色敌机出现的小圆点4是半径
      4,0,2*Math.PI
    );
    ctx.fill();
    ctx.strokeStyle=Color(obj.opacity);
    /*如果这样直接设置,ctx.strokeStyle='#FF4500,那么敌机就是一直出现在屏幕';*/
    var x_size=6;
    /* 雷达显示敌机目标大小设置*/
    ctx.lineWidth=4;
    ctx.beginPath();
    ctx.moveTo(obj_point.x-x_size,obj_point.y+x_size);
    ctx.lineTo(obj_point.x+x_size,obj_point.y-x_size);
    ctx.moveTo(obj_point.x+x_size,obj_point.y+x_size);
    ctx.lineTo(obj_point.x-x_size,obj_point.y-x_size);
    ctx.stroke();
    if (Math.abs(obj.deg - line_deg)<=1){
      obj.opacity=1;
      // 显示左上角侦查到的地理坐标
      $(".message").text("Detected: "+ obj.r.toFixed(3) + " at " +obj.deg.toFixed(3));
    }
    obj.opacity*=0.99;
    // 显示敌机出现的涟漪圈的颜色
    ctx.strokeStyle=Color(obj.opacity);
    /*目标敌机显示后的圆圈涟漪粗细设置*/
    ctx.lineWidth=1;
    ctx.beginPath();
    ctx.arc(
      obj_point.x,obj_point.y,
      10*(1/(obj.opacity+0.0001)),0,2*Math.PI
    );
    ctx.stroke();
  });
  // 雷达虚点固定圆盘的颜色,半径等设置
  ctx.strokeStyle='yellow';
  var split=120;
  var feature=15;   // 15个小格
  var start_r=230; // 半径
  var len=5;
  for(var i=0;i<split;i++){
    ctx.beginPath();
    var deg=(i/120) * 360;
    if (i%feature==0){
      len=10;
      ctx.lineWidth=5;
    }else{
      len=5; 
      ctx.lineWidth=1;
    }
    var point1=Point(start_r,deg);
    var point2=Point(start_r+len,deg);
    ctx.moveTo(point1.x,point1.y);
    ctx.lineTo(point2.x,point2.y);
    ctx.stroke();
  }
  /*画圈函数定义,下面三个画圆函数的定义*/
  function CondCircle(r,lineWidth,func_cond){
    ctx.lineWidth=lineWidth;
    /* 颜色定义*/
    /*注意颜色定义:有引号的和没有引号的区别,就是前面有没有颜色图标*/
    ctx.strokeStyle='#00C78C';
    ctx.beginPath();
    for(var i=0;i<=360;i++){
      var point=Point(r,i);
      if (func_cond(i)){
        ctx.lineTo(point.x,point.y);
      }else{
        ctx.moveTo(point.x,point.y);
      }
    }
    ctx.stroke();
  }
  // 雷达外围的移动半弧形,300为半径,2代表粗细
  CondCircle(300,2,function(deg){
    return ((deg+time/10)%180)<90;
  });
  // 雷达中间绿色的移动半弧形,160为半径,2代表粗细
  CondCircle(160,2,function(deg){
    return ((deg+time/5)%180)<90;
  });
  // 雷达内的小圆圈,绿色虚线圈,1代表粗细
  CondCircle(100,1,function(deg){
    // 如果这里没有,那么颜色就是三条线函数里统一的颜色,也可以单独设立
    ctx.strokeStyle='pink';
    return (deg%3)<1;
  });
  
}

7.新建一个style.css文件,代码如下:

html, body {
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  /*隐藏浏览器的条形伸缩*/
  overflow: hidden;
}
/*定义画布*/
canvas {
  -webkit-transform: scaleY(-1);
          transform: scaleY(-1);
}
/*显示警告信息位置设置*/
.info {
  position: absolute;
  left: 10px;
  top: 10px;
}
h1 {
  /*显示警告信息文字颜色设置*/
  color:red;
  letter-spacing: 0.5px;
  margin: 0;
  font-size:smaller;
}
.message {
  /*显示雷达预警敌机的动态位置的文字颜色设置*/
  margin: 0;
  color:#00FFFF;
  font-size: x-large;
}

8.通过简单的例子,熟悉html、css和js的关系和布局等基本知识,熟练掌握基本操作。

计算机科学中,路径是指向文件系统中某个位置的字符串。路径可以是绝对的也可以是相对的。这篇文章将详细解释绝对路径和相对路径的区别,并通过例子来展示它们的使用。

绝对路径(Absolute Paths)

绝对路径是从文件系统的根目录(在Windows系统中是驱动器的根,如C:\,在UNIX系统中是/)开始的完整路径。它包含了从根目录到目标文件或文件夹的所有目录名,并以文件或文件夹名结束。

特点

  • 不依赖于当前工作目录。
  • 通常较长,因为它们包含了完整的路径信息。
  • 在任何时候都指向同一个文件或文件夹,不会因为当前位置的变化而改变。

使用实例

假设我们有一个位于Windows系统D盘的图片文件,其路径可能是:

D:\Photos\Holiday\beach.jpg

在UNIX系统中,如果有一个配置文件位于根目录下的etc文件夹中,其路径可能是:

/etc/nginx/nginx.conf

无论当前位置在哪里,上述路径都准确指向了特定的文件。

相对路径(Relative Paths)

相对路径是相对于当前工作目录的路径。它不是从根目录开始,而是从当前目录开始描述如何到达目标文件或文件夹。

特点

  • 依赖于当前工作目录。
  • 通常较短,因为它们仅包含从当前目录到目标位置的路径信息。
  • 可能会因为当前位置的变化而代表不同的文件或文件夹。

使用实例

假设当前工作目录是D:\Photos,要引用Holiday文件夹中的beach.jpg图片,相对路径将是:

Holiday\beach.jpg

如果需要引用同一级别目录下的另一个文件夹中的文件,例如当前工作目录是D:\Photos\Holiday,要引用Work文件夹中的report.docx文件,相对路径将使用..来表示上一级目录:

..\Work\report.docx

在UNIX系统中,如果当前工作目录是/etc/nginx,要引用同一级别的apache2目录下的apache2.conf文件,相对路径将是:

../apache2/apache2.conf

特殊符号

在相对路径中,有两个特殊符号经常使用:

  • .(点):表示当前目录。
  • ..(两个点):表示上一级目录。

使用这些符号,可以在文件系统中向上或向下导航。

在网页中使用绝对路径和相对路径

在创建网页时,链接到CSS文件、JavaScript文件、图片或其他网页通常需要使用路径。使用绝对路径或相对路径取决于资源的位置和你的特定需求。

HTML中的例子

假设网站的根目录结构如下:

/ (根目录)
|-- index.html
|-- about.html
|-- css
|   |-- styles.css
|-- images
|   |-- logo.png
|-- js
    |-- scripts.js

如果在index.html中引用styles.css,相对路径将是:

<link rel="stylesheet" type="text/css" href="css/styles.css">

如果在index.html中引用logo.png,相对路径将是:

<img src="images/logo.png" alt="Logo">

如果网站的URL是http://www.example.com,那么引用logo.png的绝对路径将是:

<img src="http://www.example.com/images/logo.png" alt="Logo">

结论

绝对路径和相对路径都是定位文件系统中文件和文件夹的有效方式。绝对路径提供了明确的位置,不依赖于当前工作目录,而相对路径则更加灵活,可以简化文件的链接,尤其是在网页设计和软件开发中。理解这两种路径的差异和应用场景,对于任何与文件系统交互的活动都是至关重要的。


SS in JS

CSS in JS是一种解决css问题想法的集合,而不是一个指定的库。从CSS in JS的字面意思可以看出,它是将css样式写在JavaScript文件中,而不需要独立出.css.less之类的文件。将css放在js中使我们更方便的使用js的变量模块化tree-shaking。还解决了css中的一些问题,譬如:更方便解决基于状态的样式更容易追溯依赖关系生成唯一的选择器来锁定作用域。尽管CSS in JS不是一个很新的技术,但国内的普及程度并不高。由于Vue和Angular都有属于他们自己的一套定义样式的方案,React本身也没有管用户怎样定义组件的样式[1],所以CSS in JS在React社区的热度比较高。

目前为止实现CSS in JS的第三方库有很多:(http://michelebertoli.github.io/css-in-js/)。像JSS[2]styled-components[3]等。在这里我们就不展开赘述了(相关链接已放在下方),这篇文章的重点是JS in CSS

JS in CSS又是什么

在上面我们提到CSS in JS就是把CSS写在JavaScript中,那么JS in CSS我们可以推断出就是可以在CSS中使用JavaScript脚本,如下所示。可以在CSS中编写Paint API的功能。还可以访问:ctx,geom。甚至我们还可以编写自己的css自定义属性等。这些功能的实现都基于CSS Houdini[4]

.el {
  --color: cyan;
  --multiplier: 0.24;
  --pad: 30;
  --slant: 20;
  --background-canvas: (ctx, geom) => {
    let multiplier = var(--multiplier);
    let c = `var(--color)`;
    let pad = var(--pad);
    let slant = var(--slant);

    ctx.moveTo(0, 0);
    ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);
    ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);
    ctx.lineTo(0, geom.height);
    ctx.fillStyle = c;
    ctx.fill();
  };
  background: paint(background-canvas);
  transition: --multiplier .4s;
}
.el:hover {
  --multiplier: 1;
}

Houdini 解决了什么问题

CSS 与 JS的标准制定流程对比

在如今的Web开发中,JavaScript几乎占据了项目代码的大部分。我们可以在项目开发中使用ES 2020、ES2021、甚至提案中的新特性(如:Decorator[5]),即使浏览器尚未支持,也可以编写Polyfill或使用Babel之类的工具进行转译,让我们可以将最新的特性应用到生产环境中(如下图所示)。

JavaScript标准制定流程.png

而CSS就不同了,除了制定CSS标准规范所需的时间外,各家浏览器的版本、实战进度差异更是旷日持久(如下图所示),最多利用PostCSS、Sass等工具來帮我们转译出浏览器能接受的CSS。开发者们能操作的就是通过JS去控制DOMCSSOM来影响页面的变化,但是对于接下來的LayoutPaintComposite就几乎没有控制权了。为了解决上述问题,为了让CSS的魔力不在受到浏览器的限制,Houdini就此诞生。

CSS 标准制定流程.png

CSS Polyfill

我们上文中提到JavaScript中进入提案中的特性我们可以编写Polyfill,只需要很短的时间就可以讲新特性投入到生产环境中。这时,脑海中闪现出的第一个想法就是CSS Polyfill,只要CSS的Polyfill 足够强大,CSS或许也能有JavaScript一样的发展速度,令人可悲的是编写CSS Polyfill异常的困难,并且大多数情况下无法在不破坏性能的情况下进行。这是因为JavaScript是一门动态脚本语言[6]。它带来了极强的扩展性,正是因为这样,我们可以很轻松使用JavaScript做出JavaScript的Polyfill。但是CSS不是动态的,在某些场景下,我们可以在编译时将一种形式的CSS的转换成另一种(如PostCSS[7])。如果你的Polyfill依赖于DOM结构或者某一个元素的布局、定位等,那么我们的Polyfill就无法编译时执行,而需要在浏览器中运行了。不幸的是,在浏览器中实现这种方案非常不容易。

页面渲染流程.png

如上图所示,是从浏览器获取到HTML到渲染在屏幕上的全过程,我们可以看到只有带颜色(粉色、蓝色)的部分是JavaScript可以控制的环节。首先我们根本无法控制浏览器解析HTML与CSS并将其转化为DOMCSSOM的过程,以及Cascade,Layout,Paint,Composite我们也无能为力。整个过程中我们唯一完全可控制的就是DOM,另外CSSOM部分可控。

CSS Houdini草案中提到,这种程度的暴露是不确定的、兼容性不稳定的以及缺乏对关键特性的支持的。比如,在浏览器中的 CSSOM 是不会告诉我们它是如何处理跨域的样式表,而且对于浏览器无法解析的 CSS 语句它的处理方式就是不解析了,也就是说——如果我们要用 CSS polyfill让浏览器去支持它尚且不支持的属性,那就不能在 CSSOM 这个环节做,我们只能遍历一遍DOM,找到 <style><link rel="stylesheet"> 标签,获取其中的 CSS 样式、解析、重写,最后再加回 DOM 树中。令人尴尬的是,这样DOM树全部刷新了,会导致页面的重新渲染(如下如所示)。

即便如此,有的人可能会说:“除了这种方法,我们也别无选择,更何况对网站的性能也不会造成很大的影响”。那么对于部分网站是这样的。但如果我们的Polyfill是需要对可交互的页面呢?例如scrollresizemousemovekeyup等等,这些事件随时会被触发,那么意味着随时都会导致页面的重新渲染,交互不会像原本那样丝滑,甚至导致页面崩溃,对用户的体验也极其不好。

综上所述,如果我们想让浏览器解析它不认识的样式(低版本浏览器使用grid布局),然而渲染流程我们无法介入,我们也只能通过手动更新DOM的方式,这样会带来很多问题,Houdini的出现正是致力于解决他们。

Houdini API

Houdini是一组底层API,它公开了CSS引擎的各个部分,如下图所示展示了每个环节对应的新API(灰色部分各大浏览器还未实现),从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展CSS。Houdini是一群来自Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程师组成的工作小组设计而成的。它们使开发者可以直接访问CSS对象模型(CSSOM),使开发人员可以编写浏览器可以解析为CSS的代码,从而创建新的CSS功能,而无需等待它们在浏览器中本地实现。

CSS Houdini-API

Properties & Values API

尽管当前已经有了CSS变量,可以让开发者控制属性值,但是无法约束类型或者更严格的定义,CSS Houdini新的API,我们可以扩展css的变量,我们可以定义CSS变量的类型,初始值,继承。它是css变量更强大灵活。

CSS变量现状:

.dom {
  --my-color: green;
  --my-color: url('not-a-color'); // 它并不知道当前的变量类型
  color: var(--my-color);
}

Houdini提供了两种自定义属性的注册方式,分别是在js和css中。

CSS.registerProperty({
  name: '--my-prop', // String 自定义属性名
  syntax: '<color>', // String 如何去解析当前的属性,即属性类型,默认 *
  inherits: false, // Boolean 如果是true,子节点将会继承
  initialValue: '#c0ffee', // String 属性点初始值
});

我们还可以在css中注册,也可以达到上面的效果

@property --my-prop {
  syntax: '<color>';
  inherits: false;
  initial-value: #c0ffee;
}

这个API中最令人振奋人心的功能是自定义属性上添加动画,像这样:transition: --multiplier 0.4s;,这个功能我们在前面介绍什么是js in css那个demo[8]用使用过。我们还可以使用+使syntax属性支持一个或多个类型,也可以使用|来分割。更多syntax属性值:

属性值描述<length>长度值<number>数字<percentage>百分比<length-percentage>长度或百分比,calc将长度和百分比组成的表达式<color>颜色<image>图像<url>网址<integer>整数<angle>角度<time>时间<resolution>分辨率<transform-list>转换函数<custom-ident>ident

Worklets

Worklets是渲染引擎的扩展,从概念上来讲它类似于Web Workers[9],但有几个重要的区别:

  1. 设计为并行,每个Worklets必须始终有两个或更多的实例,它们中的任何一个都可以在被调用时运行
  2. 作用域较小,限制不能访问全局作用域的API(Worklet的函数除外)
  3. 渲染引擎会在需要的时候调用他们,而不是我们手动调用

Worklet是一个JavaScript模块,通过调用worklet的addModule方法(它是个Promise)来添加。比如registerLayout,registerPaint, registerAnimator 我们都需要放在Worklet中

//加载单个
await demoWorklet.addModule('path/to/script.js');

// 一次性加载多个worklet
Promise.all([
  demoWorklet1.addModule('script1.js'),
  demoWorklet2.addModule('script2.js'),
]).then(results => {});

registerDemoWorklet('name', class {

  // 每个Worklet可以定义要使用的不同函数
  // 他们将由渲染引擎在需要时调用
  process(arg) {
    return !arg;
  }
});

Worklets的生命周期

Worklets lifecycle

  1. Worklet的生命周期从渲染引擎内开始
  2. 对于JavaScript,渲染引擎启动JavaScript主线程
  3. 然后他将启动多个worklet进程,并且可以运行。这些进程理想情况下是独立于主线程的线程,这样就不会阻塞主线程(但它们也不需要阻塞)
  4. 然后在主线程中加载我们浏览器的JavaScript
  5. 该JavaScript调用 worklet.addModule 并异步加载一个worklet
  6. 加载后,将worklet加载到两个或多个可用的worklet流程中
  7. 当需要时,渲染引擎将通过从加载的Worklet中调用适当的处理函数来执行Worklet。该调用可以针对任何并行的Worklet实例。

Typed OM

Typed OM是对现有的CSSOM的扩展,并实现 Parsing APIProperties & Values API相关的特性。它将css值转化为有意义类型的JavaScript的对象,而不是像现在的字符串。如果我们尝试将字符串类型的值转化为有意义的类型并返回可能会有很大的性能开销,因此这个API可以让我们更高效的使用CSS的值。

现在读取CSS值增加了新的基类CSSStyleValue,他有许多的子类可以更加精准的描述css值的类型:

子类描述CSSKeywordValueCSS关键字和其他标识符(如inherit或grid)CSSPositionValue位置信息 (x,y)CSSImageValue表示图像的值属性的对象CSSUnitValue表示为具有单个单位的单个值(例如50px),也可以表示为没有单位的单个值或百分比CSSMathValue比较复杂的数值,比如有calc,min和max。这包括子类 CSSMathSum, CSSMathProduct, CSSMathMin,CSSMathMax, CSSMathNegate 和 CSSMathInvertCSSTransformValue由CSS transforms组成的CSSTransformComponent列表,其中包括CSSTranslate, CSSRotate, CSSScale, CSSSkew, CSSSkewX, CSSSkewY, CSSPerspective 和 CSSMatrixComponent

使用Typed OM主要有两种方法:

  1. 通过attributeStyleMap设置和获取有类型的行间样式
  2. 通过computedStyleMap获取元素完整的Typed OM样式

使用attributeStyleMap设置并获取

myElement.attributeStyleMap.set('font-size', CSS.em(2));
myElement.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }

myElement.attributeStyleMap.set('opacity', CSS.number(.5));
myElement.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' };

在线demo[10]

使用computedStyleMap

.foo {
  transform: translateX(1em) rotate(50deg) skewX(10deg);
  vertical-align: baseline;
  width: calc(100% - 3em);
}
const cs = document.querySelector('.foo').computedStyleMap();

cs.get('vertical-align');
// CSSKeywordValue {
//  value: 'baseline',
// }

cs.get('width');
// CSSMathSum {
//   operator: 'sum',
//   length: 2,
//   values: CSSNumericArray {
//     0: CSSUnitValue { value: -90, unit: 'px' },
//     1: CSSUnitValue { value: 100, unit: 'percent' },
//   },
// }

cs.get('transform');
// CSSTransformValue {
//   is2d: true,
//   length: 3,
//   0: CSSTranslate {
//     is2d: true,
//     x: CSSUnitValue { value: 20, unit: 'px' },
//     y: CSSUnitValue { value: 0, unit: 'px' },
//     z: CSSUnitValue { value: 0, unit: 'px' },
//   },
//   1: CSSRotate {...},
//   2: CSSSkewX {...},
// }

Layout API

开发者可以通过这个API实现自己的布局算法,我们可以像原生css一样使用我们自定义的布局(像display:flex, display:table)。在Masonry layout library[11] 上我们可以看到开发者们是有多想实现各种各样的复杂布局,其中一些布局光靠 CSS 是不行的。虽然这些布局会让人耳目一新印象深刻,但是它们的页面性能往往都很差,在一些低端设备上性能问题犹为明显。

CSS Layout API 暴露了一个registerLayout方法给开发者,接收一个布局名(layout name)作为后面在 CSS中使用的属性值,还有一个包含有这个布局逻辑的JavaScript类。

my-div {
  display: layout(my-layout);
}
// layout-worklet.js
registerLayout('my-layout', class {
  static get inputProperties() { return ['--foo']; }
  
  static get childrenInputProperties() { return ['--bar']; }
  
  async intrinsicSizes(children, edges, styleMap) {}

  async layout(children, edges, constraints, styleMap) {}
});
await CSS.layoutWorklet.addModule('layout-worklet.js');

目前浏览器大部分还不支持

Painting API

我们可以在CSS background-image中使用它,我们可以使用Canvas 2d上下文,根据元素的大小控制图像,还可以使用自定义属性。

await CSS.paintWorklet.addModule('paint-worklet.js');
registerPaint('sample-paint', class {
  static get inputProperties() { return ['--foo']; }

  static get inputArguments() { return ['<color>']; }

  static get contextOptions() { return {alpha: true}; }

  paint(ctx, size, props, args) { }
});

Animation API

这个API让我们可以控制基于用户输入的关键帧动画,并且以非阻塞的方式。还能更改一个 DOM 元素的属性,不过是不会引起渲染引擎重新计算布局或者样式的属性,比如 transform、opacity 或者滚动条位置(scroll offset)。Animation API的使用方式与 Paint APILayout API略有不同我们还需要通过new一个WorkletAnimation来注册worklet。

// animation-worklet.js
registerAnimator('sample-animator', class {
  constructor(options) {
  }
  animate(currentTime, effect) {
    effect.localTime = currentTime;
  }
});
await CSS.animationWorklet.addModule('animation-worklet.js');

// 需要添加动画的元素
const elem = document.querySelector('#my-elem');
const scrollSource = document.scrollingElement;
const timeRange = 1000;
const scrollTimeline = new ScrollTimeline({
  scrollSource,
  timeRange,
});

const effectKeyframes = new KeyframeEffect(
  elem,
  // 动画需要绑定的关键帧
  [
    {transform: 'scale(1)'},
    {transform: 'scale(.25)'},
    {transform: 'scale(1)'}
  ],
  {
    duration: timeRange,
  },
);
new WorkletAnimation(
  'sample-animator',
  effectKeyframes,
  scrollTimeline,
  {},
).play();

关于此API的更多内容:(https://github.com/w3c/css-houdini-drafts/tree/main/css-animation-worklet-1)

Parser API

允许开发者自由扩展 CSS 词法分析器。

解析规则:

const background = window.cssParse.rule("background: green");
console.log(background.styleMap.get("background").value) // "green"

const styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
console.log(styles.length) // 5
console.log(styles[0].styleMap.get("margin-top").value) // 5
console.log(styles[0].styleMap.get("margin-top").type) // "px"

解析CSS:

const style = fetch("style.css")
        .then(response => CSS.parseStylesheet(response.body));
style.then(console.log);

Font Metrics API

它将提供一些方法来测量在屏幕上呈现的文本元素的尺寸,将允许开发者控制文本元素在屏幕上呈现的方式。使用当前功能很难或无法测量这些值,因此该API将使开发者可以更轻松地创建与文本和字体相关的CSS特性。例如:

  • flex布局: align-items baseline特性。需要知道每一个flex盒子中第一个元素的基线位置。
  • 首字母: 需要知道每个字母的基线高度和字母最大的高度,以及换行内容的基线长度。
  • 单个字形的前进和后退。
  • 换行: 需要访问字体数据,文本的所有样式输入以及布局信息(可用的段落长度等)。
  • 元素中的每一个line boxes都需要一个基线。(line boxes代表包含众多inline boxes的这行)

Houdini 目前进展

Is Houdini ready yet

(https://ishoudinireadyyet.com/)

Houdini 的蓝图

了解到这里,部分开发者可能会说:“我不需要这些花里胡哨的技术,并不能带收益。我只想简简单单的写几个页面,做做普通的Web App,并不想试图干预浏览器的渲染过程从而实现一些实验性或炫酷的功能。”如果这样想的话,我们不妨退一步再去思考。回忆下最近做过的项目,用于实现页面效果所使用到的技术,grid布局方式在考虑兼容老版本浏览器时也不得不放弃。我们想控制浏览器渲染页面的过程并不是仅仅为了炫技,更多的是为了帮助开发者们解决以下两个问题:

  1. 统一各大浏览器的行为
  2. JavaScript一样,在推出新的特性时,我们可以通过Polyfill的形式快速的投入生产环境中。

几年过后再回眸,当主流浏览器完全支持Houdini的时候。我们可以在浏览器上随心所欲的使用任何CSS属性,并且他们都能完美支持。像今天的grid布局在旧版本浏览器支持的并不友好的这类问题,那时我们只需要安装对应的Polyfill就能解决类似的问题。