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()
.
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));
}
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 arevw
or both arepx
, Firefox correctly returns100
.Safari seems to always return
0
fromtan(atan2(Y, X))
for anyY
and anyX
. 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;
}
Now this works in Chrome exactly as hoped:
: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:
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 */
}
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:
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
));
}
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:
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)
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!
Oh that's clever, Jane! 👏
I think you might've just changed my life with this 😮
Based on some previous testing it seems that chrome simply ignore the unit and do the division.
You are not obliged to divide with
px
. 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.
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
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