Skip to content

Instantly share code, notes, and snippets.

@nickpiesco
Last active March 21, 2017 22:23
Show Gist options
  • Save nickpiesco/4731166 to your computer and use it in GitHub Desktop.
Save nickpiesco/4731166 to your computer and use it in GitHub Desktop.
Making Sass Linear Gradient Mixins Behave in IE

I wrote this fairly straightforward cross-browser linear gradient mixin:

@mixin gradient($from-color, $to-color) {
	background-color: mix($from-color, $to-color); /* Fallback */
	background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from($from-color), to($to-color));
	background-image: -webkit-linear-gradient(top, $from-color, $to-color); 
	background-image:    -moz-linear-gradient(top, $from-color, $to-color);
	background-image:     -ms-linear-gradient(top, $from-color, $to-color);
	background-image:      -o-linear-gradient(top, $from-color, $to-color);
	-ms-filter:	"progid:DXImageTransform.Microsoft.gradient(startColorStr='$from-color', EndColorStr='$to-color')";
}

At first blush, this should work, right? However, and in retrospect I shouldn't have been surprised by this at all, it was doing some seriously weird stuff in IE. I checked the compiled CSS; and Sass skipped parsing the variables in the -ms-filter attribute, passing the variable names through like nothing ever happened. (Apparently if -ms-filter doesn't get starting and ending colour values it understands, it picks blue and black. And now you know.)

I poked around some and came across this blog post from Josh Rubinstein, who was having the same problem. It turns out that -ms-filter, being a proprietary extension, doesn't get recognised by Sass – when it comes along, Sass just throws its virtual hands up and passes the name of the variable.

So what to do about it then? Sass's interpolation syntax to the rescue! What you need to do is wrap your variables like so: #{$from-color}. That way, Sass will parse your colour values, and all our browsers will live happily ever after. Here's the fully-functional mixin:

@mixin gradient($from-color, $to-color) {
	background-color: mix($from-color, $to-color); /* Fallback */
	background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from($from-color), to($to-color));
	background-image: -webkit-linear-gradient(top, $from-color, $to-color); 
	background-image:    -moz-linear-gradient(top, $from-color, $to-color);
	background-image:     -ms-linear-gradient(top, $from-color, $to-color);
	background-image:      -o-linear-gradient(top, $from-color, $to-color);
	-ms-filter:	"progid:DXImageTransform.Microsoft.gradient(startColorStr='#{$from-color}', EndColorStr='#{$to-color}')";
}

Share and enjoy!

@edwardbeckett
Copy link

Great @mixin man... Thanks

@andersekdahl
Copy link

A word of warning: If you use colors like #FFFFFF in a color variable, sass will shorten that to #FFF when minifying it. And IEs filter rule doesn't like short-hand hex colors. Make sure to quote the color variable as a string instead. That is, do this:

$white: "#ffffff";

Instead of:

$white: #ffffff;

http://sass-lang.com/documentation/file.SASS_REFERENCE.html#colors

@kamescg
Copy link

kamescg commented Sep 18, 2015

I add some functions and map functionality, so you can add multiple gradient colors.

// @include gradient(gradient--concat($gradient--concat));
@mixin gradient($gradient-degrees, $gradient--concat) {
    $ie_variables: gradient--ie($gradient--concat);
    $start-color: map-get($ie_variables, 'start');
    $finish-color: map-get($ie_variables, 'finish');

  background-color: mix($start-color, $finish-color); /* Fallback */
  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from($start-color), to($finish-color));
  background-image: -webkit-linear-gradient($gradient-degrees, gradient--concat($gradient--concat)); 
  background-image:    -moz-linear-gradient($gradient-degrees, gradient--concat($gradient--concat));
  background-image:     -ms-linear-gradient($gradient-degrees, gradient--concat($gradient--concat));
  background-image:      -o-linear-gradient($gradient-degrees, gradient--concat($gradient--concat));
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#{$start-color}', EndColorStr='#{$finish-color}')";
}


// Concat :: Gradient Mixin
// Concatinate the gradient map properties and values into structured CSS
@function gradient--concat($gradient--concat){
    $return-new: '';

    @each $position, $color in $gradient--concat {
       $return: $color + ' ' $position + gradient--position($position);
       $return-new: $return-new + '' + $return;
    }

    @return unquote($return-new);
}

