// Interpolate v1.0 // This mixin generates CSS for interpolation of length properties. // It has 5 required values, including the target property, initial // screen size, initial value, final screen size and final value. // It has two optional values which include an easing property, // which is a string, representing a CSS animation-timing-function // and finally a number of bending-points, that determines how many // interpolations steps are applied along the easing function. // Author: Mike Riethmuller - @MikeRiethmuller // More information: http://codepen.io/MadeByMike/pen/a2249946658b139b7625b2a58cf03a65?editors=0100 /// /// @param {String} $property - The CSS property to interpolate /// @param {Unit} $min-screen - A CSS length unit /// @param {Unit} $min-value - A CSS length unit /// @param {Unit} $max-screen - Value to be parsed /// @param {Unit} $max-value - Value to be parsed /// @param {String} $easing - Value to be parsed /// @param {Integer} $bending-points - Value to be parsed /// // Examples on line 258 // Issues: // - kubic-bezier requires whitespace // - kubic-bezier cannot parse negative values // stylelint-disable scss/dollar-variable-pattern @mixin typography-interpolate( $property, $min-screen, $min-value, $max-screen, $max-value, $easing: 'linear', $bending-points: 2 ) { // Default Easing 'Linear' $p0: 0; $p1: 0; $p2: 1; $p3: 1; // Parse Cubic Bezier string @if (str-slice($easing, 1, 12) == 'kubic-bezier') { // Get the values between the brackets // TODO: Deal with different whitespace $i: str-index($easing, ')'); // Get index of closing bracket $values: str-slice($easing, 14, $i - 1); // Extract values between brackts $list: typography-explode($values, ', '); // Split the values into a list @debug ($list); // Cast values to numebrs $p0: typography-number(nth($list, 1)); $p1: typography-number(nth($list, 2)); $p2: typography-number(nth($list, 3)); $p3: typography-number(nth($list, 4)); } @if ($easing == 'ease') { $p0: 0.25; $p1: 1; $p2: 0.25; $p3: 1; } @if ($easing == 'ease-in-out') { $p0: 0.42; $p1: 0; $p2: 0.58; $p3: 1; } @if ($easing == 'ease-in') { $p0: 0.42; $p1: 0; $p2: 1; $p3: 1; } @if ($easing == 'ease-out') { $p0: 0; $p1: 0; $p2: 0.58; $p3: 1; } #{$property}: $min-value; @if ($easing == 'linear' or $bending-points < 1) { @media screen and (min-width: $min-screen) { #{$property}: typography-calc-interpolation( $min-screen, $min-value, $max-screen, $max-value ); } } @else { // Loop through bending points $t: 1 / ($bending-points + 1); $i: 1; $prev-screen: $min-screen; $prev-value: $min-value; @while $t * $i <= 1 { $bending-point: $t * $i; $value: typography-cubic-bezier($p0, $p1, $p2, $p3, $bending-point); $screen-int: typography-lerp($min-screen, $max-screen, $bending-point); $value-int: typography-lerp($min-value, $max-value, $value); @media screen and (min-width: $prev-screen) { #{$property}: typography-calc-interpolation( $prev-screen, $prev-value, $screen-int, $value-int ); } $prev-screen: $screen-int; $prev-value: $value-int; $i: $i + 1; } } @media screen and (min-width: $max-screen) { #{$property}: $max-value; } } // Requires several helper functions including: pow, calc-interpolation, kubic-bezier, number and explode // Math functions: // Linear interpolations in CSS as a Sass function // Author: Mike Riethmuller | https://madebymike.com.au/writing/precise-control-responsive-typography/ I @function typography-calc-interpolation( $min-screen, $min-value, $max-screen, $max-value ) { $a: calc(($max-value - $min-value) / ($max-screen - $min-screen)); $b: $min-value - $a * $min-screen; $sign: '+'; @if ($b < 0) { $sign: '-'; $b: abs($b); } @return calc(#{$a * 100}vw #{$sign} #{$b}); } // This is a crude Sass port webkits cubic-bezier function. Looking to simplify this if you can help. @function typography-solve-bexier-x($p1x, $p1y, $p2x, $p2y, $x) { $cx: 3 * $p1x; $bx: 3 * ($p2x - $p1x) - $cx; $ax: 1 - $cx - $bx; $t0: 0; $t1: 1; $t2: $x; $x2: 0; $res: 1000; @while ($t0 < $t1 or $break) { $x2: (($ax * $t2 + $bx) * $t2 + $cx) * $t2; @if (abs($x2 - $x) < $res) { @return $t2; } @if ($x > $x2) { $t0: $t2; } @else { $t1: $t2; } $t2: ($t1 - $t0) * 0.5 + $t0; } @return $t2; } @function typography-cubic-bezier($p1x, $p1y, $p2x, $p2y, $x) { $cy: 3 * $p1y; $by: 3 * ($p2y - $p1y) - $cy; $ay: 1 - $cy - $by; $t: typography-solve-bexier-x($p1x, $p1y, $p2x, $p2y, $x); @return (($ay * $t + $by) * $t + $cy) * $t; } // A stright up lerp // Credit: Ancient Greeks possibly Hipparchus of Rhodes @function typography-lerp($a, $b, $t) { @return $a + ($b - $a) * $t; } // String functions: // Cast string to number // Credit: Hugo Giraudel | https://www.sassmeister.com/gist/9fa19d254864f33d4a80 @function typography-number($value) { @if type-of($value) == 'number' { @return $value; } @else if type-of($value) != 'string' { $_: log('Value for `to-number` should be a number or a string.'); } $result: 0; $digits: 0; $minus: str-slice($value, 1, 1) == '-'; $numbers: ( '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, ); @for $i from if($minus, 2, 1) through str-length($value) { $character: str-slice($value, $i, $i); @if not(index(map-keys($numbers), $character) or $character == '.') { @return to-length(if($minus, -$result, $result), str-slice($value, $i)); } @if $character == '.' { $digits: 1; } @else if $digits == 0 { $result: $result * 10 + map-get($numbers, $character); } @else { $digits: $digits * 10; $result: $result + map-get($numbers, $character) / $digits; } } @return if($minus, -$result, $result); } // Explode a string by a delimiter // Credit: https://gist.github.com/danielpchen/3677421ea15dcf2579ff @function typography-explode($string, $delimiter) { $result: (); @if $delimiter == '' { @for $i from 1 through str-length($string) { $result: append($result, str-slice($string, $i, $i)); } @return $result; } $exploding: true; @while $exploding { $d-index: str-index($string, $delimiter); @if $d-index { @if $d-index > 1 { $result: append($result, str-slice($string, 1, $d-index - 1)); $string: str-slice($string, $d-index + str-length($delimiter)); } @else if $d-index == 1 { $string: str-slice($string, 1, $d-index + str-length($delimiter)); } @else { $result: append($result, $string); $exploding: false; } } @else { $result: append($result, $string); $exploding: false; } } @return $result; } // Using vertical rhythm methods from https://scotch.io/tutorials/aesthetic-sass-3-typography-and-vertical-rhythm // Using perfect 8/9 for low contrast and perfect fifth 2/3 for high $typography-type-scale: ( -1: 0.889rem, 0: 1rem, 1: 1.125rem, 2: 1.266rem, 3: 1.424rem ); @function typography-type-scale($level) { @if map-has-key($typography-type-scale, $level) { @return map-get($typography-type-scale, $level); } @warn 'Unknown `#{$level}` in $typography-type-scale.'; @return null; } $typography-type-scale-contrast: ( -1: 1rem, 0: 1.3333rem, 1: 1.777rem, 2: 2.369rem, 3: 3.157rem ); @function typography-type-scale-contrast($level) { @if map-has-key($typography-type-scale-contrast, $level) { @return map-get($typography-type-scale-contrast, $level); } @warn 'Unknown `#{$level}` in $typography-type-scale-contrast.'; @return null; } $typography-base-font-size: 1rem; $typography-base-line-height: $typography-base-font-size * 1.25; $typography-line-heights: ( -1: $typography-base-line-height, 0: $typography-base-line-height, 1: $typography-base-line-height * 1.5, 2: $typography-base-line-height * 1.5, 3: $typography-base-line-height * 1.5 ); @function typography-line-height($level) { @if map-has-key($typography-line-heights, $level) { @return map-get($typography-line-heights, $level); } @warn 'Unknown `#{$level}` in $line-height.'; @return null; } $typography-base-line-height-contrast: $typography-base-line-height; $typography-line-heights-contrast: ( -1: $typography-base-line-height-contrast, 0: $typography-base-line-height-contrast * 2, 1: $typography-base-line-height-contrast * 2, 2: $typography-base-line-height-contrast * 2, 3: $typography-base-line-height * 3 ); @function typography-line-height-contrast($level) { @if map-has-key($typography-line-heights-contrast, $level) { @return map-get($typography-line-heights-contrast, $level); } @warn 'Unknown `#{$level}` in $typography-line-heights-contrast.'; @return null; } // Mixing these two sets of mixins ala Rachel: @mixin typography-got-rhythm($level: 0) { @include typography-interpolate( 'font-size', $size-content-width-min, typography-type-scale($level), $size-content-width-max, typography-type-scale-contrast($level) ); @include typography-interpolate( 'line-height', $size-content-width-min, typography-line-height($level), $size-content-width-max, typography-line-height-contrast($level) ); } %typography-xxlarge { @include typography-got-rhythm(3); @extend %font-heading; } %typography-xlarge { @include typography-got-rhythm(2); @extend %font-heading; } %typography-large { @include typography-got-rhythm(1); @extend %font-heading; } %typography-medium { @include typography-got-rhythm(0); @extend %font-content; } %typography-small { @include typography-got-rhythm(-1); @extend %font-content; }