Sweating the Small Stuff: Recreating Subtle Design Details Using Sass
timhettler.github.io/sweating-small-stuff/
timhettler.github.io/sweating-small-stuff/
@timhettler
Open Standards Developerat R/GA in NYC
timhettler.github.io/sweating-small-stuff/
I'm going to talk about this button
for the next 40(ish) minutes
Designers created this button
in Photoshop:
Normal State | Active State |
---|---|
This is what the devs delivered:
Normal State | Active State |
---|---|
Designers:
The Drop Shadow is at a 90° angle with a distance of 1px.
Developers:
So… what is the x- and y-offset?
Designers:
The Gradient Overlay uses the multiply blend mode.
Developers:
What the &%$^ is a blend mode?
Lots of failure points in the traditional translation process
We can automate this translation
%button {
@include box-sizing('border-box');
@include user-select('none');
-webkit-touch-callout: none;
background-clip: padding-box;
border: 0;
border-radius: 0.2em;
cursor: pointer;
display: inline-block;
font: 100%/2.6 "Trade Gothic LT Std", sans-serif;
letter-spacing: -1px;
margin: 0;
padding: 0 1em;
position: relative;
text-align: center;
text-decoration: none !important;
}
.button-orange {
@extend %button;
background-color: #fa5400;
}
.button-blue {
@extend %button;
background-color: #00a4e4;
}
.button-orange, .button-blue {
…
}
.button-orange {
background-color: #fa5400;
}
.button-blue {
background-color: #00a4e4;
}
Placeholders allow you to write code that is easy to read in developlement,
without sacrificing speed & efficiency in production.
Dark Buttons | Light Buttons |
---|---|
Compass function
to determine color lightness
@function contrast-color(
$color,
$dark: $contrasted-dark-default,
$light: $contrasted-light-default,
$threshold: $contrasted-lightness-threshold
) {
@return if(lightness($color) < $threshold, $light, $dark)
}
compass-style.org/reference/compass/utilities/color/contrast/
Appropriate text-color gets set
programatically
.dark-button {
@extend %button;
@include contrasted(#983168, #333, #fff);
}
.light-button {
@extend %button;
@include contrasted(#ffd204, #333, #fff);
}
.dark-button {
…
background-color: #983168;
color: #fff;
}
.light-button {
…
background-color: #ffd204;
color: #333;
}
#DC404A | #87e300 | #00A4E4 |
---|---|---|
hsl(356, 69%, 56%) | hsl(84, 100%, 45%) | hsl(197, 100%, 45%) |
---|---|---|
@function yiq-contrast-color(
$color,
$dark: #000,
$light: #fff
) {
$red: red($color);
$green: green($color);
$blue: blue($color);
$yiq: ( ( $red * 299 ) + ( $green * 587 ) + ( $blue * 114 ) ) / 1000;
@return if($yiq >= 128, $dark, $light);
}
GITHUB.COM/TIMHETTLER/COMPASS-YIQ-COLOR-CONTRAST
111.784 | 173.614 | 122.26 |
---|---|---|
Dark Buttons Lighten
Normal State | Hover State |
---|---|
Light Buttons Darken
Normal State | Hover State |
---|---|
.dark-button {
…
@include yiq-contrasted(#fa5400);
&:hover {
background-color: adjust-color(#fa5400, $lightness: 5);
}
}
.light-button {
…
@include yiq-contrasted(#87e300);
&:hover {
background-color: adjust-color(#87e300, $lightness: -5);
}
}
.dark-button {
…
background-color: #fa5400;
color: #fff;
}
.dark-button:hover {
background-color: #ff6315;
}
.light-button {
…
background-color: #87e300;
color: #333333;
}
.light-button:hover {
background-color: #78ca00;
}
box-shadow()
Box-Shadow CSS Syntax
offset-x offset-y (blur-radius) (spread-radius) (color) (inset)
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.25);
Calculating the missing sides of our triangle: SOHCAHTOA
$x-offset: round( cos( $angle ) * $distance )
$y-offset: round( sin( $angle ) * $distance )
@function photoshop-shadow( $angle: 120, $distance: 0, $spread: 0, $size: 0, $color: #000, $inner: false ) {
$x-offset: round( cos( $angle ) * $distance );
$y-offset: round( sin( $angle ) * $distance );
$css-spread: $size * ( $spread/100 );
$blur: $size - $css-spread;
$inset: if( $inner != false, 'inset', '' );
@return ( $x-offset $y-offset $blur $css-spread $color unquote($inset) );
}
github.com/heygrady/compass-photoshop-drop-shadow
box-shadow()
to our button.button {
…
box-shadow: photoshop-drop-shadow(
90deg, 1px, 0, 1px, rgba( #000, 0.25 )
);
&:active {
box-shadow: photoshop-inner-shadow(
90deg, 1px, 0, 4px, rgba( #000, 0.35 )
);
}
}
.button {
…
box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.25);
}
.button:active {
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.35) inset;
}
text-shadow()
@function photoshop-text-shadow( $angle: 120deg, $distance: 0, $spread: 0, $size: 0, $color: #000 ) {
@if $spread > 0 {
@warn "spread has no effect for text shadows";
}
$shadow: photoshop-shadow( $angle, $distance, $spread, $size, $color );
@return ( nth( $shadow, 1 ) nth( $shadow, 2 ) nth( $shadow, 3 ) nth( $shadow, 5 ) );
}
text-shadow()
to our button.button {
…
text-shadow: photoshop-text-shadow(
-90deg, 1px, 0, 0, rgba( #000, 0.4 )
);
&:active {
text-shadow: photoshop-text-shadow(
90deg, 1px, 0, 0, rgba( #000, 0.4 )
);
}
}
.button {
…
text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.4);
}
.button:active {
text-shadow: 0px 1px 0 rgba(0, 0, 0, 0.4);
}
linear-gradient()
Linear Gradient CSS Syntax
[angle | keyword], color-stop (, color-stop)
linear-gradient( top, #000 0%, #fff 100% )
Photoshop Gradient Overlay
Angle
Photoshop:0deg
= East
CSS:0deg
= North
$css-angle: $angle - 90deg;
@function stop-scale( $stop, $scale ) {
$stop: percentage-to-decimal( $stop );
$scale: percentage-to-decimal( $scale );
@return $scale * $stop - ( 0.5 * ( $scale - 1 ) );
}
//Examples
$stop1: stop-scale( 0%, 100% ); // 0%
$stop2: stop-scale( 50%, 100% ); // 50%
$stop3: stop-scale( 100%, 100% ); // 100%
$stop1: stop-scale( 0%, 150% ); // -25%
$stop2: stop-scale( 50%, 150% ); // 50%
$stop3: stop-scale( 100%, 150% ); // 125%
$stop1: stop-scale( 0%, 50% ); // 25%
$stop2: stop-scale( 50%, 50% ); // 50%
$stop3: stop-scale( 100%, 50% ); // 75%
…
$color-stops: ( #000 0%, #fff 100%);
$gradient-scale: 100%;
$css-color-stops: ();
@for $i from 1 through length( $color-stops ) {
$color: nth( nth( $color-stops, $i ), 1 );
$stop: stop-scale( nth( nth( $color-stops, $i ), 2 ), $gradient-scale );
$color-stop: join( $color, $stop, space );
$css-color-stops: append($color-stops, $color-stop, comma);
}
@return linear-gradient($css-direction, $css-color-stops);
.simple-gradient {
@include background-image(
photoshop-gradient-overlay(
100%, 90deg, 100%, ( #000 0% , #fff 100% )
)
);
}
.simple-gradient {
background-image: linear-gradient(0deg, #000 0%, #FFF 100%);
…
}
.complex-gradient {
@include background-image(
photoshop-gradient-overlay(
75%, 0deg, 50%, ( red 0%, orange 20%, yellow 40%, green 60%, blue 80%, violet 100% )
)
);
}
.complex-gradient {
background-image: linear-gradient(30deg, rgba(255, 0, 0, 0.75) 25%, rgba(255, 165, 0, 0.75) 35%, rgba(255, 255, 0, 0.75) 45%, rgba(0, 128, 0, 0.75) 55.0%, rgba(0, 0, 255, 0.75) 65%, rgba(238, 130, 238, 0.75) 75%);
…
}
linear-gradient()
to our button.button {
…
@include background-image(
photoshop-gradient-overlay(
50%, 90deg, 100%, ( #000 0% , #fff 100% )
)
);
&:hover {
@include background-image(
photoshop-gradient-overlay(
50%, 90deg, 100%, ( #000 0% , #fff 100% )
)
);
}
}
.button {
…
linear-gradient(
0deg,
rgba(0, 0, 0, 0.5) 0%,
rgba(255, 255, 255, 0.5) 100%
);
}
.button:active {
linear-gradient(
-180deg,
rgba(0, 0, 0, 0.5) 0%,
rgba(255, 255, 255, 0.5) 100%
);
}
@function blend--normal($bg, $fg) {
@return $fg;
}
@function blend--multiply($bg, $fg) {
@return $fg * $bg / 255;
}
@function blend--overlay($bg, $fg) {
@if ($bg < 128) {
@return 2 * $fg * $bg / 255;
} @else {
@return 255 - 2 * (255 - $fg) * (255 - $bg) / 255);
}
}
SASS Blend Mode Limitations
Must know foreground- and background-color
photoshop-gradient-overlay()
…
$background-color: #fa5400;
@for $i from 1 through length($color-stops) {
$blended-color: photoshop-blend('overlay', $background-color, nth( nth( $color-stops, $i ), 1 ), $opacity);
$stop: join( $blended-color, stop-scale( nth( nth( $color-stops, $i ), 2 ), $scale ), space );
$css-color-stops: append($css-color-stops, $stop, comma);
}
@return linear-gradient($css-angle, $css-color-stops);
github.com/timhettler/compass-photoshop-gradient-overlay
.button {
…
linear-gradient(
0deg, rgba(245, 0, 0, 0.5) 0%, rgba(255, 168, 0, 0.5) 100%
);
}
.button:active {
…
linear-gradient(
-180deg, rgba(245, 0, 0, 0.5) 0%, rgba(255, 168, 0, 0.5) 100%
);
}
.button {
@extend %button;
@include background-image(photoshop-gradient-overlay(#fa5400, 'overlay', 50%, 90deg));
@include yiq-contrasted(#fa5400);
box-shadow: photoshop-drop-shadow(90deg, 1px, 0, 1px, rgba(#000, 0.25));
text-shadow: photoshop-text-shadow(-90deg, 1px, 0, 0, rgba(yiq-contrast-color(#fa5400, #fff, #000), 0.4));
&:hover {
@if (yiq-contrast-color($background-color, #000, #fff) == #fff) {
@include background-image(photoshop-gradient-overlay(lighten(#fa5400, 5), 'overlay', 50%));
background-color: adjust-color(#fa5400, $lightness: 5);
} @else {
@include background-image(photoshop-gradient-overlay(darken(#fa5400, 5), 'overlay', 50%));
background-color: adjust-color(#fa5400, $lightness: -5);
}
}
&:active {
@include background-image(photoshop-gradient-overlay(#fa5400, 'overlay', 50%, -90deg));
box-shadow: photoshop-inner-shadow(90deg, 1px, 0, 4px, rgba(#000, 0.35));
text-shadow: photoshop-text-shadow(90deg, 1px, 0, 0, rgba(yiq-contrast-color(#fa5400, #fff, #000), 0.4));
}
}
@mixin button($background-color) {
$color-type: if(yiq-contrast-color($background-color, #000, #fff) == #000, "light", "dark");
$shadow-color: yiq-contrast-color($background-color, #fff, #000);
@extend %button;
@include background-image(photoshop-gradient-overlay($background-color, 'overlay', 50%, 90deg));
@include yiq-contrasted($background-color);
box-shadow: photoshop-drop-shadow(90deg, 1px, 0, 1px, rgba(#000, 0.25));
text-shadow: photoshop-text-shadow(-90deg, 1px, 0, 0, rgba($shadow-color, 0.4));
&:hover {
$color-adjust: if($color-type == "dark", 5, -5);
$hover-color: adjust-color($background-color, $lightness: $color-adjust);
@include background-image(photoshop-gradient-overlay($hover-color, 'overlay', 50%));
background-color: $hover-color;
}
&:active {
@include background-image(photoshop-gradient-overlay($background-color, 'overlay', 50%, -90deg));
box-shadow: photoshop-inner-shadow(90deg, 1px, 0, 4px, rgba(#000, 0.35));
text-shadow: photoshop-text-shadow(90deg, 1px, 0, 0, rgba($shadow-color, 0.4));
}
}
.button--orange {
@include button(#fa5400);
}
.button--hotpink {
@include button(hotpink);
}
.button--bada55 {
@include button(#bada55);
}
timhettler.github.io/sweating-small-stuff