// Position :: Gradient Mixin
// Add commas to the end of every property concationation, except the last one. 
@function gradient--position($position){
    @if ( $position != 100%) {
        @return ",";
    }
    @else {
        @return "";
    }
}

// Internet Explore Fallback
// Extract the first and last color variables for "-ms-filter"
@function gradient--ie($gradient--concat){
    $start_map: '';
    $end_map: '';

    @each $position, $color in $gradient--concat {
        @if ( $position == 0%) {
            $start_map: (
                'start': $color
                )
        }
        @if ( $position == 100%) {
            $end_map: (
                'finish': $color
                )
        }   
    }

    @return map-merge($start_map, $end_map)

}

$gradient-primary-map: (
0%: #1777c1,
50%: #6d34bc,
100%: #8259e0,
);

.bg--color--primary {
@include gradient(135deg, $gradient-primary-map);
}

@fyrebase
Copy link

@kamescg you are missing a + in a string concat within the gradient--concat function.

// Concat :: Gradient Mixin
// Concatinate the gradient map properties and values into structured CSS
@function gradient--concat($gradient--concat){
    $return-new: '';

    @each $position, $color in $gradient--concat {
        $return: $color + ' ' + $position + gradient--position($position);
        $return-new: $return-new + '' + $return;
    }

    @return unquote($return-new);
}

@yanickrochon
Copy link

yanickrochon commented Jun 10, 2016

I found this today, and modified it so it works with filter :

/// Mixin printing a linear-gradient
/// as well as a plain color fallback
/// and the `-webkit-` prefixed declaration
/// @access public
/// @param {Keyword | Angle} $direction - Linear gradient direction
/// @param {Arglist} $color-stops - List of color-stops composing the gradient
@mixin linear-gradient($direction, $color-stops...) {
  // Direction has been omitted and happens to be a color-stop
  @if is-direction($direction) == false {
    $color-stops: $direction, $color-stops;
    $direction: 180deg;
  }

  background: nth(nth($color-stops, 1), 1);
  background: -webkit-linear-gradient(legacy-direction($direction), $color-stops);
  background: linear-gradient($direction, $color-stops);
  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#{nth(nth($color-stops, 1), 1)}', endColorstr='#{nth(nth($color-stops, length($color-stops)), 1)}', GradientType=0 ); /* IE6-9 */
}

/// Test if `$value` is a valid direction
/// @param {*} $value - Value to test
/// @return {Bool}
@function is-direction($value) {
  $is-keyword: index((to top, to top right, to right top, to right, to bottom right, to right bottom, to bottom, to bottom left, to left bottom, to left, to left top, to top left), $value);
  $is-angle: type-of($value) == 'number' and index('deg' 'grad' 'turn' 'rad', unit($value));

  @return $is-keyword or $is-angle;
}

/// Convert a direction to legacy syntax
/// @param {Keyword | Angle} $value - Value to convert
/// @require {function} is-direction
/// @require {function} convert-angle
/// @throw Cannot convert `#{$value}` to legacy syntax because it doesn't seem to be a direction.;
@function legacy-direction($value) {
  @if is-direction($value) == false {
    @error "Cannot convert `#{$value}` to legacy syntax because it doesn't seem to be a direction.";
  }

  $conversion-map: (
    to top          : bottom,
    to top right    : bottom left,
    to right top    : left bottom,
    to right        : left,
    to bottom right : top left,
    to right bottom : left top,
    to bottom       : top,
    to bottom left  : top right,
    to left bottom  : right top,
    to left         : right,
    to left top     : right bottom,
    to top left     : bottom right
  );

  @if map-has-key($conversion-map, $value) {
    @return map-get($conversion-map, $value);
  }

  @return 90deg - $value;
}

@eriku
Copy link

eriku commented Dec 9, 2016

@andersekdahl has a good point but you should probably use SASS's ie-hex-str() function.

-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#{ie-hex-str($from-color)}', EndColorStr='#{ie-hex-str($to-color)}')";

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