About Sass conflicts and relatively new CSS features

More recently, there have been many interesting features in CSS, such as CSS Variables and new features . While all of this can make life a lot easier for web designers, these features can interact with CSS preprocessors like Sass in unexpected ways. The author of the material, the translation of which we publish today, will talk about what problems she had to face, how she dealt with them, and why she believes that Sass is still impossible to do without.











Errors



If you are experimenting with CSS-features min()and max()then using different units of measurement, we could be faced with error messages like this: Incompatible units: vh and em.





Error message when using different units in min () and max () functions



This message is displayed because Sass has its own functionmin(). The CSS function ismin()ignored as a result. In addition, Sass cannot perform calculations using units that do not have a clear relationship between them.



For example, the relationship between units iscmwellindefined, so Sass can find the result of a functionmin(20in, 50cm)and won't throw an error if you use something like that in your code.



The same thing happens with other units of measurement. For example, all corner units are interconnected:1turn,1rador1gradare always converted to the same values ​​expressed in units deg. The same is true, for example, in the case when 1sit is always equal 1000ms. 1kHzalways equal 1000Hz, 1dppxalways equal 96dpi, 1inalways equal 96px. This is why Sass can convert values ​​expressed in these units to each other, and mix them in calculations used in various functions, such as its own function min().



But everything goes wrong when there is no clear relationship between the units of measurement (as, for example, above, y emand vh).



And this happens not only when using values ​​expressed in different units of measurement. Trying to use a function calc()internallymin()also results in an error. If you try to min(), put something like calc(20em + 7px), it displays this error: calc(20em + 7px) is not a number for min.





Error message occurs when you try to use calc () in min ()



Another problem occurs in a situation when trying to use the CSS-variable or the output of CSS-mathematical functions (such ascalc(),min(),max()) in CSS-like filtersinvert().



Here is the error message that you might see under similar circumstances:$color: 'var(--p, 0.85) is not a color for invert





Using var () in the filter: invert () results in an error



The same thing happens withgrayscale():$color: ‘calc(.2 + var(--d, .3))‘ is not a color for grayscale.





Using the calc () in the filter: grayscale () results in an error



designfilter: opacity()is also subject to similar issues:$color: ‘var(--p, 0.8)‘ is not a color for opacity.





Using var () in the filter: opacity () results in an error



, but other functions usedfilter, includingsepia(),blur(),drop-shadow(),brightness(),contrast()andhue-rotate(), working with the CSS-variables is perfectly normal!



It turned out that the cause of this problem is similar to the one that affects the functionsmin()andmax(). The Sass is no built-in functionssepia(),blur(),drop-shadow(),brightness(),contrast(),hue-rotate(). But it has its own functions grayscale () , invert () and opacity () . The first argument to these functions is the value$color. The error appears due to the fact that when using problematic constructions, Sass does not find such an argument.



For the same reason, problems arise when using CSS variables that represent at least two hsl()or hsla()-values.





Error when using var () in color: hsl ()



On the other hand, without using Sass, a constructcolor: hsl(9, var(--sl, 95%, 65%))is perfectly correct and perfectly working CSS.



The same is true for functions such asrgb()andrgba():





Error using var () in color: rgba ()



Also, if you import Compass and try to use a CSS variable insidelinear-gradient()orradial-gradient(), you might encounter another error. But, at the same time,conic-gradient()you can use variables without any problems (of course, if the browser supports this function).





Error using var () in background: linear-gradient ()



The reason for the problem lies in the fact that Compass has its own linear-gradient () andradial-gradient()functions, but the functionconic-gradient()was never there.



In general, all these problems stem from the fact that Sass and Compass have their own functions, whose names are the same as those in CSS. Both Sass and Compass, when meeting these functions, believe that we are going to use their own implementations of these functions, and not the standard ones.



Here is an ambush!



Solution



This problem can be solved by remembering that Sass is case sensitive, but CSS is not.



This means that you can write something like this Min(20em, 50vh)and Sass doesn't recognize its own function in that construct min(). No errors will be generated. This construct will be well-formed CSS that works exactly as expected. Similarly, to get rid of problems with other functions can be, non-standard way by writing their names: HSL(), HSLA(), RGB(), RGBA(), Invert().



When it comes to gradients, I usually use this shape: linear-Gradient()and radial-Gradient(). I do this because this notation is close to the names used in SVG, but any other similar name that includes at least one capital letter will work in this situation.



Why all these complications?



Almost every time I tweet something about Sass, I get lectured about how now that you have CSS variables, you don't need to use Sass anymore. I decided that I should answer this and explain the reason for my disagreement with this idea.



First off, I'll note that I find CSS Variables extremely useful, and that I've used them for a variety of tasks over the past three years. But I suppose you need to remember that using them has a performance impact. And the search for a problem in the maze of callscalc()can be the most unpleasant experience. Standard browser developer tools aren't very good at this yet. I try not to get carried away with the use of CSS variables, so as not to get into situations in which their disadvantages show themselves more than their advantages.





It is not easy to understand what the results of evaluating these calc () expressions will be.In



general, if a variable is used as a constant, it does not change from element to element, or from state to state (and in such cases, CSS variables should definitely use needed ), or if the variable doesn't reduce the amount of compiled CSS (solving the repetition problem created by prefixes), then I'll use a Sass variable.



Second, variable support has always been a pretty minor reason among the reasons I use Sass. When I started using Sass in the second half of 2012, I did it mainly for loops. For a feature that CSS still lacks. While I have moved some of the looping logic into the HTML preprocessor (since this reduces the amount of generated code and avoids the need to modify both HTML and CSS), I still use Sass loops in a lot of cases. These include generating lists of values, creating values ​​for adjusting gradients, creating lists of points when working with a function polygon(), creating lists of transformations, and so on.



Below is an example of what I would have done earlier when creating some HTML elements using the preprocessor. Which preprocessor it is doesn't really matter, but I chose Pug:



- let n = 12;

while n--
  .item


Then I would create a variable $nin Sass (and this variable should have the same value as in HTML) and start a loop using it, in which I would generate the transformations used to position each of the elements:



$n: 12;
$ba: 360deg/$n;
$d: 2em;

.item {
  position: absolute;
  top: 50%; left: 50%;
  margin: -.5*$d;
  width: $d; height: $d;
  /*    */

  @for $i from 0 to $n {
    &:nth-child(#{$i + 1}) {
      transform: rotate($i*$ba) translate(2*$d) rotate(-$i*$ba);
      &::before { content: '#{$i}' }
    }
  }
}


The downside to this is that I would have to change the values ​​in both the Pug code and the Sass code if the number of elements changed. In addition, there is a lot of repetition in the code.





CSS code generated from the above code



Now I took a different approach. Namely, using Pug, I generate indexes as custom properties and then use them in the declarationtransform.



Here's the code that Pug is planning to do:



- let n = 12;

body(style=`--n: ${n}`)
  - for(let i = 0; i < n; i++)
    .item(style=`--i: ${i}`)


Here's the Sass code:



$d: 2em;

.item {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -.5*$d;
  width: $d;
  height: $d;
  /*    */
  --az: calc(var(--i)*1turn/var(--n));
  transform: rotate(var(--az)) translate(2*$d) rotate(calc(-1*var(--az)));
  counter-reset: i var(--i);

  &::before { content: counter(i) }
}


You can experiment with this code here.





Elements Generated and Styled Using Loops



Using this approach has significantly reduced the amount of automatically generated CSS.





CSS generated from the above code



But if you want to create something like a rainbow, you can't do without Sass loops.



@function get-rainbow($n: 12, $sat: 90%, $lum: 65%) {
  $unit: 360/$n;
  $s-list: ();

  @for $i from 0 through $n {
    $s-list: $s-list, hsl($i*$unit, $sat, $lum)
  }

  @return $s-list
}

html { background: linear-gradient(90deg, get-rainbow()) }


Here's a working version of this example.





Multi-colored background



