Variables don't work with calc() #818

Open
c-johnson opened this Issue Jun 24, 2013 · 59 comments

Projects

None yet
@c-johnson

The following scss code does not work:

$var: 10px;
height: calc(100% - $var);

Tested with latest version (3.2.9).

@Snugug
Contributor
Snugug commented Jun 24, 2013

You need to interpolate the $var in order to have it print out the 10px there:

$var: 10px;
height: calc(100% - #{$var});
@chriseppstein
Member

@nex3 is this intentional?

@nex3
Contributor
nex3 commented Jun 28, 2013

@nex3 is this intentional?

More or less. calc is considered to be a type of string literal, similarly to how url is treated. We could theoretically include a parser for the calc mini-grammar that would have explicit rules about how variables work, but then variables would work differently in that context than any other context, which may be unexpected.

I'm going to mark this as a feature request and leave it open until we come to a decision about what behavior we'd ideally like for calc.

@Undeterminant

I think that calc should be treated as a special case. If you do calc(100% - $var) and $var = 5%, it should just output 95% without the calc because it's unnecessary, but if $var is 100px, it should still output the calc.

@chriseppstein
Member

So the thing about calc() is that it's a CSS syntax hack. There are a couple of syntax issues which make the bare calculations like we have in Sass complicated and annoying to implement, so they introduce calc as a kind of quote. So what I think you should write in this case is simply 100% - $var and if $var is 5% it would compile to 95% and if $var is 100px it would compile to calc(100% - 100px).

In a future release, we will add support for calc() output, we just need some way to indicate that the feature should be enabled.

@mcgoooo
mcgoooo commented Nov 7, 2013

@chriseppstein what kind of indication would be good for you to enable it, i would find this an immensely useful feature seeing the impending death of ie8.

@cimmanon
cimmanon commented Nov 7, 2013

@mcgoooo Why do you think there is some magical impending death of IE8 on the horizon? Windows XP is still the 2nd most popular OS right now and it cannot upgrade beyond IE8. IE8 is also more popular than any other IE version at the moment.

Even if IE8 wasn't a concern, Android still is (no version of Android supports calc).

@mcgoooo
mcgoooo commented Nov 7, 2013

today google apps dropped support for ie9.
In april windows is officially discontinuing support for windows xp, meaning that for some they will not have to support ie8, and may even be the recommended direction to protect our users.

As for android, i have been using it to great effect with standard values and progressively enhancing with them and the results are wonderful. i have been very impressed with the support, and even found a reasonable polyfill for the (few) android browsers that do not support. My current android device with chrome seems to handle it natively.

polyfill
https://github.com/CJKay/PolyCalc

would some +1's on the issue help, i can maybe have a look at doing a pull request

currently to support i am looking at using erb before i use the sass, hardly an ideal solution, is there maybe another way to get this to work?

@lolmaus
lolmaus commented Nov 7, 2013

@cimmanon is absolutely right that calc() can't be used in projects that require maximum browser coverage. I'm not using it for this reason in any of my projects.

But this is not the case for everybody. I'm digging into Derby.js, a modern full stack framework based on Node. It doesn't support IE8 at all and is perfect for building rich internet applications that aren't supposed to be used on mobile.

In this case, i would totally use calc(), so i'm looking forward to @chriseppstein and other Compass maintainers to implement its proper support.

@mcgoooo
mcgoooo commented Nov 7, 2013

@lolmaus i think you said it better than me 👍

@mcgoooo
mcgoooo commented Nov 7, 2013

also here is the associated bug
https://code.google.com/p/android/issues/detail?id=55199&q=calc&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

ahh i see why it works on my native browser as well as chrome, i have kit kat
http://caniuse.com/#search=cal

@manovotny

Super glad I found this issue!

The interpolate tip @Snugug pointed out works like a champ, but I would love to see this handled automatically at some point.

For now, the work around will suffice.

@lolmaus
lolmaus commented Jul 28, 2014

@csdco, this shouldn't be so. Can you elaborate on your environment, versions, etc?

@charlie-s

My bad, I was using grunt-sass to compile, which is missing some features. I switched to grunt-contrib-sass and it's working as expected. Perhaps others will run into this comment.

@hasokeric hasokeric referenced this issue in drewrygh/ionic-ion-horizontal-scroll-cards Jul 19, 2015
Open

Bug in CSS/SCSS #5

@vlrprbttst

+1

@mcgoooo
mcgoooo commented Sep 9, 2015

using calc all the time now, is a great css feature.

@mcgoooo
mcgoooo commented Sep 9, 2015

@cimmanon i beleive ie8 is dead now, as is ie9 just about. Is there any way you could point me in the direction to fix this myself and put a patch in?

@chriseppstein
Member

Ok, here's my proposal for calc integration.

calc() expressions should be parsed into SassScript AST expressions. The parsing should use the calc grammar with the exception that it will allow Sass variable references in place of a value. I think this is the most natural expectation from the author's perspective.

Any SassScript expression that cannot be computed because the units are incompatible will no longer result in an error, instead that expression will output as a calc() expression.

Any calc() expression that can be resolved statically will evaluate to static value in the output instead of outputting as a calc() expression.

We need to decide how intermediate expressions will work.

For example:

$padding: 3px;
$margin: 2rem;
$runtime-expression: $padding + $margin;
$derived-expression: $runtime-expression / 2;

.output {
  calc-expression: $runtime-expression;
  derived-expression: $derived-expression
}

This could be an error at line 2 (because 3px + 2rem immediately resolves to a calc string) or it could output a value for derived-expression as calc( ( 3px + 2rem ) / 2 ). I lean towards the latter, even though it is harder to implement. Expressions would resolve their variable references into values, but otherwise unresolvable expressions would stay as expression references instead of values until output so that they can be composed with other expressions.

If we want SassScript expressions to be mixable in the same line of code with Calc expressions, I think we'd need a better parsing hint than interpolation (which always results in a string). Probably sass-script(...expression...). E.g. calc(1px + 2em + sass-script($foo % 3)) Personally, I think this is unnecessary at this time.

If we do this right, basically the calc() expression syntax is an implementation detail and not something that our users will need to use directly unless they want to.

@nex3, @xzyfer, @mgreter thoughts?

@davidkpiano

@chriseppstein would this proposal also support similar operations on calc expressions themselves?

E.g. calc(3px + 4rem) + 1rem - calc(2px + 5%) == calc(1px + 5rem - 5%), treating calc() as if they were parentheses.

@chriseppstein
Member

@davidkpiano Yes. though the output would probably be the following:

calc(3px + 4rem + 1rem - ( 2px + 5% ) )

We can write an expression simplifier as a follow-on feature at some point if it's a big deal.

@mcgoooo
mcgoooo commented Sep 9, 2015

@chriseppstein 👍
thanks, even a simple version of this would save me so many headaches.

@nex3
Contributor
nex3 commented Sep 11, 2015

@chriseppstein

Any SassScript expression that cannot be computed because the units are incompatible will no longer result in an error, instead that expression will output as a calc() expression.

This has potentially serious browser support implications. Based on caniuse, there are about 10% of browsers in use today that don't fully support calc(), and for users targeting those browsers automatic calc() generation is a ticking time bomb—it'll look like it works when they test on their main browser, but will fail in potentially-confusing ways on more obscure platforms.

This is something I think we should consider someday, but I don't think it's time for it yet.

The rest of your proposal sounds solid to me. I'm even okay with explicitly declared calc() values supporting arithmetic with normal numbers. This makes calc() a clear signal that something not-fully-compile-time is going on without having to write it over and over again.

@mcgoooo
mcgoooo commented Sep 13, 2015

@nex3 i fully disagree with this, for websites such as the one i run (https://boomf.com) the support on desktop browsers is at 99%ish of our desktop browsers, and there is different concerns on mobile.

to put it in context, we have made a business choice to not support below ie9, which supports calc with one minor bug (doesn't work in background position values)

We have been using this on production for close to a year with absolutely no problems, as it solves a lot of problems, and pushes the web forward. We give less than perfect to the 0.5% that is still usable.

I also disagree fundamentally with the aspect of not including something in case a professional in the field does not know how to use something in a reasonable and good manor, our job is to work around browser problems. you don't stop people using the feature by not including it, you just make it more difficult (and liable to bugs) The type of people that are going to be using experimental features, are likely to understand how to use these things correctly.

The only outlier is android, of which and with general responsive layouts these days, it is more needed in the field of desktop, which is at full support level pretty much. there is one minor bug in ie9 that does not really affect general usage.

screen shot 2015-09-13 at 13 18 07

one month of browser usage
screen shot 2015-09-13 at 13 25 03

and internet explorer usage
screen shot 2015-09-13 at 13 25 11

@cimmanon

Whether or not your target market supports calc is not the only consideration that needs to be made here. There are places where calc is explicitly forbidden according to the CSS specification, such as in media queries.

I am very uncomfortable with Sass silently converting expressions using incompatible units to calc. We already have a lot of confused users who don't understand that #{10}px is a string and that you can't do anything with it other than print it as is. You can't do greater/less than comparisons on it, you can't send it through the unitless function and get anything other than true. Having Sass do this without any explicit say-so is going to add to the confusion (why does Sass think this is a string? I am using numbers in my expression!).

@mcgoooo
mcgoooo commented Sep 13, 2015

@cimmanon that makes sense, it is definitely a tough and tricky problem and may cause some problems.
it would be nice to have it in sass, but if's not really possible/desirable, that is understandable, you have much better knowledge of how this would trip up users.

i may of jumped the gun a couple of years ago when i was bringing it up, but as a css feature it is mature enough to be used in production. with the cycle of android and lollipop replacing out the native browser with chrome, i can imagine it will be garnering wider attention in the next year or so. it can create some really amazing layouts.

@chriseppstein
Member

@nex3 10% is low enough for some users. I'd like to build this and keep it behind a feature flag that can be enabled at compile time. When the percentage is low enough we can change the default.

On Sep 11, 2015, at 2:06 PM, Natalie Weizenbaum wrote:

@chriseppstein
Any SassScript expression that cannot be computed because the units are incompatible will no longer result in an error, instead that expression will output as a calc() expression.

This has potentially serious browser support implications. Based on caniuse, there are about 10% of browsers in use today that don't fully support calc(), and for users targeting those browsers automatic calc() generation is a ticking time bomb—it'll look like it works when they test on their main browser, but will fail in potentially-confusing ways on more obscure platforms.

@xzyfer
Contributor
xzyfer commented Sep 14, 2015

Apologies for my naivety. Is the issue with calc() purely that it allows
arithmetic on units that Sass considers erroneous, or is there more to it?
On 14 Sep 2015 14:00, "Chris Eppstein" notifications@github.com wrote:

@nex3 10% is low enough for some users. I'd like to build this and keep it
behind a feature flag that can be enabled at compile time. When the
percentage is low enough we can change the default.

On Sep 11, 2015, at 2:06 PM, Natalie Weizenbaum wrote:

@chriseppstein
Any SassScript expression that cannot be computed because the units are
incompatible will no longer result in an error, instead that expression
will output as a calc() expression.

This has potentially serious browser support implications. Based on
caniuse, there are about 10% of browsers in use today that don't fully
support calc(), and for users targeting those browsers automatic calc()
generation is a ticking time bomb—it'll look like it works when they test
on their main browser, but will fail in potentially-confusing ways on more
obscure platforms.


Reply to this email directly or view it on GitHub
#818 (comment).

@chriseppstein
Member

Is the issue with calc() purely that it allows arithmetic on units that Sass considers erroneous, or is there more to it?

@xzyfer There's lots of issues with calc.

  • Presently we treat calc values as essentially strings right now, so when you change a variable from 2em to calc(2em - 2px) which is still conceptually a numeric value but it can't be used in any further mathematical calculations because it became a string.
  • We can optimize calc values that can be fully resolved at compile time but we don't.
  • We can change an error into a success (as you mentioned)
  • Calc Expressions and SassScript Expressions are two ways of writing numeric expressions, but once parsed to an expression (AST), they are like scss vs sass syntaxes there's no reason they can't interoperate.
@chriseppstein
Member

I am very uncomfortable with Sass silently converting expressions using incompatible units to calc. We already have a lot of confused users who don't understand that #{10}px is a string and that you can't do anything with it other than print it as is. You can't do greater/less than comparisons on it, you can't send it through the unitless function and get anything other than true. Having Sass do this without any explicit say-so is going to add to the confusion (why does Sass think this is a string? I am using numbers in my expression!).

@cimmanon I think there's some miscommunication here. My proposal is not that calc would evaluate immediately to a string, but instead to some intermediate "expression" representation that is not a simple value yet. A lot can be done to that expression that can't be done to a string.

You can't do greater/less than comparisons on it

This is true, trying to do a comparison would raise an error because there's not a calc-compatible if statement (yet).

you can't send it through the unitless function and get anything other than true

Not so, I can determine that there is no unit on the value calc(10 - 2) and that the unit of calc(2em - 2px) is em without actually performing the math on it.

why does Sass think this is a string?

Sass wouldn't think it's a string. It would give better error messages with my proposal than the current system. E.g. calc(2em + 3px) % 1em would return the error like "Error: cannot take the modulus of runtime expressions." (because calc doesn't have a modulo operator).

@nex3
Contributor
nex3 commented Sep 15, 2015

I think @cimmanon's point about strings is a more general one: that users don't have a strong sense of the non-visible semantics of SassScript values. #{12}px is one example of that, but silently generating calc() tickles it in different ways. Users doing what looks like a normal numeric operation with two numbers get back an object that doesn't behave like a number in a lot of situations.

And the visibility of automatic calc-conversion would be very low. Not only is it happening at runtime on stylesheets with no calc() in the source, it's triggered based on units, which users already have a hard time tracking and understanding. It's just a recipe for non-local errors that users can't understand or stylesheets with calc()s in their @media queries failing to style things properly in production.

I don't think it's onerous to require the user to explicitly write calc() in places where they want calc-y unit behavior. It follows the general Sass philosophy of making preprocessing visible, and if we make the calc()iness of a value contagious it'll be possible to use them pervasively without needing to annotate every operation.

@mcgoooo
mcgoooo commented Sep 15, 2015

generally from a users point of view, i wouldnt expect sass to be doing any of the calculation within the brackets, the only thing i would be expecting is variable susbtition.

Anything within the brackets is for the browser to calculate and i would expect that to left as such, rounding and calculation would logically be being done by the browser (how could you calculate vh etc)

Sass couldn't possibly ever make an intelligent presumption what was supposed to happen within the brackets, so it would make logical sense to me that it just got left alone bar variable substition.

in some ways im starting to understand how much trouble this gives more than initially, it is a fundamental step change in the abilities of css.

@nex3
Contributor
nex3 commented Sep 18, 2015

Sass wouldn't do any math in calc() other than reducing the expression to its simplest form.

@cimmanon

I do think something should be done when the value is already a calc expression and you want to expand the expression further:

$foo: calc(100% - 10px);
$bar: $foo - 5em;

Ideally, we'd want $bar to contain calc(100% - 10px - 5em). Right now, we get calc(100% - 10px) - 5em, which isn't valid CSS.

@nex3
Contributor
nex3 commented Oct 2, 2015

That's definitely how it would work. We would have a new value type, "calc expression", and arithmetic between calc expressions and numbers would pull the numbers into the inner expressions.

@chriseppstein
Member

So in this alternate proposal this would work?

$foo: 100px;
$bar: $foo + calc(5%); // => calc(100px + 5%)

If all that's needed is to wrap a number in a calc(), I like it. It's more obvious and not all that onerous.

@nex3
Contributor
nex3 commented Oct 2, 2015

@chriseppstein Exactly 😃.

@xzyfer
Contributor
xzyfer commented Oct 5, 2015

I like how this has ended up. I think @nex3 and @cimmanon are on to something good here.

@mcgoooo
mcgoooo commented Oct 5, 2015

Me too +1

On Monday, October 5, 2015, Michael Mifsud notifications@github.com wrote:

I like how this has ended up. I think @nex3 https://github.com/nex3 and
@cimmanon https://github.com/cimmanon are on to something good here.


Reply to this email directly or view it on GitHub
#818 (comment).

@shmdhussain

This is not working as i am substituting $xlarge-up-font-size and $large-up-font-size with values 1.38vw and 22 px
@media #{$large} {
margin-top: calc(-#{$large-up-font-size}-18px);
}
@media #{$xlarge} {
margin-top: calc(-#{$xlarge-up-font-size}-18px);
}

What I am getting is
screen shot 2015-10-19 at 8 05 59 pm

@davidkpiano

@shmdhussain Don't worry, that's not a Sass problem. You just need to add a space before and after the minus operator: calc(-#{$large-up-font-size} - 18px);

@shmdhussain

Thanks davidplano for spotted the spacing, that is the cause for the issue, Once I gave spacing, issue resolved.

@jossef
jossef commented Oct 26, 2015

+1 please add support for this

@Technolink

+1 support as well

@UnifiedInc

this is very confusing and support should be added

@jrauh
jrauh commented Apr 19, 2016

Has anyone ever run into the issue of calc functions not compiling with grunt? Here's my code:

$grid-gutter: 1rem;

.half {
  padding: calc(#{$grid-gutter} / 2);
}

The output I get is padding: calc(#{$grid-gutter} / 2); instead of the expected padding: 0.5rem;

Here's the other catch. In one of my projects, this is working fine. Not problems. In the other, I'm getting this issue. All calc functions with Sass interpolated variables won't work.

HELP!

@cimmanon

@jrauh Using calc() isn't supposed to evaluate the expression. I cannot reproduce what you're claiming. When I compile it, I get the expected output of this:

.half {
  padding: calc(1rem / 2);
}
@jrauh
jrauh commented Apr 19, 2016

You're right, I misspoke. I would expect the output to be what you mentioned. And I typically do in most projects. All but this one. I'm assuming it then is not sass related... just another bug somewhere else...? :/ I wasn't sure if anyone else had ever seen this issue.

@chriseppstein
Member

@jrauh Sass does not evaluate calc expressions. Are you running your compiled CSS through any other compressors or processors?

@jrauh
jrauh commented Apr 19, 2016

@chriseppstein Yepp I just figured it out. I'm using grunt and I needed to updated a version number. That was a big headache for such a small fix... 😐 Thanks for your help, all.

@machineghost
machineghost commented Aug 25, 2016 edited

Just in case it's been missed, this issue has racked up over 500 votes on a single Stack Overflow question:

http://stackoverflow.com/questions/17982111/sass-variable-in-css-calc-function

Until SASS supports:

height: calc(100% - $body_padding);

I suspect you're just going to have more and more confused people looking for answers on Stack Overflow (and elsewhere).

@speedplane
speedplane commented Sep 22, 2016 edited

I recently upgraded my SCSS compiler. The behavior of my custom calc mixin broke. This is my mixin:

@mixin width-calc($c) {
    width: -webkit-calc(#{$c});
    width: -moz-calc(#{$c});
    width: calc(#{$c});
}
$foo: 240px;
@include width-calc(100% - #{$foo});

This used to compile to the following:

width: -webkit-calc(100%-240px);
width: -moz-calc(100%-240px);
width: calc(100%-240px);

But now compiles to:

width: -webkit-calc(100%-"240px");
width: -moz-calc(100%-"240px");
width: calc(100%-"240px");

Notice that the pixels are quoted, which breaks the CSS. I'm not sure which commit may have changed this, but how would I go about writing a mixin that would work as expected?

@bursquare

Hi, I'm not sure you can use percentage substracted with pixels. But I had a similar issue with quotes, here it had to do with including a variable in the variable added to the calc function.

$myvar = $some-other-var / 4;
width: calc(100% - #{$myvar})

I've changed $myvar to #{$some-other-var} / 4 and now it seems to work.

@alberto2000

Any update on this? I'm trying to use calc() inside transforms, with no luck.

@nex3
Contributor
nex3 commented Dec 14, 2016

@speedplane What version of Sass are you using? I can't reproduce that on the latest stable.

@luigimannoni

@speedplane came here for the same reason, then found out a small mistake: forgot to close a double parenthesis, maybe the same applies to you.

This line:

transform: translateX(calc(($edge-threshold/2)*-1 - #{$size}) translateY($vertical-space);
Results in SASS complaining with this error:

transform: translateX(calc(($edge-threshold/2)*-1 - #{$size}) translateY($vertical-space);
                    ^
Expected a variable name (e.g. $x) or ')' for the parameter list for translateX

Slightly bit confusing, as probably it should point the mistake like this:

transform: translateX(calc(($edge-threshold/2)*-1 - #{$size}) translateY($vertical-space);
                                                              ^
Unexpected 'translateY' for the parameter list on translateX: expected a variable name (e.g. $x) or ')'
@trusktr
trusktr commented Jan 13, 2017

I'm trying something like following, using the technique mentioned above:

.foo {
  height: calc(100% - #{$foo-bar});
}

however that doesn't work. I can only get it to work by doing:

.foo {
  height: #{'calc(100% - ' + $foo-bar + ')'};
}

Why is that?

@nex3
Contributor
nex3 commented Jan 13, 2017

@trusktr That example works for me. What version of Sass are you using?

@trusktr
trusktr commented Jan 14, 2017 edited

@nex3 I think it's 1.0.1. Maybe that's why. x]

@nex3
Contributor
nex3 commented Jan 14, 2017

You're probably not using the Ruby implementation, either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment