Modern Frontend Development with Sass & Compass
Introduction
tweet: @maddesigns #t3dd12 #sass
Sven Wolfermann | maddesigns
Introduction
tweet: @maddesigns #t3dd12 #sass
Sven Wolfermann | maddesigns
CSS3 ist not enough…
No PHP Knowledge nessessary*
No TYPO3 inside (this is the last time you see TYPO3 in this presentation
*can contain tracks of Ruby
Sass means syntactic awesome style sheets
preprocessor
Similar preprocessors: LESS, Stylus (needs JS, i.e. node.js as server
In order to install and run Sass, you need to have Ruby installed on your system.
Easy! Ruby is built in :)
if not installed, use the package manager
$ sudo apt-get install ruby1.9.1-full
$ sudo gem install sass
install beta version (3.2.):
$ sudo gem install sass --pre
already installed Sass?
check with
$ sass --version
switch to the sass folder
create styles.scss
Sass has two syntaxes.
The new main syntax is known as “SCSS” (for “Sassy CSS”).
SCSS files use the extension .scss
.
The second, older syntax is known as the indented syntax (or just “Sass”).
Instead of brackets and semicolons,
it uses the indentation of lines to specify blocks.
Files in the indented syntax use the extension .sass
.
section { margin: 1em 0; header { background-color: lightpink; } }
section margin: 1em 0 header background-color: lightpink
SCSS is easier for beginners, Sass is more strict and clean
wrap the HTML elements in the H5BP template
<div class="container"> <header>header
</header> <div role="main" class="content"> </div> <footer>footer
</footer> </div>
crating first styles in style.scss
html, body { height: 100%; } .container { min-height: 100%; max-width: 1200px; width: auto; margin: 0 auto; } h1 { border-bottom: 2px solid #69A550; // green color: #FF8700; // orange margin: 0 0 0.5em; }
Uuh... looks like CSS, isn't it?
open terminal
watch folder
$ sass --watch sass:css
watch file
$ sass --watch sass/style.scss:css/style.css
compiled to /css/style.css
html, body { height: 100%; } .container { min-height: 100%; max-width: 1200px; width: auto; margin: 0 auto; } h1 { border-bottom: 2px solid #69A550; color: #FF8700; margin: 0 0 0.5em; }
h1 { border-bottom: 2px solid #69A550; // green color: #FF8700; // orange margin: 0 0 0.5em; }
change to global variables
$color-main: #69A550; $color-alt: #FF8700; h1 { border-bottom: 2px solid $color-main; color: $color-alt; margin: 0 0 0.5em; }
variables can be colors, sizes, percentage, ...
$page_max_width: 1200px; $padding: 20px;
.container { min-height: 100%; max-width: $page_max_width; width: auto; margin: 0 auto; padding: 0 $padding; }
Variables can be include dashes or underscores
they're compatible to each other and are the same!
SCSS
$variable-name: value; .foo { property: $variable_name; }
$variable-name
and $variable_name
both refer to the
same variable
SCSS
.container { max-width: $page_max_width - $padding * 2; padding: 0 $padding; ... }
CSS
.container { max-width: 1160px; /* 1200px - 20px * 2 */ padding: 0 20px; ... }
doesn't work in Sass 3.1 – fixed in Sass 3.2
$break-small: 320px; $break-large: 1200px; .profile-pic { float: left; width: 250px; @media screen and (max-width: $break-small) { width: 100px; float: none; } @media screen and (min-width: $break-large) { float: right; } }
$break-small: 320px; $break-large: 1024px; @mixin respond-to($media) { @if $media == handhelds { @media only screen and (max-width: $break-small) { @content; } } @else if $media == medium-screens { @media only screen and (min-width: $break-small + 1) and (max-width: $break-large - 1) { @content; } } @else if $media == wide-screens { @media only screen and (min-width: $break-large) { @content; } } }
.profile-pic { float: left; width: 250px; @include respond-to(handhelds) { width: 100% ;} @include respond-to(medium-screens) { width: 125px; } @include respond-to(wide-screens) { float: none; } }
.profile-pic { float: left; width: 250px; } @media only screen and (max-width: 320px) { .profile-pic { width: 100%; } } @media only screen and (min-width: 321px) and (max-width: 1023px) { .profile-pic { width: 125px; } } @media only screen and (min-width: 1024px) { .profile-pic { float: none; } }
$width: 10px; $double_width: $width * 2; /* 20px */ $half_width: $width / 2; /* 5px */ $width_plus_2: $width + 2; /* 12px */ $width_minus_2: $width - 2; /* 8px */
spaces are important
font: 18px / 1.45em; // 18px/1.45em font: (20px / 5); // 4px font: 20px / 5 + 1; // 5px font: $base / 5; // 4px $size: 20px / 5; // 4px
in the end you come to such solutions
h1, .h1 { font: px2em($base-fontsize * 2) / #{$base-lineheight} $font-main; }* more will be explained later
writing long selectors is time consuming
short selectors are better in general
CSS
nav {float: right;} nav li {float: left;} nav li a {color: #666;} nav li a:hover {color: #333;} nav li.current {font-weight: bold;}
SCSS
nav { float: right; li { float: left; a { color: #666; &:hover { color: #333; } } &.current { font-weight: bold; } } }
identation with SCSS makes no difference in CSS output
SCSS
nav {float: right; li {float: left; a {color: #666; &:hover { color: #333;} } &.current { font-weight: bold;} }}but sure it looks better if intended
CSS rules aren't the only things that can be nested in Sass. Properties can, too.
.foo { border: { style: solid; width: 1px 2px 3px 4px; color: red green blue black; } }
Sass nesting != HTML nesting
be careful with nesting!
you can run into performance issues with long selectors
div.container { div.content { div.articles { & > div.post { div.title { h1 { a { } } } div.content { ul { li { ... } } } } } } }
div.content div.container { ... } div.content div.container div.articles { ... } div.content div.container div.articles > div.post { ... } div.content div.container div.articles > div.post div.title { ... } div.content div.container div.articles > div.post div.title h1 { ... } div.content div.container div.articles > div.post div.title h1 a { ... } div.content div.container div.articles > div.post div.content { ... } div.content div.container div.articles > div.post div.content ul { ... } div.content div.container div.articles > div.post div.content ul li { ... }
wait, did I see such bad nesting ever?
DIV.csc-textpic DIV.csc-textpic-imagewrap DL.csc-textpic-image { float: left; } DIV.csc-textpic DIV.csc-textpic-imagewrap DL.csc-textpic-image DT { float: none; } DIV.csc-textpic DIV.csc-textpic-imagewrap DL.csc-textpic-image DD { float: none; } DIV.csc-textpic DIV.csc-textpic-imagewrap DL.csc-textpic-image DD IMG { border: none; }
SCSS
div { color: black .foo { color: black } // descendant combinator + .foo { color: black } // adjacent sibling combinator > .foo { color: black } // child combinator ~ .foo { color: black } // general sibling combinator & .foo { color: black } // Sass' parent selector &.bar { color: black } &:hover { color: black } }
CSS
div { color: black; } div .foo { color: black; } div + .foo { color: black; } div > .foo { color: black; } div ~ .foo { color: black; } div .foo { color: black; } div.bar { color: black; } div:hover { color: black; }
the & (ampersand) has a placeholder function for the parental selector
div { .foo { color: black } & .foo { color: black } }
are compiled to the same
div .foo { color: black }
div { &.foo { color: black } }
div.foo { color: black; }
div { &:hover { color: black } }
div:hover { color: black; }
div { background-color: rgba(#000, 0.8); // Sass feature for Hex to RGB colors .no-rgba & { background-color: black; } }
div { background-color: rgba(0,0,0, 0.8); } .no-rgba div { background-color: black; }
div { .parent & .child { color: black } }
.parent div .child { color: black; }
SCSS
aside { width: 25%; .index & { width: 30%; } }
aside { width: 25% } .index aside { width: 30% }
@media rules in place
aside { width: 25%; @media screen and (max-width: 480px) { width: 100%; } }
aside { width: 25%; } @media screen and (max-width: 480px) { aside { width: 100%; } }
/* Hey look at this multiline comment * that we want to show up in our CSS output. */ .container { color: black; } // These comments are single lines and // we do not want them to appear in our CSS footer { color: #336699; }
This compiles to:
/* Hey look at this multiline comment * that we want to show up in our CSS output. */ .container { color: black; } footer { color: #336699; }
@extend clones the attributes from rules and adds them to another rule.
.button { background-color: $color-main; font-weight: bold; color: white; padding: 5px; }
Then we can @extend the class to another
.button-checkout { @extend .button; background-color: darken($color-main, 20%); }
.button-checkout { @extend .button; background-color: darken($color-main, 20%); .msg & { @extend .button; background-color: darken($color-main, 30%); } }
.button, .button-checkout, .msg .button-checkout { background-color: blue; font-weight: bold; color: white; padding: 5px; } .button-checkout { background-color: #000099; } .msg .button-checkout { background-color: #000066; }
be careful with extending
try to extend only single rules
.my-layout { margin: 1px; h1 { font-weight: bold; font-size: 2em; } h2 { @extend h1; font-size: 1.5em; } } .home-page { @extend .my-layout; } .about-page { @extend .my-layout; } .login-page { @extend .my-layout; } .register-page { @extend .my-layout; }
.my-layout h1, .home-page h1, .about-page h1, .login-page h1, .register-page h1, .my-layout h2, .home-page .my-layout h2, .my-layout .home-page h2, .about-page .my-layout h2, .mylayout .about-page h2, .login-page .my-layout h2, .mylayout .login-page h2, .register-page .my-layout h2, .mylayout .register-page h2, .my-layout .home-page h2, .homepage .my-layout h2, .home-page h2, .about-page .home-page h2, .home-page .about-page h2, .login-page .home-page h2, .homepage .login-page h2, .register-page .home-page h2, .homepage .register-page h2, .my-layout .about-page h2, .aboutpage .my-layout h2, .home-page .about-page h2, .about-page .homepage h2, .about-page h2, .login-page .about-page h2, .aboutpage .login-page h2, .register-page .about-page h2, .aboutpage .register-page h2, .my-layout .login-page h2, .loginpage .my-layout h2, .home-page .login-page h2, .login-page .homepage h2, .about-page .login-page h2, .login-page .about-page h2, .login-page h2, .register-page .login-page h2, .loginpage .register-page h2, .my-layout .register-page h2, .registerpage .my-layout h2, .home-page .register-page h2, .registerpage .home-page h2, .about-page .register-page h2, .registerpage .about-page h2, .login-page .register-page h2, .registerpage .login-page h2, .register-page h2 { font-weight: bold; font-size: 2em; }
Redundant selectors were also sometimes created by nested selectors using @extend. That redundancy has been eliminated as well.
Man, tell me the cool things!
Are code snippets (reusable elements)
Parameterizable (use reasonable defaults)
@mixin border-radius($value) { -webkit-border-radius: $value; -moz-border-radius: $value; border-radius: $value; } .box { color: $color-main; font-family: $helvetica-font-stack; @include border-radius(5px); }
compiled to
.box { color: blue; font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
but thats a bad example – no need for the vendor prefixes of border-radius
only use border-radius: 5px;
in your stylesheets
Defaults and named arguments
@mixin link-colors($text:blue, $hover:red) { color: $text; &:hover { color: $hover; } } a { @include link-colors($hover: green); }
a { color: blue; } a:hover { green; }
SCSS
@mixin my-btn($color) { color: $color; } @include my-btn(red);
Sass
=my-btn($color) color: $color +my-btn(red)
the way in CSS
/* style.css */ @import "base.css"; @import url("styles.css"); @import url("druck.css") print;
Importing CSS files into one file can cause performance issues
Limit your external references in your HTML
split your stylesheet in many chunks and use the import function of Sass
@import "modules/base"; @import "partials/header", "partials/footer";
create subfolders and devide into partials
use underscore in your filenames to concatinate the partials within the compiling process
Imagine this structure
style.sass modules ┗ _normalize.sass ┗ _base.sass ┗ _mixins.sass partials ┗ _footer.sass ┗ _header.sass ┗ ie.sass ┗ print.sass
none underscore files will be compiled into seperate CSS files
# style.sass @import modules/normalize @import modules/base @import modules/mixins @import partials/header @import partials/footer @import partials/ie @import partials/print
/css ┗ style.css ┗ ie.css ┗ print.css
maybe one day you will come to such a structure...
// Libraries // Layout @import compass @import public/layout @import public/config @import public/lightbox @import public/reset @import public/images @import shared/avantgarde @import public/mixins // Forms @import public/sprites @import public/form_styles @import shared/payment_types @import public/form_layout @import public/messaging // Typography @import public/type_styles @import public/type_layout @import public/links @import public/pagination
Unlike plain CSS, Sass allows @imports to appear inside CSS rules.
So if one file, named _foo.scss, contained this:
.foo {foo: bar}
And then another imported it, like so:
.bar {@import foo} // same as .bar { .foo {foo: bar} }
And because Sass is compiled to CSS, you can import CSS files in your Sass files too
@import foo.css @import lightbox.css
# Convert CSS to SCSS $ sass-convert style.css style.scss # Convert CSS to Sass $ sass-convert style.css style.sass # Convert SCSS to Sass $ sass-convert style.scss style.sass
Relational operators (<, >, <=, >=) evaluate numbers
1 < 20 // true 10 <= 20 // true 4 > 1 // true 4 >= 1 // true
Comparison operators (==, !=) evaluate all data types
1 + 1 == 2 // true small != big // true #000 == black // true
RGB Functions
rgb($red, $green, $blue) rgba($red, $green, $blue, $alpha) rgba($color, $alpha) red($color) green($color) blue($color) mix($color-1, $color-2, [$weight])
HSL Functions
hsl($hue, $saturation, $lightness) hsla($hue, $saturation, $lightness, $alpha) hue($color) saturation($color) lightness($color) adjust-hue($color, $degrees) lighten($color, $amount) darken($color, $amount) saturate($color, $amount) desaturate($color, $amount) grayscale($color) complement($color) invert($color)
Opacity Functions
alpha($color) / opacity($color) rgba($color, $alpha) opacify($color, $amount) / fade-in($color, $amount) transparentize($color, $amount) / fade-out($color, $amount)
Other color functions
adjust-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha]) scale-color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha]) change-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])
percentage(13/25) // 52% round(2.4) // 2 ceil(2.2) // 3 floor(2.6) // 2 abs(-24) // 24
$main-bg: #000; .main { color: if($main-bg == black, #fff, #000); }
@if
@for
@each
@while
$theme: ocean; div { @if $theme == dusty { background: #c6bba9; color: $color; } @else if $theme == ocean { background: blue; color: white; } }
@for $level from 0 to 5 { .tag-#{$level + 1} { font-size: .7em + ($level * .5em); } }
.tag-1 { font-size: 0.7em; } .tag-2 { font-size: 1.2em; } .tag-3 { font-size: 1.7em; } .tag-4 { font-size: 2.2em; } .tag-5 { font-size: 2.7em; }
@each $section in home, about, archive, projects { nav { .#{$section} & { background-image: url(/images/nav/#{$section}.png); } } }
.home nav { background-image: url(/images/nav/home.png); } .about nav { background-image: url(/images/nav/about.png); } .archive nav { background-image: url(/images/nav/archive.png); } .projects nav { background-image: url(/images/nav/projects.png); }
@function grid-width($cells) { @return ($cell-width + $cell-padding) * $cells; }
Function to calculate the em font size from pixels
@function px2em($font_size, $base_font_size: 16) { @return $font_size / $base_font_size + em }
// This ruleset won't be rendered on its own. #context a%extreme { color: blue; font-weight: bold; font-size: 2em; }
placeholder selectors can be extended, just like classes and IDs. The extended selectors will be generated, but the base placeholder selector will not
.notice { @extend %extreme; }
#context a.notice { color: blue; font-weight: bold; font-size: 2em; }
#{} interpolation is now allowed in all plain CSS directives (such as @font-face
, @keyframes
, and of course @media
).
Sass 3.2 adds the ie-hex-str
function which returns a hex string for a color suitable for use with IE filters.
Sass 3.2 adds the min
and max
functions, which return the minimum and maximum of several values.
There is now much more comprehensive support for using @extend alongside CSS3 selector combinators (+, ~, and >). These combinators will now be merged as much as possible.
Compass is a Framework, that extends SASS
It brings a lot of CSS3 mixins and useful CSS stuff
$ sudo gem install compass
or simple use the new Mac Installer
Create a new project
$ compass create <myproject>
Compass creates following:
config.rb sass ┗ ie.scss ┗ print.scss ┗ screen.scss stylesheets ┗ ie.css ┗ print.css ┗ screen.css
The compass configuration file is a ruby file, which means that we can do some clever things if we want to. But don’t let it frighten you; it’s really quite easy to set up your project.
Standard configuration: config.rb
http_path = "/" css_dir = "stylesheets" sass_dir = "sass" images_dir = "images" javascripts_dir = "javascripts"
my favourite config.rb
# config.rb http_path = "/" css_dir = "css" sass_dir = "sass" images_dir = "img" javascripts_dir = "js" fonts_dir = "fonts" #environment = :production relative_assets = true line_comments = false output_style = :compact #output_style = (environment == :production) ? :compressed : :expanded
$ compass compile -e production --force
Create a separate configuration file for production
$ cp config.rb prod_config.rb # ..edit prod_config.rb to suit your needs.. $ compass compile -c prod_config.rb --force
@import "compass"
importing only few parts
@import "compass/css3"
@import "compass/utilities"
@import "compass/typography/links"
Sample: Compass Horizontal List
ul.nav { @include horizontal-list-container; > li { @include horizontal-list-item; } }
Sample: Compass Horizontal List
ul.nav { margin: 0; padding: 0; border: 0; overflow: hidden; *zoom: 1; } ul.nav > li { list-style-image: none; list-style-type: none; margin-left: 0; white-space: nowrap; display: inline; float: left; padding-left: 4px; padding-right: 4px; } ul.nav > li:first-child, ul.nav > li.first { padding-left: 0; } ul.nav > li:last-child { padding-right: 0; } ul.nav > li.last { padding-right: 0; }
most CSS3 features are implemented with browser vendor prefixes
think about linear-gradient – hard to know all the syntaxes
.gradient { background-color: yellow; /* Fallback Color */ background: -webkit-gradient(linear,left top,left bottom,from(yellow),to(blue)); background: -webkit-linear-gradient(top, yellow 0%, blue 100%); background: -moz-linear-gradient(top, yellow 0%, blue 100%); background: -ms-linear-gradient(top, yellow 0%, blue 100%); background: -o-linear-gradient(top, yellow 0%, blue 100%); /* current W3C standard, not implemented implemented with prefix in FF10, Opera 11.60 */ background: linear-gradient(to bottom, yellow 0%, blue 100%); }
easy with Compass
.gradient { @include background(linear-gradient(top, yellow, blue)); }
full browser support
.gradient { $experimental-support-for-svg: true; @include background(linear-gradient(top, yellow, blue)); // for older IEs .oldie & { background-color: yellow; @include filter-gradient(yellow, blue, vertical); } }
compiled to
.gradient { background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0i.....'); background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffff00), color-stop(100%, #0000ff)); background: -webkit-linear-gradient(top, #ffff00, #0000ff); background: -moz-linear-gradient(top, #ffff00, #0000ff); background: -o-linear-gradient(top, #ffff00, #0000ff); background: -ms-linear-gradient(top, #ffff00, #0000ff); background: linear-gradient(top, #ffff00, #0000ff); } .oldie .gradient{ background-color: yellow; *zoom: 1; filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFFFF00', endColorstr='#FF0000FF'); }
// _base.scss $experimental-support-for-svg: true; $support-for-original-webkit-gradients: false; $legacy-support-for-ie6: false; $legacy-support-for-ie7: false;
header { background: image-url("header-bg.png"); h1 { height: image-height("logo.png"); width: image-width("logo.png"); background: transparent inline-image("logo.png") no-repeat; } }
header { background: url('../img/header-bg.png'); } header h1 { height: 185px; width: 140px; background: transparent url('data:image/png;base64,/9j/4AAQSkZJRgABAgAAZABkAAD....') no-repeat; }
really useful helper functions
// reads the width of the image image-width($image) // reads the height of the image image-height($image) // converts the image to an inline image inline-image($image, $mime-type)
aside { #{headings(2,4)} { text-transform: uppercase; } }
aside h2, aside h3, aside h4 { text-transform: uppercase; }
the best feature of Compass
Automatic spriting
public/images/icon/new.png public/images/icon/edit.png public/images/icon/save.png public/images/icon/delete.png
@import "icon/*.png"; @include all-icon-sprites;
Generates sprite images of given folder
Compass watches image folders for changes
CSS
.icon-sprite, .icon-delete, .icon-edit, .icon-new, .icon-save { background: url('/images/icon-s34fe0604ab.png') no-repeat; } .icon-delete { background-position: 0 0; } .icon-edit { background-position: 0 -32px; } .icon-new { background-position: 0 -64px; } .icon-save { background-position: 0 -96px; }
img/player/play.png img/player/play_active.png img/player/play_hover.png
@import "player/*.png"; @include all-player-sprites; .button-play { @include player-sprite(play); }
.player-sprite, .player-play, .player-play_pause { background: url('/images/player-sc690ad66a4.png') no-repeat; } .player-play { background-position: 0 -110px; } .player-play:hover, .player-play.play_hover, .player-play.play-hover { background-position: 0 0; } .player-play:active, .player-play.play_active, .player-play.play-active { background-position: 0 -55px; }
Sprite-Layout Configuration
$player-layout: horizontal;
Sprite-Layout Configuration
$player-layout: diagonal;
$player-layout: smart;
$player-spacing: 100px // add more space between sprite images @import "player/*.png" @include all-player-sprites; .button-play { @include player-sprite(play); height: image-height("player/play.png"); width: image-width("player/play.png"); }
Styling file downloads
img/fileicons/jpg.png img/fileicons/doc.png img/fileicons/zip.png img/fileicons/pdf.png img/fileicons/xml.png img/fileicons/txt.png
@import "fileicons/*.png"; @include all-fileicons-sprites; $icons: sprite-map("fileicons/*.png"); // point to sprite-map @each $sprite-name in sprite-names($icons) { a[href $=".#{$sprite-name}"] { padding-left: 25px; } }
Sprites configuration variables
$<map>-spacing - space in px around the sprites $<map>-repeat - whether to repeat the sprite bg $<map>-position - the x position of the sprite on the map $<map>-sprite-base-class - the base class (default ".<map>-sprite") $<map>-clean-up - whether to delete old image maps $<map>-<sprite>-spacing - spacing, for individual sprites $<map>-<sprite>-repeat - repeat, for individual sprites $<map>-<sprite>-position - position, for individual sprites
Example:
$sprites-header_bg-repeat: repeat-x;
useful additions: disable asset caching
rename generated Sprite image
# config.rb asset_cache_buster :none # Rename the generated sprite images on_sprite_saved do |filename| if File.exists?(filename) FileUtils.mv filename, filename.gsub(%r{-s[a-z0-9]{10}\.png$}, '.png') end end # Replace in stylesheets generated references to sprites # by their counterparts without the hash uniqueness. on_stylesheet_saved do |filename| if File.exists?(filename) css = File.read filename File.open(filename, 'w+') do |f| f << css.gsub(%r{-s[a-z0-9]{10}\.png}, '.png') end end end
loading Compass plugins:
add to the config.rb
# config.rb ... require 'rgbapng' require 'compass-bootstrap'
Sven Wolfermann | maddesigns