-
-
Save ry5n/2026666 to your computer and use it in GitHub Desktop.
// 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); | |
} |
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!
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.
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;
}
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:This will result in the following CSS
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:
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.