CSS魔法！如何将任意CSS类型转换为数值？
2023-10-16T10:04:09+08:00


欢迎关注我的公众号前端侦探本技巧参考自：https://dev.to/janeori/css-type-casting-to-numeric-tanatan2-scalars-582j在 CSS 中有各式各样的类型值，例如 1rem、10vw、100cqw等等，这些相对值给与了 CSS 强大的适应能力。但有时候，我们还需要知道这些相对值所对应的真实值，也就是px值，比如在移动端，我们经常会设置这样的根字号：:root{
  font-size: max(12px, min( 12px + (100vw - 320px) / 55 * 4, 20px ));
}
p{
  font-size: 2rem;
}那么，p此时的真实px值应该是多少呢？在以往，我们只能通过 JS去获取，但是现在，仅仅凭 CSS也能做到，一起了解一下吧一、CSS 三角函数从理论上来讲，将一个长度除以1px就应该得到具体的数值，相当于将这个长度以1px被分成了多少份2rem / 1px但是，大家可能都知道，calc在进行除法运算时，除数是不能带单位的calc(2rem / 1px) /*不合法*/不排除浏览器以后会支持不过现在终于迎来了转机，在 Chrome 111中，CSS 新增了一系列三角函数sin()')：正弦cos()')：余弦tan()')：正切asin()')：反正弦acos()')：反余弦atan()')：反正切atan2()')：反正切这里我们不聊三角函数的作用，只是作为一个桥梁中转一下关于正切函数，这里简单回顾一下高中数学在直角三角形中，其中一个锐角的对边和临边的比值就是这个角的正切值，反正切就是通过两条直角边反向去求出该角的弧度值，示意如下：这些函数本身没什么特别的，业界通用，但有一个非常例外，那就是atan2，它和atan作用是一样的，都是反正切函数。唯一的区别是，atan2支持两个值前端中的 atan 就是数学中的 arctanatan2(y, x)例如：transform: rotate(atan2(3, 2));
/*等同于*/
transform: rotate(atan(1.5));看似好像没啥意义，直接用tan不就好了？🤔transform: rotate(atan2(3/2));和JS不同的是，CSS 中还能支持单位，如下transform: rotate(atan2(1rem, 20px));
/*等同于?*/这就不得了了，如果此时的1rem是16px，那么浏览器会根据1rem的真实长度和20px做对比，相当于让两个带单位的长度做除法运算/*等同于*/
transform: rotate(atan(.8)); /* 16px / 20px */ 也就是说，y/x的值可以得到两个CSS长度之间的真实比值，而无需额外转换。然后，通过正切函数tan将括号里面的值解析出来tan(atan2(1rem, 20px)) /*.8*/这一点其实在 JS 中也可以验证一下，只是 CSS 可以带单位的值如果将后面的值设置为1px，就相当于把任意长度按照1px进行切分，也就得到了任意长度的px数值，如下tan(atan2(1rem, 1px)) /*16*/有了这个不带单位的绝对值，我们就可以做很多事情了，下面举几个例子二、CSS 获取当前字号回到文章开头的问题，如何通过 CSS 获取当前字号呢？根据上一节的原理，只要通过tan（） 和 atan2（）就可以将任意长度转换为数值了:root{
  font-size: max(12px, min( 12px + (100vw - 320px) / 55 * 4, 20px ));
}
body{
  --px-font: tan(atan2(1rem, 1px));
}这样--px-font就得到了真实的px数值。然后，我们还可以借助 CSS计数器，将这个数字渲染到页面关于这个技巧，可以参考之前的这篇文章：如何让CSS计数器支持小数的动态变化body::after{
  counter-reset: font var(--px-font);
  content: "当前根字号：" counter(font);
}结果...效果如下好像不太对？貌似是浏览器的 bug，这种情况下忽略了tan(atan2(1rem, 1px))中的1rem单位。要修复这个问题其实很容易，需要通过@property定义一个CSS变量，类型为长度，然后将需要计算的值赋给这个变量，如下@property --font {
  syntax: "<length>";
  initial-value: 1px;
  inherits: false;
}
:root{
  font-size: max(12px, min( 12px + (100vw - 320px) / 55 * 4, 20px ));
}
body::after{
  --font: 1rem; /*定义好的CSS变量*/
  --px-font: tan(atan2(var(--font), 1px));
  counter-reset: font var(--px-font);
  content: "当前根字号：" counter(font);
}这样就正常了，在改变屏幕尺寸时会自动渲染出当前字号你也可以访问在线链接CSS font-size (codepen.io)')三、实时显示容器尺寸CSS容器查询中出现了几个新的尺寸单位。这里只介绍下面两种关于容器查询，可以参考这篇文章：介绍2022最期待且已正式支持的CSS container容器查询 « 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)，本文并不涉及具体容器查询语句cqw  容器查询宽度（Container Query Width）占比。1cqw等于容器宽度的1%。假设容器宽度是1000px，则此时1cqw对应的计算值就是10px。cqh  容器查询高度（Container Query Height）占比。1cqh等于容器高度的1%。利用前面的技巧，可以将cqw、cqh这些单位转换为具体的px值。假设有这样一个容器.box{
  display: grid;
  place-content: center;
  width: 200px;
  height: 200px;
  background-color: #fff;
}为了使容器尺寸生效，必须声明容器类型.box{
  /**/
  container-type: size;
}然后用同样的技巧，在伪元素中通过计数器将宽高渲染出来.box::before{
  --w: 100cqw;
  --h: 100cqh;
  --px-width: tan(atan2(var(--w), 1px));
  --px-height: tan(atan2(var(--h), 1px));
  counter-reset: w var(--px-width) h var(--px-height);
  content: counter(w) " x " counter(h);
}别忘了修复 bug@property --w {
  syntax: "<length>";
  initial-value: 100px;
  inherits: false;
}
@property --h {
  syntax: "<length>";
  initial-value: 100px;
  inherits: false;
}效果如下，完全没有任何 JS你也可以访问在线链接CSS resize (codepen.io)')四、自适应文本的头像下面看一个自适应文本的头像，当文本较多时，会自动缩放，让文本可以完整展示，如下之前在这篇文章中有详细介绍：CSS 实现自适应文本的头像，有兴趣的可以回顾一下。原理其实是通过容器尺寸的映射关系来动态设置文本大小，关键实现如下.avator-container span {
  font-size: calc( 24px - 10cqw );
}但是，通过文字大小来缩放有一个限制，在PC上，一般会有最小字号限制（通常是12号），所以更好的方式应该是通过scale来实现。现在，我们可以通过上面的技巧，将容器尺寸转换成scale能够识别的数值，通过文本宽度和容器尺寸的比值来确实缩放比例，关键实现如下.avator-container span {
  --w: 100cqw;
  --scale: tan(atan2(30px, var(--w)));
  transform: scale(var(--scale));
}这样就得到了完全自适应尺寸的文本头像了完整代码可以访问在线链接CSS avator auto scale (codepen.io)')五、总结一下以上就是本文的全部内容了，介绍了一个非常巧妙并且有用的 CSS 小技巧，下面总结一下CSS 有很多类型的尺寸，这些尺寸给与了 CSS 强大的适应能力从理论上讲，将一个长度除以 1px 就可以得到具体的数值，但是calc不支持CSS 反正切函数 atan2(y,x) 支持两个参数，并且还支持带CSS单位通过 tan(atan2(y,x))可以得到y/x的比值，如果x是1px，可以得到y的实际px值利用这个技巧，可以将任意类型值转换为数值纯数值可以在各个地方都可以使用了，比如scale(var(--n))，如果需要 px，直接calc(var(--n) * 1px )最后，如果觉得还不错，对你有帮助的话，欢迎点赞、收藏、转发❤❤❤欢迎关注我的公众号前端侦探