DEV Community

Jane Ori
Jane Ori

Posted on • Edited on

CSS Type Casting to Numeric: tan(atan2()) Scalars

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, and with enough support it may happen soon. But for now, no way to produce scalars based on size.

With, technically, one up-and-coming exception, atan2().

screenshot of caniuse data for atan2 showing 87.25% support

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 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

:root {
  --px-width: tan(atan2(100vw, 1px));
}
Enter fullscreen mode Exit fullscreen mode

but atan2 is super buggy in browsers right now. (mixing vw and px was intended to work)

Chrome returns 100 in this case, which is strange/undesirable.

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())

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

@property --100vw {
  syntax: "<length>";
  initial-value: 0px;
  inherits: false;
}
Enter fullscreen mode Exit fullscreen mode

Now this works in Chrome exactly as hoped:

:root {
  --100vw: 100vw;
  --px-width: tan(atan2(var(--100vw), 1px));
}
Enter fullscreen mode Exit fullscreen mode

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

here it is live including one for 100vh too:


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()).

Easy peasy! Potentially super useful.

Works with <time> too

: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 */
}
Enter fullscreen mode Exit fullscreen mode

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 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

atan( ratio ) is a function 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() 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

@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
  ));
}
Enter fullscreen mode Exit fullscreen mode

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 CodePen DEV Blog GitHub Mastodon

and 𝕏@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!

Top comments (6)

Collapse
 
ajstimson profile image
Andrew J Stimson

This is brilliant! Hey, we're hiring a Senior Frontend Engineer role at Agility Robotics and your background looks fantastic. Hit me up on LinkedIn!

Collapse
 
maxart2501 profile image
Massimo Artizzu

Oh that's clever, Jane! 👏

Collapse
 
maxwell_barvian_6944666e0 profile image
Maxwell Barvian

I think you might've just changed my life with this 😮

Collapse
 
afif profile image
Temani Afif

Based on some previous testing it seems that chrome simply ignore the unit and do the division.

You are not obliged to divide withpx. Any unit will do the job. I suppose it's because we cannot divide two units yet so the implementation is kind of strange.

This will probably get fixed at the same time when unit division is allowed.

Collapse
 
afif profile image
Temani Afif

In the example of the font size, if you replace the 1px with 1em/1rem/1vh/1ex, etc you will get the same result. What matters is the 1

Collapse
 
janeori profile image
Jane Ori

Yep yep, seems to be an imperfect implementation. Probably best to leave the unit on since the unit is technically correct and I imagine without it, it will not work in the future once it's cleaned up