Of course, this can be generated using Pug variables, but this approach has no advantage over the dynamic nature of CSS variables, and it will not reduce the amount of code transmitted to the browser. As a result, it makes no sense for me to give up what I'm used to.



I use Sass's (and Compass's) built-in math functions a lot, such as trigonometric functions. These days, these features are part of the CSS specification , but not all browsers support them yet. Sass doesn't have these functions, but Compass does, which is why I often use Compass.



And of course I can write my own functions of this kind in Sass. I did this in the very beginning, before Compass had support for inverse trigonometric functions. I really need these functions, so I wrote them myself using Taylor series . But these days, these features are in Compass. They are better and more productive than those that I wrote myself.



Math functions are very important to me because I am a programmer, not an artist. The values ​​in my CSS are usually generated from mathematical calculations. These are not some "magic numbers", or something that plays a purely aesthetic role. An example of their use is generating a list of regular or quasi-regular polygons forclip-path... This is used, for example, when creating something like avatars or stickers, the shape of which is different from rectangular.



Consider a regular polygon whose vertices lie on a circle. Dragging the slider in the following example, which we can experiment with here , allows us to see where the points are placed for shapes with different numbers of vertices.





A shape with three vertices



This is what the corresponding Sass code looks like:



@mixin reg-poly($n: 3) {
  $ba: 360deg/$n; //  
  $p: (); //   ,  

  @for $i from 0 to $n {
    $ca: $i*$ba; //  
    $x: 50%*(1 + cos($ca)); //  x  
    $y: 50%*(1 + sin($ca)); //  y  
    $p: $p, $x $y //       
  }

  clip-path: polygon($p) //       clip-path 
}


Please note that we are using loops and other constructs here, which are very inconvenient to use with pure CSS.



A slightly more advanced version of this example might involve rotating the polygon by adding the same offset ( $oa) to the corner corresponding to each vertex. This can be seen in the following example . Stars are generated here, which are arranged in a similar way, but always have an even number of vertices. Moreover, each vertex with an odd index is located on a circle whose radius is less than the main circle ( $f*50%).





Star



You can make such interesting stars.





Stars



You can create stickers with borders (border) created using unusual templates. In this example, the sticker is created from a single HTML element, and the template used for customizationborderis created usingclip-pathSass loops and math. In fact, there are a lot of calculations here.





Stickers with Complex Borders



Another example is how to create a background for cards. Here, in a loop, using the modulus operator and exponential functions, a background is created with an imitation of the dithering effect.





Dithering Effect



Here, too, CSS Variables are heavily used.



Next, you can think of using mixins to avoid having to write the same declaration over and over again when styling something like sliders . Different browsers use different pseudo-elements to style the internal components of such controls, so you need to define styles for each component that control their appearance using different pseudo-elements.



Unfortunately, in CSS, as tempting as it sounds, you can't put something like the following code:



input::-webkit-slider-runnable-trackinput::-moz-range-trackinput::-ms-track { /*   */ }


It won't work. This entire set of rules is ignored if at least one selector is not recognized. And, since no browser knows about the existence of all three selectors in this example, these styles will not be applied in any browser.



If you want the styling to work, you will need to do something like this:



input::-webkit-slider-runnable-track { /*   */ }
input::-moz-range-track { /*   */ }
input::-ms-track { /*   */ }


But this can lead to the fact that the same styles appear in the code three times. And if you need, say, to change a trackproperty background, this will mean that you have to edit the styles in ::-webkit-slider-runnable-track, in ::-moz-range-trackand in ::-ms-track.



The only sane solution to this problem is to use mixins. Styles are repeated in the compiled code, as we cannot do without it, but now we, at least, do not have to enter the same code three times in the editor.



@mixin track() { /*   */ }

input {
  &::-webkit-slider-runnable-track { @include track }
  &::-moz-range-track { @include track }
  &::-ms-track { @include track }
}


Outcome



The main conclusion that I can draw is this: Sass in is something we can't do without yet.



Do you use Sass?






All Articles