CSS Type Casting to Numeric: tan(atan2()) Scalars
Tags: css, webdev, hacking, howto
Published: 2023-10-09 05:48:34

In current browser implementations of CSS, you cannot divide by length types; `calc(100vw / 5px)` does not work. It will eventually because [it's in the spec](https://www.w3.org/TR/css-values-4/#calc-type-checking), and [with enough support it may happen soon](https://github.com/web-platform-tests/interop/issues/513). But for now, no way to produce scalars based on size.

With, technically, one up-and-coming exception, [`atan2()`](https://developer.mozilla.org/en-US/docs/Web/CSS/atan2).

[![screenshot of caniuse data for atan2 showing 87.25% support](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/417b276cdr1mkrvyvq21.png)](https://caniuse.com/mdn-css_types_atan2)

`atan2()` accepts 2 params, effectively the slope, rise over run, y divided by x, `atan2(height, width)` and returns the angle from the bottom of the slope looking to the top.

Then, did you know? `tan(atan2(height, width))` is the scalar `<number>` between the two dimensions.

> No, I did not know that; is that true?

That's right... One could scale all kinds of dimensions, using simple trigonometry functions.

> Really...?

If one were so _inclined_.

---

## tan(atan2()) is just a scalar

No need to dive into normal trig things; pretty graphs, maps, and geometric art, etc.

Fundamentally, `tan(atan2())` is just a scalar between two dimensions.

For this article I'll focus on identity scaling as a means to typecast into a numeric value but there are many ways to use this and compare any two dimensions now.

---

### Screensize

One thing many people want is the ability to have the numeric pixel width (or height) of the viewport to find the aspect-ratio or do other calcs down the line.

I solved screensize in 100% CSS previously with a binary search using [The CPU Hack](https://dev.to/janeori/expert-css-the-cpu-hack-4ddj) but I realized today it's so much easier and faster with `tan(atan2())`:

> What is the numeric pixel value of 100vw?

should be as easy as

```css
:root {
  --px-width: tan(atan2(100vw, 1px));
}
```

but atan2 is super buggy in browsers right now. ([mixing vw and px was intended to work](https://developer.mozilla.org/en-US/docs/Web/CSS/atan2#parameters))

> Chrome returns `100` in this case, [which is strange/undesirable](https://bugs.chromium.org/p/chromium/issues/detail?id=1491662).

> Firefox returns `0` in this case because of the mixed units failing. If both are `vw` or both are `px`, Firefox correctly returns `100`.

> Safari seems to _always_ return `0` from `tan(atan2(Y, X))`  for _any_ `Y` and _any_ `X`. With or without units, mixed or matching. (edit: [Unless you wrap it in calc()](https://bugs.webkit.org/show_bug.cgi?id=263000))

So for now we first need `100vw` as `NNNpx`, which is easy in CSS because that's what happens automatically if you register a var as `<length>` then set it to a value like `--100vw: 100vw`

```css
@property --100vw {
  syntax: "<length>";
  initial-value: 0px;
  inherits: false;
}
```

Now this works in Chrome exactly as hoped:

```css
:root {
  --100vw: 100vw;
  --px-width: tan(atan2(var(--100vw), 1px));
}
```

`--px-width` is the width of the screen as a, usually integer, number

here it is live including one for `100vh` too:

{% codepen https://codepen.io/propjockey/pen/6a0d83e6c3dd0eacdfa35cd736a26d83 default-tab=css,result theme-id=40148 %}

---

### get font-size and more

You can use this same idea for any container query units, calc sizes containing mixed units, find out the px size of rem, anything you want. Just register a length, set its value, and convert to numeric px with `tan(atan2())`.

{% codepen https://codepen.io/propjockey/pen/58bff61364b7fbdd344bd0ac6a71dd45 default-tab=css,result theme-id=40148 %}

Easy peasy! Potentially super useful.

Works with `<time>` too

```css
:root::after {
  --ms: tan(atan2(12s + 1ms, 1ms));
  counter-reset: val var(--ms);
  content: "Numeric ms value of 12s + 1ms is: " counter(val);
  /* counter prints 12001 */
}
```

That's all numeric typecasting identity scalars, and it's fun to think about all the ways you can mix calcs now, but there's even more cool stuff tan-atan2 can-a-can-do, perhaps for a future article ~

---

## The Trig (update)

The trig behind this isn't necessary to know to use it, but I had a couple questions come up in private about it:

`tan( angle )` is a [function](https://en.wikipedia.org/wiki/Trigonometric_functions#Right-angled_triangle_definitions) that takes an angle then produces a result that's equal to "opposite over adjacent" - the height divided by the width of a right triangle:

![a right triangle with a shorter height than width, the hypotenuse bisects the image from the top left to the bottom right, the left side is labeled "height", the bottom is labeled "width". On the left bottom corner, inside the triangle, is a square indicating that it's a right triangle. The corner to the right on the bottom has an arc drawn and is labeled "angle"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/doia1yad6ya0wpp4a3c3.png)

`atan( ratio )` is a [function](https://en.wikipedia.org/wiki/Inverse_trigonometric_functions) that takes the value of height divided by width and returns the angle; the inverse of `tan()`.

`atan2( Y, X )` is a [programming language's adaptation of atan()](https://en.wikipedia.org/wiki/Atan2) that takes the height (Y) and width (X) as separate arguments instead of dividing them before passing, then returns the same thing atan would have.

So what this trick is doing is kind of silly in most worlds (and probably why nobody pointed it out sooner) because we're using two trig functions instead of division that `calc()` implementations can't do yet.

> `atan2( Height, Width )` = `angle` from the picture above

> `tan( angle )` = `Height / Width` from the picture above

> `tan( atan2( Height, Width ) )` = `Height / Width`

```css
@property --MyFullInlineSize {
  syntax: "<length>";
  initial-value: 0px;
  inherits: false;
}
@property --MyEm {
  syntax: "<length>";
  initial-value: 0px;
  inherits: false;
}
div.in-a-container {
  --MyFullInlineSize: 100cqi;
  --MyEm: 1em;
  --MyNumEmsWide: tan(atan2(
    var(--MyFullInlineSize),
    var(--MyEm)
  ));
  --MyNumPxWide: tan(atan2(
    var(--MyFullInlineSize),
    1px
  ));
}
```

---

## The End

If you think this is useful, fun, or interesting, it's the kind of thing I do in my free time! So please do consider following me around the web:

| PropJockey.io | CodePen | DEV Blog | GitHub | Mastodon |
| --- | --- | --- | --- | --- |
| [![PropJockey.io](https://raw.githubusercontent.com/propjockey/propjockey-brand/main/external-social/100px/propjockey-lines.svg)](https://propjockey.io) | [![CodePen](https://raw.githubusercontent.com/propjockey/propjockey-brand/main/external-social/100px/codepen.svg)](https://codepen.io/propjockey) | [![DEV Blog](https://raw.githubusercontent.com/propjockey/propjockey-brand/main/external-social/100px/dev.svg)](https://dev.to/janeori) | [![GitHub](https://raw.githubusercontent.com/propjockey/propjockey-brand/main/external-social/100px/github.svg)](https://github.com/propjockey) | [![Mastodon](https://raw.githubusercontent.com/propjockey/propjockey-brand/main/external-social/100px/mastodon.svg?)](https://front-end.social/@JaneOri) |

and [𝕏@Jane0ri](https://x.com/jane0ri)

👽💜
// Jane Ori

---

PS: I've been laid off recently and am looking for a job!

https://linkedin.com/in/JaneOri

Over 13 years of full stack (mostly JS) engineering work and consulting, ready for the right opportunity!