Skip to content

Instantly share code, notes, and snippets.

@ry5n
Created March 13, 2012 04:06
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save ry5n/2026666 to your computer and use it in GitHub Desktop.
Save ry5n/2026666 to your computer and use it in GitHub Desktop.
An alternative to Compass's built-in vertical-rhythm module. Only supports output values in rem, with pixel fallbacks
// Configurable variables
// ⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻
// Absolute height of body text, in pixels
$base-font-size: 16px !default;
// Absolute height of one line of type, in pixels
$base-line-height: 24px !default;
// The font unit to use when returning values in rhythm functions
$rhythm-font-unit: px !default;
// Allows the `adjust-font-size-to` mixin and the `lines-for-font-size` function
// to round the line height to the nearest half line height instead of the
// nearest integral line height to avoid large spacing between lines.
$round-to-nearest-half-line: true !default;
// Ensure there is at least this many pixels
// of vertical padding above and below the text.
$min-line-padding: 2px !default;
// Constants
// ⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻
// Most (all?) browsers use a default of 16px for type.
$browser-default-font-size: 16px;
// The height of one line of type, in rems.
$rem-line-height: $base-line-height / $base-font-size * 1rem;
// Moving parts
// ⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻
// Given pixel inputs, print rem values with pixel fallbacks.
// Based on Bitmanic's rem mixin (https://github.com/bitmanic/rem)
//
// $property - The css property name
// $px-values - The value or values (space-separated list) for $property, in pixels
@mixin px-to-rem($property, $px-values) {
// Number of pixels in 1rem (default: 16px/rem)
// When converting to rems, we must divide by this ratio.
$px-per-rem: $base-font-size / 1rem;
// Print the pixel fallback declaration first so we can override it in capable browsers.
#{$property}: $px-values;
// If there is only one value, print the declaration with the value converted to rems.
@if type-of($px-values) == "number" {
#{$property}: $px-values / $px-per-rem;
}
@else {
// Otherwise, we've got a list and we'll need to convert each value in turn.
// Create an empty list that we can dump values into.
$rem-values: ();
@each $value in $px-values {
// If the value is zero, a string or a color, leave it be.
@if $value == 0 or type-of($value) == "string" or type-of($value) == "color" {
$rem-values: append($rem-values, $value);
} @else {
$rem-values: append($rem-values, $value / $px-per-rem);
}
}
// Print the property and its list of converted values.
#{$property}: $rem-values;
}
}
// Return the height of n baselines.
// Returns px or rem based on the value of $rhythm-font-unit.
@function rhythm($lines: 1) {
$line-height: if($rhythm-font-unit != px, $rem-line-height, $base-line-height);
@return $line-height * $lines;
}
// Calculate the number of baselines required to accomodate a
// given pixel-based font size.
@function lines-for-font-size($font-size) {
$lines: if(
$round-to-nearest-half-line,
ceil(2 * $font-size / $base-line-height) / 2,
ceil($font-size / $base-line-height)
);
@if $lines * $base-line-height - $font-size < $min-line-padding * 2 {
$lines: $lines + if($round-to-nearest-half-line, 0.5, 1);
}
@return $lines;
}
// Set type size and baseline grid on the root element.
@mixin establish-baseline {
html {
$new-font-size: $base-font-size / $browser-default-font-size * 100%; // eg. 16px ÷ 16px * 100%
// Only set the font size if it differs from the browser default
@if $new-font-size != 100% {
font-size: $new-font-size;
}
@include set-leading(1);
}
}
// Set the font size to the specified number of pixels while
// maintaining the vertical rhythm.
//
// $to-size - Desired font size, in pixels
// $lines - Desired leading, expressed in baselines (can fractional)
@mixin set-font-size($to-size, $lines: lines-for-font-size($to-size)) {
@include px-to-rem(font-size, $to-size);
@include set-leading($lines);
}
// Adjust the leading to a new multiple of the baseline
@mixin set-leading($lines) {
@include px-to-rem(line-height, $base-line-height * $lines);
}
// Apply leading whitespace
@mixin leader($lines: 1, $property: margin) {
@include px-to-rem(#{$property}-top, rhythm($lines));
}
// Apply trailing whitespace
@mixin trailer($lines: 1, $property: margin) {
@include px-to-rem(#{$property}-bottom, rhythm($lines));
}
// Apply leading whitespace as padding
@mixin padding-leader($lines: 1) {
@include leader($lines, padding);
}
// Apply trailing whitespace as padding
@mixin padding-trailer($lines: 1) {
@include trailer($lines, padding);
}
// Apply leading and trailing whitespace together.
// Defaults to equal margins above and below and no padding.
@mixin rhythm-spacing($leader: 1, $trailer: $leader, $padding-leader: 0, $padding-trailer: $padding-leader) {
@include leader($leader);
@include trailer($trailer);
@include padding-leader($padding-leader);
@include padding-trailer($padding-trailer);
}
// Apply a border to one side of an element without throwing off
// the vertical rhythm. The available space ($lines) must be
// greater than the width of your border.
@mixin side-rhythm-border($side, $lines: 1, $border: $rule-width $rule-style $rule-color) {
$width: nth($border, 1);
$style: nth($border, 2);
$color: nth($border, 3);
@include px-to-rem(border-#{$side}, $width $style $color);
$padding: ($base-line-height - $width) * $lines;
@include px-to-rem(padding-#{$side}, $padding);
}
// Apply a leading rhythm border
@mixin leading-border($lines: 1, $border: $rule-width $rule-style $rule-color) {
@include side-rhythm-border(top, $lines, $border);
}
// Apply a trailing rhythm border
@mixin trailing-border($lines: 1, $border: $rule-width $rule-style $rule-color) {
@include side-rhythm-border(bottom, $lines, $border);
}
// Apply borders equally to all sides with padding to maintain the vertical rhythm
@mixin rhythm-borders($lines: 1, $border: $rule-width $rule-style $rule-color) {
$width: nth($border, 1);
$style: nth($border, 2);
$color: nth($border, 3);
@include px-to-rem(border, $width $style $color);
$padding: ($base-line-height - $width) * $lines;
@include px-to-rem(padding, $padding);
}
@ry5n
Copy link
Author

ry5n commented Jul 8, 2012

To clarify why rems help even when we have Sass to help us with the math, consider the following situation: I have an h1 set to 42px size, but within that heading I have some secondary text that I want to be 38px. If I'm using ems (as Compass' vertical rhythm module does by default), the following won't work:

$base-font-size: 16px;
$base-line-height: 24px;
// etc.
.subhead {
  @include adjust-font-size-to(38px);
}

This will result in the following CSS

.subhead {
  font-size: 2.375em;
  line-height: 1.263em;
}

The resulting font-size is intended to compute to 38px, but because its parent element's font-size is 42px, not 16px, the computed font-size will be gigantic. Likewise, the line-height won't be correct. Compass solves this by requiring us to tell it what the current font-size is, if it's not the default:

$base-font-size: 16px;
$base-line-height: 24px;
// etc.
.subhead {
  // using mixin adjust-font-size-to($to-size, $lines, $from-size)
  @include adjust-font-size-to(38px, 2, 42px);
}

That's a lot of finicky work for something that's trying to make our lives easier! Using rems we can always say @include adjust-font-size-to(38px); and not worry about the font-sizing context.

@darekrossman
Copy link

This is all I needed to get away from Compass completely. Thanks so much.

One thing that I found was that @mixin side-rhythm-border didn't calculate padding like I thought it would (unless I'm missing something intentional).

$padding: ($base-line-height - $width) * $lines;

The rem values aren't being calculated properly. For example:

(24px - 1px) * 0.5 = 11.5  // expected 11
(24px - 2px) * 0.25 = 5.5  // expected 4

Just swapping the order of operations fixed it right up:

$padding: ($base-line-height * $lines) - $width;

Again, this is fantastic stuff, thanks!

@paolodina
Copy link

I used this v/r alternative to create a simple typography module. I noted that vertical rhythm maintains perfectly in Firefox (30/Linux) but not in Chrome (35/Linux). I wonder if I did something wrong or if it's something in the vertical rhythm mixins. If someone happen to have a look and advise I'd really appreciate that, here is the minimal code showing the issue.

@raahede
Copy link

raahede commented Jan 31, 2015

Hey @ry5n - nice little set of mixins you've made.

I've made an addition in my own fork because I needed to address <hr>s. Check it out:

@mixin rhythm-rule($lines: 3, $rule: $rule-width $rule-color) {
    $width: nth($rule, 1);
    $color: nth($rule, 2);
    background-color: $color;
    @include px-to-rem(height, $width);

    $margin: ( $base-line-height * $lines - $width ) / 2;
    @include px-to-rem(margin-bottom, $margin);
    @include px-to-rem(margin-top, $margin);
}

I've used it like this:

hr {
    border: none;
    @include rhythm-rule;
}

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