6

I am using SCSS and wanted to specify a variable at top that is based on a sum of a px and a rem value.

I understand that it is not possible to combine unit types in SCSS variables, so instead I use calc():

$navbar-top: calc(40px + 1 rem);

Further down in the style sheet, I wanted to include a negated version of $navbar-top.

I tried both these, with no success:

(-$navbar-top)
-#{$navbar-top}

Any idea how I can negate the SCSS variable, without having to declare a new variable, or writing calc again throughout the CSS.

Thanks.


EDIT

  • As noted in the comments below, this is not a duplicate of Sass negative variable value?
  • This question is about negating a variable set with calc(), which is unrelated to the above SO post. I have noted both solutions presented in that question, above. None of them work in this particular case.

EDIT 2: SCSS SOLUTION BASED ON TEMANI'S ANSWER BELOW

Declare the necessary variables at top, including a "negator" (-1):

$navbar-top: calc(40px + 1rem);
$neg: -1;

Then, the variable can be used throughout (negated or not), like this:

/* Non-negated */
margin: $navbar-top 0 0;

/* Negated */
margin: calc(#{$neg} * #{$navbar-top}) 0 0;

The negated version above will compile to the following CSS:

margin: calc(-1 * calc(40px + 1rem)) 0 0;
Magnus
  • 6,791
  • 8
  • 53
  • 84
  • Possible duplicate of [Sass negative variable value?](https://stackoverflow.com/questions/13296419/sass-negative-variable-value) – csilk May 20 '18 at 14:13
  • @csilk I have tried all those solutions, as noted in the OP. The problem here is the combination of calc and negation. It's not a duplicate. – Magnus May 20 '18 at 14:31
  • So the only thing for it is to use another `calc`, as you noted. – Mr Lister May 20 '18 at 14:32
  • @csilk or whoever added the duplicate note, please remove it. The two questions are unrelated and the duplicate suggestion is misleading for future visitors. I edited the OP with an extra clarification. – Magnus May 20 '18 at 16:42

2 Answers2

3

Since calc is not a usual value you cannot simply append a - sign on it to obtain the negative value.

An idea would be to consider another parameter to control the sign. Here is an idea using pure CSS and CSS variable without the need of another calc()

.box {
  margin-top: calc( var(--s,1)*(40px + 1rem));
  height:100px;
  background:rgba(255,0,0,0.5);
}
<div class="box"></div>
<div class="box" style="--s:-1"></div>

You can define the negative value using class

.box {
  margin-top: calc( var(--s,1)*(40px + 1rem));
  height:100px;
  background:rgba(255,0,0,0.5);
}
.neg {
  --s:-1;
}
<div class="box"></div>
<div class="box neg"></div>

UPDATE

You may also consider the expression using variables:

:root {
  --exp:(40px + 1rem);
}

.box {
  margin-top: calc( var(--s,1)*var(--exp));
  height:100px;
  background:rgba(255,0,0,0.5);
}
<div class="box"></div>
<div class="box" style="--s:-1"></div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • I'm not sure using `--s:-1` instead of a negating `calc` makes things easier to maintain. Here I would advocate the KISS principle. – Mr Lister May 20 '18 at 14:36
  • @MrLister But i don't think using another calc make it flexible, as you will have something static on your code unlike CSS variable where we have a default value that we change only if needed – Temani Afif May 20 '18 at 14:38
  • Thanks Temani / @MrLister . Couple of follow-up questions if I may: 1) Is var() not experimental, i.e. not fully supported across all browsers? 2) Will it not be necessary to include the `40px + 1rem` across all places in the CSS I want to refer to that result? The hope was that I could defne it once at the top, then use it throughout. – Magnus May 20 '18 at 16:46
  • @Magnus 1) [caniuse](https://caniuse.com/#search=css%20var) says all modern browsers can use var, so it's a matter of how many versions back you want to support. 2) In this snippet, SCSS can't be used, so Temani Afif used the "compiled" CSS expression instead. You can put the SCSS variable there, of course. – Mr Lister May 20 '18 at 17:20
  • @MrLister Ah, got it, that makes sense. I guess we then still have to repeat the calc() function in each place, and also remember the inline styling on each relevant element. If I am using a pseudo-element, say `.foo::before {...}`, I guess I would set the custom variable there. But it does seem to be a lot of hassle. It almost feels like the easiest solution is just to define two separate variables, one with positive sign and one with negative. Even though you then have to update in two places when the px/rem needs to change. – Magnus May 20 '18 at 17:55
  • @Magnus yes you can define it once using more CSS variable. I am not experienced with SASS so I provided a CSS only solution that you can reduce using SASS [check the update] – Temani Afif May 20 '18 at 17:55
  • @Magnus also concerning the inline style, you are not obliged, you can define it using a class ... and you are not obliged to use it within the pseudo element as the pseudo element will inherit the value defined with its parent ... i simply went with the easier solution, check the update again – Temani Afif May 20 '18 at 18:12
  • 1
    Just for future visitors: Based on the above solution by @TemaniAfif , this is how I did it with SCSS: `margin: calc(#{$neg} * #{$navbar-top}) 0 0;`. Then at top I defined: `$navbar-top: calc(40px + 1rem);` and `$neg: -1;`. This solution compiles to the following CSS: `margin: calc(-1 * calc(40px + 1rem)) 0 0;` – Magnus May 20 '18 at 18:36
  • Final comment (@MrLister): var is unfortunately not supported by Internet Explorer, so better to stick with SCSS and calc() if IE support is needed. https://caniuse.com/#feat=css-variables. I updated the OP with my final solution, based on Temani's answer. – Magnus May 20 '18 at 19:02
  • 1
    @Magnus and with me you won't get a chance to have something working wit IE :p .. I came from the future with mordern browsers solutions and in this future IE is dead :p – Temani Afif May 20 '18 at 19:17
2

You can use an @function instead of a variable

DEMO

// The argument is defaulted to true,
// so you only need to pass an argument if you want a negative number
@function navbar-top($isPositive: true) {
    @if $isPositive { @return calc(40px + 1rem); }
    @else {@return calc((40px + 1rem) * -1); }
}

// Calling the function with the default value
.normal { margin-top: navbar-top(); }

// Calling the function with negative value
.negative { margin-top: navbar-top(false); }
Ari
  • 1,595
  • 12
  • 20
  • Thanks Ari, this almost brings us over into JS territory :) Good solution. – Magnus May 20 '18 at 18:22
  • Happy to help. You don't have to use a boolean argument, a string would be fine too. The solution can definitely be more clever, you can use a `if()` like a ternary instead of an `@if/@else`, just wanted to point out that an `@function` was a possible solution. – Ari May 21 '18 at 00:53