Heygrady (there's a new blog)

I don't get It.

Using Images and JavaScript and Compass for Easy Cross-Browser Rounded Corners

Permalink

In a previous post exploring rounded corner methods, I covered a technique using absolute positioning for faking rounded corners in browsers greater than IE6. One issue with faking rounded corners is the amount of HTML markup required as well as the hassle of crafting the accompanying CSS. I have created a jQuery plug-in for adding the required markup and a complementary Sass/Compass @mixin for generating the required CSS. The markup generated utilizes jQuery UI theming for the CSS class names. The code is available on GitHub

Summary of the Technique

As covered before, the idea is to use extra markup and CSS to create rounded corners even without support for border-radius. Of course the technique is also useful for creating fancy corners that border-radius wouldn't support. The two examples below show a normal div using border-radius and a box that is using the absolutely positioned span technique from the previous article.

simple box with a red border
Border Radius
(view live example)
Absolute Position using a Sprite
(view live example)

Basic Markup

The absolute positioning technique requires some additional markup to be added to the page for the corners and sides of the box. Below the initial .box is shown with no additional markup.

box.htmlGist page
<div class="box">
<h2>Some Headline</h2>
<p>My content.</p>
</div>

In order to create the illusion of corners, some additional markup must be added to the page. First the content is wrapped in a .ui-corners-content that is used with z-index to ensure that the corners never cover up the content of our .boxborder-radius doesn't clip content unless overflow: hidden; is set. Next, eight spans are added; one for each corner and side. Below is the markup used for creating the rounded corner illusion.

corners.htmlGist page
<div class="box ui-corners">
<div class="ui-corners-content">
<h2>Some Headline</h2>
<p>My content.</p>
</div>
<span class="ui-corners-corner ui-corners-corner-tl"></span>
<span class="ui-corners-corner ui-corners-corner-tr"></span>
<span class="ui-corners-corner ui-corners-corner-br"></span>
<span class="ui-corners-corner ui-corners-corner-bl"></span>
<span class="ui-corners-side ui-corners-side-top"></span>
<span class="ui-corners-side ui-corners-side-right"></span>
<span class="ui-corners-side ui-corners-side-bottom"></span>
<span class="ui-corners-side ui-corners-side-left"></span>
</div>

As you can see above, the extra markup is very straightforward but it's not exactly ideal to have to add this to every box on a website — this is the point of the jQuery plug-in that will be discussed further down.

Basic CSS

In order to optimize performance and simplify the JavaScript plug-in, all of the styling is done using CSS. The base CSS will set the z-index of the .ui-corners-content and position each of the corners and sides. As mentioned in the previous article, conflicting positioning properties are used to ensure that the sides stretch the fill width of the .box. For instance the left side, .ui-corners-side-left, is given a top and a bottom of 10px. Setting the top and bottom at the same time has the effect of stretching the span the entire height because both properties are honored. The 10px spacing is to prevent the sides from overlapping the corners. As mentioned in the previous article, IE6 will ignore the conflicting properties which makes this a poor solution for sites that require IE6 support.

The CSS below will correctly position all of the spans and set some default heights and widths. In order to actually see something, the base CSS needs to be extended by adding background images and altering the default heights and widths — this is the point of the Compass @mixin that will be discussed further down.

base.cssGist page
/* Default Corners CSS*/
.ui-corners { position: relative; }
.ui-corners-content { position: relative; z-index: 1; }
.ui-corners-corner, .ui-corners-side { position: absolute; z-index: 0; }
.ui-corners-corner { height: 10px; width: 10px; background-repeat: no-repeat; }
.ui-corners-corner-tl { top: 0; left: 0; background-position: left top; }
.ui-corners-corner-tr { top: 0; right: 0; background-position: right top; }
.ui-corners-corner-br { bottom: 0; right: 0; background-position: right bottom; }
.ui-corners-corner-bl { bottom: 0; left: 0; background-position: left bottom; }
.ui-corners-side-top, .ui-corners-side-bottom { left: 10px; right: 10px; height: 10px; background-repeat: repeat-x; }
.ui-corners-side-left, .ui-corners-side-right { top: 10px; bottom: 10px; width: 10px; background-repeat: repeat-y; }
.ui-corners-side-top { top: 0; background-position: left top; }
.ui-corners-side-bottom { bottom: 0; background-position: left bottom; }
.ui-corners-side-left { left: 0; background-position: left top; }
.ui-corners-side-right { right: 0; background-position: right top; }

Although absolutely positioned background images can look like a native border-radius, they don't actually affect the sizing of the box like a real border does. It's easy to fake the presence of a border by adding padding to the .box equal to the width of the border. While it's possible to simply set a transparent border — border: 10px solid transparent; — setting a border will require additional calculations for the corner and side spans because border appears outside the box.

Using the jQuery Corners Plug-in

The jQuery Corners plug-in is very straightforward. Exactly as described in the basic markup section above, it wraps the content of the container in a div and adds eight spans to the container. Again, starting with the basic box.html file in the basic markup section above, the additional markup can be added as shown below.

usage.jsGist page
$('.box').corners();

Currently the plug-in takes no arguments and does not apply any styling to the page. This is intentional because the styling should be controlled using CSS, similar to most jQuery UI widgets.

The Actual Plug-In

Below is the actual plug-in that adds the markup to the page. As noted above, the plug-in wraps the contents of the target element in a .ui-corners-content and then appends all of the side and corner spans.

jquery-corners.jsGist page
///////////////////////////////////////////////////////
// Corners
///////////////////////////////////////////////////////
;(function($, undefined) {
// set up strings
var corners = ["tl", "tr", "br", "bl"],
sides = ["top", "right", "bottom", "left"],
className = "ui-corners",
cornerClassName = className + "-corner",
sideClassName = className + "-side",
spans = "",
spanStart = '<span class="';
// creat the span string
$.each(corners, function() {
spans += spanStart + cornerClassName + ' ' + cornerClassName + '-' + this + '"/>';
});
$.each(sides, function() {
spans += spanStart + sideClassName + ' ' + sideClassName + '-' + this + '"/>';
});
// add corners/sides to an element
// wrap the contents with a content div
function addCorners(el) {
$(el).wrapInner('<div class="' + className + '-content" />').append(spans).addClass(className);
}
// create a jQuery plugin for adding in the corners
$.fn.corners = function() {
this.not('.' + className).each(function() {
addCorners(this);
});
};
})(jQuery);

Special care is taken to avoid adding the extra markup to elements that have already been processed. The .ui-corners class is detected and those elements are skipped. Also, repeated strings are saved in variables to aid in compressing. The script is only 402 characters after running it through YUI Compressor and 267 bytes after gzipping.

Seeing the Plug-in In Action

Below you can see a .box that has had the corners applied. The base.css file shown above is used for the styling. Background colors have been applied to the side and corner spans to make them visible. It's also clear that the corners and sides are not overlapping the content.

jQuery Corners Basic Usage
(view live example)

For more advanced styling it is recommended to use the SASS @mixin described below.

Using the UI Corners @mixin

Although the base.css above will correctly position all of the corners and sides, more calculations are needed for actually using background images. For these purposes the SASS/Compass plug-in provides two @mixins: corner-images and corner-border-width. These @mixins greatly simplify the creation of CSS for mimicking rounded corners as well as more advanced examples.

Using @mixin corner-images

The corner-images @mixin takes eight arguments, one for each corner and side. The arguments are ordered clockwise starting with the top-left corner and ending with the left side. Each argument is a Sass list of values. The corners and sides require slightly different values in the list — corners have a height and width while sides only have one dimension that can be specified — however background-image value is always the first value. All other values in the list can be left at default. Each argument is optional and may be skipped by passing in "auto" or using Sass keyword arguments.

@include corner-images( [$tl], [$t], [$tr], [$r], [$br], [$b], [$bl], [$l] )
  • $tl — [background-image] [width] [height] [background-position-x] [background-position-y] [left] [top]
  • $t — [background-image] [height] [background-position-x] [background-position-y] [top] [left] [right]
  • $tr — [background-image] [width] [height] [background-position-x] [background-position-y] [right] [top]
  • $r — [background-image] [width] [background-position-x] [background-position-y] [right] [top] [bottom]
  • $br — [background-image] [width] [height] [background-position-x] [background-position-y] [right] [bottom]
  • $b — [background-image] [height] [background-position-x] [background-position-y] [bottom] [left] [right]
  • $bl — [background-image] [width] [height] [background-position-x] [background-position-y] [left] [bottom]
  • $l — [background-image] [width] [background-position-x] [background-position-y] [left] [top] [bottom]

Using @mixin corner-border-width

The corner-border-width @mixin mimics a real border on the box using padding. As discussed above, setting a transparent border will cause some positioning issues because borders appears outside of the element. This issues is overcome by simply using padding. For convenience, if real padding is needed it can be passed as the second argument which will be added to the first argument. Both arguments are Sass lists taking the same values as the CSS padding property. Similar to padding, each argument can take a list of one, two, three or four values.

@include corner-border-width( $border-width, [$padding] )
  • $border-width — [top-width] [right-width] [bottom-width] [left-width]
  • $padding — [top-width] [right-width] [bottom-width] [left-width]

The Actual @mixin

Below is the entire @mixin that is used for generating the CSS. There are the two mixins mentioned above as well as a number of helper functions for manipulation all of the list values. Close inspection reveals that there are a number over default variables available. Passing "default" instead of "auto" for any value will favor the default value if it exists. Auto values are based on the other passed values while default values rely blindly on the default variables. The default variables can be easily overridden. At the bottom of the file, the default styles are visible. Simply including this plugin will generate the default styles as shown in the base.css above.

_ui-corners.scssGist page
//-----------------------------------------------
// Variables
//-----------------------------------------------
$ui-corners-cache-buster: true !default;
// default widths
$ui-corners-corner-height: 10px !default;
$ui-corners-corner-width: 10px !default;
$ui-corners-side-width: 10px !default;
// defaults per corner
$ui-corners-corner-tl-height: $ui-corners-corner-height !default;
$ui-corners-corner-tl-width: $ui-corners-corner-width !default;
$ui-corners-corner-tr-height: $ui-corners-corner-height !default;
$ui-corners-corner-tr-width: $ui-corners-corner-width !default;
$ui-corners-corner-br-height: $ui-corners-corner-height !default;
$ui-corners-corner-br-width: $ui-corners-corner-width !default;
$ui-corners-corner-bl-height: $ui-corners-corner-height !default;
$ui-corners-corner-bl-width: $ui-corners-corner-width !default;
// defaults per side
$ui-corners-side-top-width: $ui-corners-side-width !default;
$ui-corners-side-bottom-width: $ui-corners-side-width !default;
$ui-corners-side-left-width: $ui-corners-side-width !default;
$ui-corners-side-right-width: $ui-corners-side-width !default;
//-----------------------------------------------
// Corner Border Width
// Adds padding to the corner container to simulate a border
// The second parameter allows for setting an inner padding in addition to the border
// @param list $width border-width list: [top] [right] [bottom] [left]
// @param list $padding (optional) padding list: [top] [right] [bottom] [left]
//-----------------------------------------------
@mixin corner-border-width ($border-width, $padding: false) {
@if $padding {
$border-width: corner-zip-widths($border-width, $padding);
}
&.ui-corners { padding: $border-width; }
}
//-----------------------------------------------
// Corner Zip Widths
// returns a list of the two lists added together
// @param list $a list of widths: [top] [right] [bottom] [left]
// @param list $b (optional) list of widths: [top] [right] [bottom] [left]
// @return list
//-----------------------------------------------
@function corner-zip-widths($a, $b: 0) {
$a-t: corner-nth($a, 1, 0);
$a-r: corner-nth($a, 2, $a-t);
$a-b: corner-nth($a, 3, $a-t);
$a-l: corner-nth($a, 4, $a-r);
$b-t: corner-nth($b, 1, 0);
$b-r: corner-nth($b, 2, $b-t);
$b-b: corner-nth($b, 3, $b-t);
$b-l: corner-nth($b, 4, $b-r);
$a-t: $a-t + $b-t;
$a-r: $a-r + $b-r;
$a-b: $a-b + $b-b;
$a-l: $a-l + $b-l;
@if $a-t == $a-b and $a-r == $a-l and $a-t == $a-r {
@return $a-t; // all equal
} @elseif $a-t == $a-b and $a-r == $a-l and $a-t != $a-r {
@return $a-t $a-r; // x and y different
} @elseif $a-t != $a-b and $a-r == $a-l {
@return $a-t $a-r $a-b; // y different
} @else {
@return $a-t $a-r $a-b $a-l;// all different
}
}
//-----------------------------------------------
// Corner Images
// @param list $tl [background-image] [width] [height] [background-position-x] [background-position-y] [left] [top] top-left corner
// @param list $t [background-image] [height] [background-position-x] [background-position-y] [left] [right] [top] top side
// @param list $tr [background-image] [width] [height] [background-position-x] [background-position-y] [right] [top] top-right corner
// @param list $r [background-image] [width] [background-position-x] [background-position-y] [right] [top] [bottom] right side
// @param list $br [background-image] [width] [height] [background-position-x] [background-position-y] [right] [bottom] bottom-right corner
// @param list $b [background-image] [height] [background-position-x] [background-position-y] [left] [right] [bottom] bottom side
// @param list $bl [background-image] [width] [height] [background-position-x] [background-position-y] [left] [bottom] bottom-left corner
// @param list $l [background-image] [width] [background-position-x] [background-position-y] [left] [top] [bottom] left side
//-----------------------------------------------
@mixin corner-images ($tl: auto, $t: auto, $tr: auto, $r: auto, $br: auto, $b: auto, $bl: auto, $l: auto) {
// corner images and dimensions
$tl-image: nth($tl, 1);
$tl-width: corner-var(corner-nth($tl, 2, auto) corner-image-width($tl-image) $ui-corners-corner-tl-width);
$tl-height: corner-var(corner-nth($tl, 3, auto) corner-image-height($tl-image) $ui-corners-corner-tl-height);
$tr-image: nth($tr, 1);
$tr-width: corner-var(corner-nth($tr, 2, auto) corner-image-width($tr-image) $ui-corners-corner-tr-width);
$tr-height: corner-var(corner-nth($tr, 3, auto) corner-image-height($tr-image) $ui-corners-corner-tr-height);
$br-image: nth($br, 1);
$br-width: corner-var(corner-nth($br, 2, auto) corner-image-width($br-image) $ui-corners-corner-br-width);
$br-height: corner-var(corner-nth($br, 3, auto) corner-image-height($br-image) $ui-corners-corner-br-height);
$bl-image: nth($bl, 1);
$bl-width: corner-var(corner-nth($bl, 2, auto) corner-image-width($bl-image) $ui-corners-corner-bl-width);
$bl-height: corner-var(corner-nth($bl, 3, auto) corner-image-height($bl-image) $ui-corners-corner-bl-height);
$t-image: nth($t, 1); $r-image: nth($r, 1); $b-image: nth($b, 1); $l-image: nth($l, 1);
// all backgrounds are the same
@if $tl-image != none and $tl-image == $t-image and $tl-image == $tr-image and $tl-image == $r-image
and $tl-image == $br-image and $tl-image == $b-image and $tl-image == $b-image and $tl-image == $l-image {
.ui-corners-corner, .ui-corners-side { background-image: image-url($tl-image, false, $ui-corners-cache-buster); }
$tl-image: none; $tr-image: none; $br-image: none; $bl-image: none;
$t-image: none; $r-image: none; $b-image: none; $l-image: none;
}
// normalize height and width
$width: false;
$height: false;
// check all width are the same
@if $tl-width == $tr-width and $tl-width == $br-width and $tl-width == $bl-width { $width: $tl-width; }
// check all heights are the same
@if $tl-height == $tr-height and $tl-height == $br-height and $tl-height == $bl-height { $height: $tl-height; }
// set the width and height with a generic class
@if ($width and $width != $ui-corners-corner-width) or ($height and $height != $ui-corners-corner-height)
or ($tl-image != none and $tl-image == $tr-image and $tl-image == $br-image and $tl-image == $bl-image) {
// when it matches default, fall back to generic corner class
.ui-corners-corner {
@if $width and $width != $ui-corners-corner-width { width: $width; }
@if $height and $height != $ui-corners-corner-height { height: $height; }
@if $tl-image != none and $tl-image == $tr-image and $tl-image == $br-image and $tl-image == $bl-image {
background-image: image-url($tl-image, false, $ui-corners-cache-buster);
$tl-image: none; $tr-image: none; $br-image: none; $bl-image: none;
}
}
}
// Top Left
@if $tl != auto {
.ui-corners-corner-tl {
@include corner-props(
$image: $tl-image,
$w: if($width, false, $tl-width),
$h: if($height, false, $tl-height),
$x: corner-nth($tl, 4, auto) false 0,
$y: corner-nth($tl, 5, auto) false 0,
$l: corner-nth($tl, 6, auto) false 0,
$t: corner-nth($tl, 7, auto) false 0
);
}
}
// Top Right
@if $tr != auto {
.ui-corners-corner-tr {
@include corner-props(
$image: $tr-image,
$w: if($width, false, $tr-width),
$h: if($height, false, $tr-height),
$x: corner-nth($tr, 4, auto) false 0,
$y: corner-nth($tr, 5, auto) false 0,
$r: corner-nth($tr, 6, auto) false 0,
$t: corner-nth($tr, 7, auto) false 0
);
}
}
// Bottom Right
@if $br != auto {
.ui-corners-corner-br {
@include corner-props(
$image: $br-image,
$w: if($width, false, $br-width),
$h: if($height, false, $br-height),
$x: corner-nth($br, 4, auto) false 0,
$y: corner-nth($br, 5, auto) false 0,
$r: corner-nth($br, 6, auto) false 0,
$b: corner-nth($br, 7, auto) false 0
);
}
}
// Bottom Left
@if $bl != auto {
.ui-corners-corner-bl {
@include corner-props(
$image: $bl-image,
$w: if($width, false, $bl-width),
$h: if($height, false, $bl-height),
$x: corner-nth($bl, 4, auto) false 0,
$y: corner-nth($bl, 5, auto) false 0,
$l: corner-nth($bl, 6, auto) false 0,
$b: corner-nth($bl, 7, auto) false 0
);
}
}
// Side left, right, top and bottom
$t-height: corner-var(corner-nth($t, 2, auto) $tl-height $ui-corners-side-left-width);
$t-left: corner-var(corner-nth($t, 6, auto) ($tl-width + corner-nth($tl, 6, 0)) $ui-corners-corner-width);
$t-right: corner-var(corner-nth($t, 7, auto) ($tr-width + corner-nth($tr, 6, 0)) $ui-corners-corner-width);
$b-height: corner-var(corner-nth($b, 2, auto) $bl-height $ui-corners-side-left-width);
$b-left: corner-var(corner-nth($b, 6, auto) ($bl-width + corner-nth($bl, 6, 0)) $ui-corners-corner-width);
$b-right: corner-var(corner-nth($b, 7, auto) ($br-width + corner-nth($br, 6, 0)) $ui-corners-corner-width);
$l-width: corner-var(corner-nth($l, 2, auto) $tl-width $ui-corners-side-left-width);
$l-top: corner-var(corner-nth($l, 6, auto) ($tl-height + corner-nth($tl, 7, 0)) $ui-corners-corner-height);
$l-bottom: corner-var(corner-nth($l, 7, auto) ($bl-height + corner-nth($bl, 7, 0)) $ui-corners-corner-height);
$r-width: corner-var(corner-nth($r, 2, auto) $tr-width $ui-corners-side-right-width);
$r-top: corner-var(corner-nth($r, 6, auto) ($tr-height + corner-nth($tr, 7, 0)) $ui-corners-corner-height);
$r-bottom: corner-var(corner-nth($r, 7, auto) ($br-height + corner-nth($br, 7, 0)) $ui-corners-corner-height);
// normalize left and right, top and bottom
$left: false;
$right: false;
$top: false;
$bottom: false;
$width: false;
$height: false;
// check all sides are the same
@if $t-left == $b-left { $left: $t-left; }
@if $t-right == $b-right { $right: $t-right; }
@if $t-height == $b-height { $height: $t-height; }
@if $l-top == $r-top { $top: $l-top; }
@if $l-bottom == $r-bottom { $bottom: $l-bottom; }
@if $l-width == $r-width { $width: $l-width; }
@if $t-image != none and $t-image == $r-image and $t-image == $b-image and $t-image == $l-image {
.ui-corners-side { background-image: image-url($t-image, false, $ui-corners-cache-buster); }
$t-image: none; $r-image: none; $b-image: none; $l-image: none;
}
// set the top and bottom sides with a generic class
@if ($left and $left != $ui-corners-corner-width) or ($right and $right != $ui-corners-corner-width)
or ($height and $height != $ui-corners-side-width) or ($t-image != none and $t-image == $b-image) {
// when it matches default, fall back to generic corner class
.ui-corners-side-top, .ui-corners-side-bottom {
@if $left and $left != $ui-corners-corner-width { left: $left; }
@if $right and $right != $ui-corners-corner-width { right: $right; }
@if $height and $height != $ui-corners-side-width { height: $height; }
@if $t-image != none and $t-image == $b-image { background-image: image-url($t-image, false, $ui-corners-cache-buster); $t-image: none; $b-image: none; }
}
}
// set the left and right sides with a generic class
@if ($top and $top != $ui-corners-corner-height) or ($bottom and $bottom != $ui-corners-corner-height)
or ($width and $width != $ui-corners-side-width) or ($l-image != none and $l-image == $r-image) {
// when it matches default, fall back to generic corner class
.ui-corners-side-left, .ui-corners-side-right {
@if $top and $top != $ui-corners-corner-height { top: $top; }
@if $bottom and $bottom != $ui-corners-corner-height { bottom: $bottom; }
@if $width and $width != $ui-corners-side-width { width: $width; }
@if $l-image != none and $l-image == $r-image { background-image: image-url($l-image, false, $ui-corners-cache-buster); $l-image: none; $r-image: none; }
}
}
// Top
@if $t != auto {
.ui-corners-side-top {
@include corner-props(
$image: $t-image,
$h: if($height, false, $t-height),
$x: corner-nth($t, 3, auto) false left,
$y: corner-nth($t, 4, auto) false top,
$l: if($left, false, $t-left),
$r: if($right, false, $t-right) + corner-nth($tr, 6, 0),
$t: corner-nth($t, 5, auto) false 0
);
}
}
// Bottom
@if $b != auto {
.ui-corners-side-bottom {
@include corner-props(
$image: $b-image,
$h: if($height, false, $b-height),
$x: corner-nth($b, 3, auto) false left,
$y: corner-nth($b, 4, auto) false bottom,
$l: if($left, false, $b-left),
$r: if($right, false, $b-right),
$b: corner-nth($b, 5, auto) false 0
);
}
}
// Left
@if $l != auto {
.ui-corners-side-left {
@include corner-props(
$image: $l-image,
$w: if($width, false, $l-width),
$x: corner-nth($l, 3, auto) false left,
$y: corner-nth($l, 4, auto) false top,
$l: corner-nth($l, 5, auto) false 0,
$t: if($top, false, $l-top),
$b: if($bottom, false, $l-bottom)
);
}
}
// Right
@if $r != auto {
.ui-corners-side-right {
@include corner-props(
$image: $r-image,
$w: if($width, false, $r-width),
$x: corner-nth($r, 3, auto) false right,
$y: corner-nth($r, 4, auto) false top,
$r: corner-nth($r, 5, auto) false 0,
$t: if($top, false, $r-top),
$b: if($bottom, false, $r-bottom)
);
}
}
}
//-----------------------------------------------
// Corner Props
// creates the porperties of corner or side
// @param image $image (optional)
// @param list $w (optional) [value] [auto] [default] element width.
// @param list $h (optional) [value] [auto] [default] element height.
// @param list $x (optional) [value] [auto] [default] background position x.
// @param list $y (optional) [value] [auto] [default] background position y.
// @param list $l (optional) [value] [auto] [default] position left.
// @param list $r (optional) [value] [auto] [default] position right.
// @param list $t (optional) [value] [auto] [default] position top.
// @param list $b (optional) [value] [auto] [default] position bottom.
//-----------------------------------------------
@mixin corner-props ($image: none, $w: false, $h: false, $x: false, $y: false, $l: false, $r: false, $t: false, $b: false) {
// each variable takes 3 values: passed auto default
$left: corner-var($l);
$right: corner-var($r);
$top: corner-var($t);
$bottom: corner-var($b);
$width: corner-var($w);
$height: corner-var($h);
@if $image != none { background-image: image-url($image, false, $ui-corners-cache-buster); }
@if $left != false { left: $left; }
@if $right != false { right: $right; }
@if $top != false { top: $top; }
@if $bottom != false { bottom: $bottom; }
@if $width != false { width: $width; }
@if $height != false { height: $height; }
$position-x: corner-var($x);
$position-y: corner-var($y);
@if $position-x != false or $position-y != false { background-position: if($position-x != false, $position-x, nth($x, 3)) if($position-y != false, $position-y, nth($x, 3)); }
}
//-----------------------------------------------
// Corner Nth
// returns the value of a list at an index or default value if the index is missing
// @param list $list value list
// @param int $n index within the lsit
// @param mixed $default default value if index does not exist
//-----------------------------------------------
@function corner-nth($list, $n, $default: false) {
$value: $default;
@if length($list) > $n - 1 { $value: nth($list, $n) }
@return $value;
}
//-----------------------------------------------
// Corner Image Width/Height
// returns the width of an image or default
// @param image $image
//-----------------------------------------------
@function corner-image-width($image) {
@if ($image == none) { @return default; }
@return image-width($image);
}
@function corner-image-height($image) {
@if ($image == none) { @return default; }
@return image-height($image);
}
//-----------------------------------------------
// Corner Vars
// returns the current value unless it is "auto" or "default" then those values are returned
// @param list $list list if values [current value] [value of auto] [value of default]
//-----------------------------------------------
@function corner-var($list) {
$val: nth($list, 1);
@if $val == auto { $val: nth($list, 2); }
@if $val == default { $val: nth($list, 3); }
@return $val;
}
//-----------------------------------------------
// Basic Styles (required)
//-----------------------------------------------
// corner wrapper
.ui-corners {
position: relative;
}
// the content of the wrapper
.ui-corners-content {
position: relative;
z-index: 1;
}
// all corners and sides
.ui-corners-corner, .ui-corners-side {
position: absolute;
z-index: 0;
}
//-----------------------------------------------
// Corners
//-----------------------------------------------
.ui-corners-corner {
height: $ui-corners-corner-height;
width: $ui-corners-corner-width;
background-repeat: no-repeat;
}
.ui-corners-corner-tl {
top: 0;
left: 0;
@if $ui-corners-corner-height != $ui-corners-corner-tl-height { height: $ui-corners-corner-tl-height; }
@if $ui-corners-corner-width != $ui-corners-corner-tl-width { width: $ui-corners-corner-tl-width; }
background-position: left top;
}
.ui-corners-corner-tr {
top: 0;
right: 0;
@if $ui-corners-corner-height != $ui-corners-corner-tr-height { height: $ui-corners-corner-tr-height; }
@if $ui-corners-corner-width != $ui-corners-corner-tr-width { width: $ui-corners-corner-tr-width; }
background-position: right top;
}
.ui-corners-corner-br {
bottom: 0;
right: 0;
@if $ui-corners-corner-height != $ui-corners-corner-br-height { height: $ui-corners-corner-br-height; }
@if $ui-corners-corner-width != $ui-corners-corner-br-width { width: $ui-corners-corner-br-width; }
background-position: right bottom;
}
.ui-corners-corner-bl {
bottom: 0;
left: 0;
@if $ui-corners-corner-height != $ui-corners-corner-bl-height { height: $ui-corners-corner-bl-height; }
@if $ui-corners-corner-width != $ui-corners-corner-bl-width { width: $ui-corners-corner-bl-width; }
background-position: left bottom;
}
//-----------------------------------------------
// Sides
//-----------------------------------------------
.ui-corners-side-top, .ui-corners-side-bottom {
left: $ui-corners-corner-width;
right: $ui-corners-corner-width;
height: $ui-corners-side-width;
background-repeat: repeat-x;
}
.ui-corners-side-left, .ui-corners-side-right {
top: $ui-corners-corner-height;
bottom: $ui-corners-corner-height;
width: $ui-corners-side-width;
background-repeat: repeat-y;
}
.ui-corners-side-top {
top: 0;
@if $ui-corners-side-width != $ui-corners-side-top-width { height: $ui-corners-side-top; }
background-position: left top;
}
.ui-corners-side-bottom {
bottom: 0;
@if $ui-corners-side-width != $ui-corners-side-bottom-width { height: $ui-corners-side-bottom; }
background-position: left bottom;
}
.ui-corners-side-left {
left: 0;
@if $ui-corners-side-width != $ui-corners-side-left-width { width: $ui-corners-side-left; }
background-position: left top;
}
.ui-corners-side-right {
right: 0;
@if $ui-corners-side-width != $ui-corners-side-right-width { width: $ui-corners-side-right; }
background-position: right top;
}

Basic Fake border-radius Example

In order to mimic border-radius we'll need some images. For this example we'll use eight separate images as described in the previous article. Using the @mixin this becomes very simple as shown below.

box.scssGist page
@import "ui-corners";
.box {
@include corner-images(
"corner-tl.png",
"corner-t.png",
"corner-tr.png",
"corner-l.png",
"corner-br.png",
"corner-b.png",
"corner-bl.png",
"corner-l.png"
);
@include corner-border-width(1px, 10px);
}

Above is a .box where an image is used for each corner and side. The .box also has a border-width of 1px and padding of 10px. Below is the CSS code that is generated. Only the code that is distinct form the basic CSS is generated. The "?1313871969" after the images is a cache busting artifact caused by the @mixins reliance on Compass's image-url @function and can be turned off by setting the global $ui-corners-cache-buster variable to false.

box.cssGist page
.box .ui-corners-corner { width: 12px; height: 12px; }
.box .ui-corners-corner-tl { background-image: url('../i/corner-tl.png?1312605049'); }
.box .ui-corners-corner-tr { background-image: url('../i/corner-tr.png?1312605049'); }
.box .ui-corners-corner-br { background-image: url('../i/corner-br.png?1312605049'); }
.box .ui-corners-corner-bl { background-image: url('../i/corner-bl.png?1312605049'); }
.box .ui-corners-side-top, .box .ui-corners-side-bottom { left: 12px; right: 12px; height: 12px; }
.box .ui-corners-side-left, .box .ui-corners-side-right { top: 12px; bottom: 12px; width: 12px; background-image: url('../i/corner-l.png?1312605049'); }
.box .ui-corners-side-top { background-image: url('../i/corner-t.png?1312605049'); }
.box .ui-corners-side-bottom { background-image: url('../i/corner-b.png?1312605049'); }
.box.ui-corners { padding: 11px; }

One important thing to note is that the images are all measured using the Compass image dimension functions. Because all of list values other than [background-image] were left to auto, the corners were given a height and width equal to their respective background images. Similarly the sides were given values based on the images. For instance, the left side was given a top equal to the hight of the top-left image, a bottom equal to the hight of the bottom-left image and a width equal to the width of the top-left image. By default the sides are given the respective width or height of the nearest corner instead of the height width of their own image. This will help in cases where background colors also need to be applied to the sides to match the desired design.

Mimicking border-radius
(view live example)

Basic Sprite Example

Of course include eight separate images just to mimic border-radius isn't most appealing thing in the world — sprites are a good thing. Currently the sprite needs to be produced beforehand and cannot be generated with Compass's excellent sprite functions. The sprite image from the previous article will be used.

box-sprite.scssGist page
@import "ui-corners";
.box {
@include corner-images(
"corner.png" 12px 12px -13px -13px,
"corner.png" auto 0 -25px,
"corner.png" 12px 12px -1px -13px,
"corner.png" auto 11px 0,
"corner.png" 12px 12px -1px -1px,
"corner.png" auto 0 11px,
"corner.png" 12px 12px -13px -1px,
"corner.png" auto -25px 0
);
@include corner-border-width(1px, 10px);
}

In the example above the same image is passed in for each of the corners and sides. For each corner a width, height, background-position-x and background-position-y are supplied. For the sides the respective height or width is specified as "auto" and then background-position-x and background-position-y are supplied. The above example will generate the CSS below.

box-sprite.cssGist page
.box .ui-corners-corner, .box .ui-corners-side { background-image: url('../i/corner.png?1312605049'); }
.box .ui-corners-corner { width: 12px; height: 12px; }
.box .ui-corners-corner-tl { background-position: -13px -13px; }
.box .ui-corners-corner-tr { background-position: -1px -13px; }
.box .ui-corners-corner-br { background-position: -1px -1px; }
.box .ui-corners-corner-bl { background-position: -13px -1px; }
.box .ui-corners-side-top, .box .ui-corners-side-bottom { left: 12px; right: 12px; height: 12px; }
.box .ui-corners-side-left, .box .ui-corners-side-right { top: 12px; bottom: 12px; width: 12px; }
.box .ui-corners-side-top { background-position: 0 -25px; }
.box .ui-corners-side-bottom { background-position: 0 11px; }
.box .ui-corners-side-left { background-position: -25px 0; }
.box .ui-corners-side-right { background-position: 11px 0; }
.box.ui-corners { padding: 11px; }

Similar to the basic example above, only the rules that differ from the basic.css are generated. When values are identical for groups of selectors they are appropriately grouped. For instance, the background-image is only specified once. Also of note, the background-position is no longer the default and is now in the CSS.

Sprite Example
(view live example)

Using a sprite in this example will significantly reduce the file sizes. The eight images together total 4.4KB while the sprite image is only 1.1KB. Of course this also removes eight HTTP requests which is always a good thing — ask Google.

Fancy Sprite Example

Of course, this technique is not limited by simply mimicking border-radius. It's also possible to apply fancier borders that CSS could never do. For instance, imagine creating a box where nearly every Photoshop Layer Style has been applied — Drop Shadow, Inner Shadow, Outer Glow, Inner Glow, Bevel and Emboss and Color Overlay. It would be hard to mimick that without using images.

Super Fancy Border
Super Fancy Sprite
Super Fancy Sides

Above is an image created using the list of Photoshop filters listed above. It has been turned into two different sprites. It's not possible to use a single sprite for all eight corners and sides when the sides are more that 1px solid colors. The top and bottom sides are repeated above and below the corners in the sprite.

box-fancy.scssGist page
@import "ui-corners";
.box {
@include corner-images(
"corner-fancy-sprite.png" 18px 18px 1px -18px -9px -4px,
"corner-fancy-sprite.png" auto 0 0 -4px,
"corner-fancy-sprite.png" 20px 18px -16px -18px -2px -4px,
"corner-fancy-sprite-sides.png" auto -18px 0 -2px,
"corner-fancy-sprite.png" 20px 17px -16px -36px -2px -7px,
"corner-fancy-sprite.png" auto 0 -52px -7px,
"corner-fancy-sprite.png" 18px 17px 1px -36px -9px -7px,
"corner-fancy-sprite-sides.png" auto 0 0 -9px
);
@include corner-border-width(7px 8px 5px 5px, 4px 3px 6px 6px);
}

In the above Sass, the positioning values are also used to move the shadow and glow outside the original box. For instance, the top-left corner is the first argument in corner-images and the first item in the list is the fancy sprite. The top left corner image is 18px by 18px and starts at 0px by 18px within the sprite. The first 8px of the corner are actually shadow as are the top 4px. The similar measurements are done for all of the corners and sides. The right and left sides get their own sprite because they didn't fit in the other one. This generates the CSS below.

box-fancy.cssGist page
.box .ui-corners-corner { background-image: url('../i/corner-fancy-sprite.png?1314340388'); }
.box .ui-corners-corner-tl { left: -8px; top: -4px; width: 18px; height: 18px; background-position: 0px -18px; }
.box .ui-corners-corner-tr { right: -4px; top: -4px; width: 20px; height: 18px; background-position: -18px -18px; }
.box .ui-corners-corner-br { right: -4px; bottom: -7px; width: 20px; height: 17px; background-position: -18px -36px; }
.box .ui-corners-corner-bl { left: -8px; bottom: -7px; width: 18px; height: 17px; background-position: 0px -36px; }
.box .ui-corners-side-top, .box .ui-corners-side-bottom { right: 16px; background-image: url('../i/corner-fancy-sprite.png?1314340388'); }
.box .ui-corners-side-left, .box .ui-corners-side-right { top: 14px; background-image: url('../i/corner-fancy-sprite-sides.png?1314340389'); }
.box .ui-corners-side-top { right: false-4px; top: -4px; height: 18px; background-position: 0 0; }
.box .ui-corners-side-bottom { bottom: -7px; height: 17px; background-position: 0 -53px; }
.box .ui-corners-side-left { left: -8px; width: 18px; background-position: 0 0; }
.box .ui-corners-side-right { right: -4px; width: 20px; background-position: -18px 0; }
.box.ui-corners { padding: 11px; }

Below you can see the fancy sprite corners in a live example. It is important to note that the CSS will scale with the box as its content grows. The generated CSS would apply to any size box without any alterations.

Fancy Example
(view live example)

Comments