Closed LeaVerou closed 5 years ago
My primary use case is for syncing rotation angles and scale factors or translation distances when I animate multiple elements such that corners/ edges match throughout the animation.
In this demo, I need to sync the rotation angle of the bright yellow squares and the post-skew scale factor of the orange squares. This is achieved by doing the trigonometric computations in the JS and then setting the values to the custom properties used in the transform.
A way to do it all from the CSS would be immensely useful.
I can try to fake it with different timing functions for rotation angles and scale factors or translation distances as I did in this demo, but that's just hardcoding the cubic-bezier() for every case.
Adding these would require a solution to enter π other than 0.5turn
etc. #309
Adding these would require a solution to enter π other than 0.5turn etc. #309
Um, why? (Yes, I did read the issue you linked to)
Some awesome use cases in presentation by @askd:
@LeaVerou Because @tabatkins said so in https://github.com/w3c/csswg-drafts/issues/309#issuecomment-330652717
On Sat, 2018-02-17 at 13:45 -0800, Christoph Päper wrote:
@LeaVerou Because @tabatkins said so in https://github.com/w3c/csswg- drafts/issues/309#issuecomment-330652717
Tab made a compelling case for wanting pi as a constant; others felt a unit would be better. But even Tab admits it's not strictly necessary. That is, you could add sin, cos, tan, atan, sqrt, pow, exp, without adding numeric constants. And maybe --pi works for now.
-- Liam Quin, W3C, http://www.w3.org/People/Quin/ Staff contact for Verifiable Claims WG, SVG WG, XQuery WG Improving Web Advertising: https://www.w3.org/community/web-adv/ Personal: awesome vintage art: http://www.fromoldbooks.org/
I also periodically come across situations in CSS -- and very often in SVG -- where trig functions would be very helpful for converting between angles and x/y dimensions.
In static markup, the solution is to hard-code approximate values, but that often leaves pixel gaps or discontinuities from rounding errors. In dynamic situations, as others have mentioned, the only solution is JavaScript (with lots of converting back and forth between radians for the JS functions and degrees or turns for my design and for SVG properties, which is the only time I usually need Math.PI
!).
I would argue for adding the trig functions and then wait and see how much demand there is for pi constant, or a root-2 constant. As Liam notes, an author could always define an arbitrary-precision constant as a custom property.
And most importantly, separating out the two features means that we could move ahead with functions without having to first decide the best syntax for constants.
Which exact functions are proposed here? sin()
, cos()
, arctan()
– tan()
, arcsin()
, arccos()
, …?
Several programming languages distinguish two arcus tangens functions: atan()
with a single parameter and atan2()
with two parameters.
(A generic (square) root sqrt()
or exponent exp()
or power pow()
function or a specific square root of two constant deserves its own issue.)
I use sqrt(3)
for 30/60 degree angles just as much as sqrt(2)
for 45 degree angles.
The MVP might be sin() cos() tan() arcsin() arccos() arctan() arctan2() sqrt()
.
Authors and libraries can use these to define --pi
, the golden ratio and --degreesPerRadian
.
Why would anyone ever need to manually specify --degreesPerRadian
when CSS has angular units? @ewilligers
@ewilligers Yeah, that set looks useful as a first pass. More general roots and powers might be useful in the future, but we should wait and see what the uptake is on these; they cover the vast majority of what you want to do in trig.
(I'd prefer the asin
/etc wording, to match JS.)
As @AmeliaBR suggests, we can leave off constants for now. I think it's probably best to address them thru the env()
function, as that nicely namespaces them for use everywhere.
Moz has defined: sin, sinh, cos, cosh, tan, tanh, atan, atan2, atanh... Cr also. In JS. I think names for CSS can be the same. Easier to remember.
hypot
would be nice, too - even if it's restricted to two args, My javascript uses hypot three times more often than sqrt; it's also fewer characters to type than sqrt( x * x + y * y )
, more clearly expresses the intent, and can be more accurate than the naive implementation.
The CSS Working Group just discussed trig
, and agreed to the following:
RESOLVED: Add sin() cos() tan() acos() asin() atan() atan2() hypot() sqrt() pow()
Canʼt atan2(y, x)
just be atan(y, x)
, cf. rgba()
?
I would hate to see sqrt()
be extended to two parameters later on, overriding the meaning of sq. Can we call it root()
instead and let a (future) second, optional parameter default to 2 for square root?
atan2() is attested by that name in tons of languages; we think it's more valuable to match that usage than try to be clever in saving function names.
sqrt() is just a convenience function; it's easier and more understandable than typing pow(N, .5)
. I doubt we'll want to add more root functions, since people can just use pow()
; square-root is just a very common operation.
An important concern in our naming was to match JS's Math.*
names, as they're already familiar to devs. We're not bound to sticking with those, but we'd like to stick with them if at all possible.
Is **
syntax for pow()
really considered?
JS uses **, others use ^, but everyone's function is called pow()
Do we use pow()
just for consistency with other languages?
Consistency is a reasonable argument I think ^_^. But also it means we get to avoid all the confusing evaluation rules; what's 2 ** 3 ** 4
equal to, what's -1 ^ 2
equal to, etc etc. JS had a lot of confusion solving these, and ended up with some interesting restrictions as a result (for example, you have to parenthesize the LHS if it's a negative value, to avoid any ambiguity).
pow(X, 2)
, on the other hand, is 100% unambiguous all the time.
On Wed, 2019-02-27 at 17:34 -0800, Tab Atkins Jr. wrote:
pow(X, 2)
, on the other hand, is 100% unambiguous all the time.
It’s also easier to search for than ** for people trying to understand a style sheet. I think that’s valuable when the feature is likely to be used, es, but not so often that it’ll be familiar to everyone.
Liam
-- Liam Quin, https://www.delightfulcomputing.com/ Available for XML/Document/Information Architecture/XSLT/ XSL/XQuery/Web/Text Processing/A11Y training, work & consulting. Web slave for vintage clipart http://www.fromoldbooks.org/
Iʼm all for author convenience, e.g. I still want an angular pi
unit for the same reason. Javascript also has cube root, cbrt()
, which should mean that some people find this convenient.
It would probably be convenient to also have functions for cotangent, secant and cosecant, even if Javascript does not provide these reciprocal functions. A reciprocal function, 1/x, for just the value, i.e. without changing the unit, might be beneficial as well, but no actual use case comes to mind.
Will commas (,
) or slashes (/
) be used as the argument delimiter in these functions?
More recently, I’ve seen slashes (/
) used as the “official” argument delimiter — like with background and grid declarations or modern color functions — while commas have been used to separate values from fallbacks — like with var()
. However, slashes (/
) might be confused for division symbols in math functions.
With a slash:
width: pow(2% / 4);
/* becomes */
width: 16%;
With a comma:
width: pow(2%, 4);
/* becomes */
width: 16%;
Neither feel especially wrong to me, but it looks a little funny when I add some inner calc.
With a slash:
width: pow(calc(2% / 4) / 4);
/* becomes */
width: 0.0625%;
With a comma:
width: pow(calc(2% / 4), 4);
/* becomes */
width: 0.0625%;
How about spaces?
width: pow(2% 4);
By requiring commas, we allow the arguments to be <calc-sum>
, like for the other math functions.
Users can write atan2(1 + 2, 3 + 4)
instead of atan2(calc(1 + 2) / calc(3 + 4))
.
Thanks for all the input everyone! (You're making my eventual job much easier.)
A couple quick responses:
@Crissov The consensus in the room was to match JavaScript's Math
functions for naming. I agree with Tab that sqrt()
should be considered a convenience function. If it turns out that we need a general root function because of numerical issues with pow
(i.e., with pow(pow(x, 1/3), 3)
not neatly cancelling out), then a root()
could be added later.
Regarding cotangent and so on: we're not saying they'll never be added, but they aren't the priority. JS has survived without them, so we think devs can work with what they're given. Again, until we start getting complaints about numerical imprecision causing real-world problems!
@jonathantneal @valtlai The way I interpret the slash delimiter is that it is added for clarity if simple space separation is ambiguous because of optional values, and commas indicate lists of repeated compound values. But, we have lots of existing functions that use commas for
The only functions (of those we're discussing at this point) that take multiple parameters are atan2, pow, and hypot.
atan2 is interesting since it is conceptually a ratio/division, but we want the function to be able to keep track of whether the numerator or denominator or both are negative. But it would probably be weird to have a slash mean "conceptual division and separator" in this one function when it is simple division in the others. And we're never going to extend it to a list, so there's no ambiguity in using a comma.
pow could be space-separated, but again there's no harm in making it a comma separator.
hypot is more a list of values, especially if we allow it to work on more than 2 (I'd like to at least support 3 values, for 3D transform constructs). So comma works there quite naturally.
@ewilligers I definitely like the idea of skipping the nested calc()
function if it can be omitted unambiguously. And as you say, it's an argument against using a slash as the separator, and against using whitespace — even if whitespace between two math expressions could technically be unambiguous to the parser, it would be very ambiguous to a human author!
@jonathantneal this from https://github.com/w3c/csswg-drafts/issues/2331#issuecomment-468473201:
With a comma:
width: pow(2%, 4);
/* becomes */
width: 16%;
disturbs me from a mathematical point of view. Isn't pow(2%, 4)
the same as pow(0.02, 4)
which evaluates to 0.00000016
or 0.000016%
?
Is there a consensus that for %
values the number before the %
is subjected to the calculation as though it were unit-less and then the %
is added back on again at the end? That seems weird to me...
To avoid this kind of ambiguities like pow(2%, 4)
, I support
TabAtkins: Maybe go back to pow() and sqrt() only accept numbers
Then authors can choose pow(2, 4) * 1%
to get 16%
or pow(2/100, 4) * 100%
to get 0.000016%
.
Note this is not just about percentages, e.g. if 1in
is 96px
, then it doesn't make much sense if pow(1in, 2)
is still 1in
or 96px
, but pow(96px, 2)
becomes 9216px
. If values with non-empty types are accepted, the types should be multiplied.
disturbs me from a mathematical point of view
Not only you!
I brought this up at the end of the face-to-face discusssion, but maybe it didn't get clearly reflected in the minutes. All mathematical operations will need to apply to dimensions in a proper way, following the CSS Typed OM rules (which treat %
as a unit which may have a separate meaning from its numeric equivalent, so pow(2%, 4)
would return a value with % to the power of 4).
Powers and roots will need to cancel out to get regular CSS values. This is why numeric precision may become an issue. It is slightly annoying if powers and roots don't neatly cancel out on the value. But it is a big problem if they don't neatly cancel out on the unit! One suggestion at the end of the call was to limit pow()
and sqrt
to plain numbers without units for now. (hypot
would still be available for the most common case of manipulating lengths, since it cancels out units internally.)
Ah, sorry, I was just commenting on what I saw on this issue and haven't caught up with the minutes yet. Glad I'm not alone here!
Yeah, we'll be using commas for argument separation, so we can use <calc-sum>
for the arguments. Same as min()
and max()
.
Yes, plan is for pow()
and sqrt()
to only accept <number>
, so we sidestep the numerical precision issues with their units. You can remove and re-add units yourself easily with some token division/multiplication, it's just some busywork. (To get pow(2px, 4)
== 16px
, you can instead write (pow(2px / 1px, 4) * 1px)
.) hypot()
will take dimensions, because it outputs the same unit type as it inputs, and doesn't do anything funky internally.
Adding more functions is on the table if necessary; abs()
and mod()
, in particular, are probably worth adding. We don't want to go too deep, as there's a lot of possible math stuff one could add, and Houdini Custom Functions is the next spec on my list once I finally finish up Typed OM sufficiently to feel happy building on it.
Another use case for trig functions (pow()
specifically) would be use it for fluid typography where you are interpolating between multiple values across breakpoints:
https://www.smashingmagazine.com/2017/05/fluid-responsive-typography-css-poly-fluid-sizing/
Finally we will be able to have responsive Skewing using atan
Example: https://stackoverflow.com/a/53446820/8620333
And some other responsive shapes that relies on sin
/cos
/tan
https://stackoverflow.com/a/55149965/8620333 https://stackoverflow.com/a/54875824/8620333 https://stackoverflow.com/a/54909491/8620333
For future reference, https://test262.report/browse/built-ins/Math is the JS testsuite for Math, for when I'm writing the CSS tests.
It's great news to hear that trigonometric functions are being considered for CSS.
Bit of a shameless plug here but I use square root when calculating the typographic scale across different devices meaning sometimes the scale needs to change. This is easier to do by just changing one CSS variable and letting CSS calculate the sizes dynamically responsively. Having sqrt()
in CSS will solve a lot of issues for me.
I created a workaround for now using PostCSS. https://github.com/limitlessloop/postcss-sqrt
Also created pow()
but it only works for whole numbers.
https://github.com/limitlessloop/postcss-pow
FYI @limitlessloop, but the trig functions and sqrt(), among others, aren't just being considered, they were already accepted and are in the spec. ^_^
@tabatkins even better, thanks for making me aware!
I keep stumbling on use cases that need trigonometric functions to be solved, and I always have to resort to JS or a preprocessor for them, which is a pain. Basically almost any time you need to match two things and angles are involved.
Sadly I couldn't find most of my use cases, but here are two:
Also the following use sqrt (precomputed), but would need trigonometry in the general case :
(credits: @MeFoDy for collecting these from my book)
What do you think? Can we make it happen?