├── .gitignore ├── .npmignore ├── package.json ├── LICENSE ├── readme.md └── src └── index.scss /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gitignore 3 | example 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@union/scss-slope-calc", 3 | "version": "2.1.7", 4 | "main": "src/index.scss", 5 | "author": "UNION (http://union.co)", 6 | "repository": { 7 | "type": "git", 8 | "url" : "https://github.com/unionco/scss-slope-calc.git" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Union 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Slope Calc 2 | ========== 3 | 4 | ## Notes|Caveats 5 | 6 | Slope-calc import must come at the end of your `@use` rules (if applicable), but before all other non-use rules so that we can properly use "sass:math" and prevent the following error 7 | `SassError: @use rules must be written before any other rules.` 8 | 9 | ``` scss 10 | // app.scss 11 | 12 | @charset "UTF-8"; 13 | @use X; 14 | @use Y; 15 | @import "@union/scss-slope-calc"; 16 | // (^ This places `@use "sass:math";` before non-use rules, followed by slope-calc 17 | ``` 18 | 19 | ## Example 20 | 21 | Scss Input: 22 | ```scss 23 | .my-widget { 24 | @include slope-calc(padding, 20px 320px, 60px 1440px); 25 | } 26 | ``` 27 | 28 | CSS Result: 29 | ```css 30 | .my-widget { 31 | padding: calc(3.57143vw + .53571rem); 32 | } 33 | ``` 34 | 35 | ## Usage 36 | 37 | The first argument is a single property or a list of properties (space separated). 38 | 39 | The following arguments are lists containing 2 numbers. The first item is the target value. 40 | The second item is the screen width that the property should equal the first value. 41 | 42 | ## Flags 43 | Flags are an optional argument passed in as the last argument. 44 | In scss, strings work with or without quotes. 45 | 46 | - `important` 47 | apply the css "!important" flag to the property (or properties) at all breakpoints. 48 | 49 | Scss Input: 50 | ```scss 51 | .my-widget { 52 | @include slope-calc(padding, 20px 320px, 60px 1440px, 60px 1441px, important); 53 | } 54 | ``` 55 | 56 | CSS Result: 57 | ```css 58 | .my-widget { 59 | padding: calc(3.57143vw + .53571rem) !important; 60 | } 61 | 62 | @media only screen and (min-width: 90em) { 63 | .my-widget { 64 | padding: 3.75rem !important; 65 | } 66 | } 67 | ``` 68 | 69 | - `clip` 70 | add a media query at the largest breakpoint that sets the property to a fixed value 71 | 72 | Scss Input: 73 | ```scss 74 | .my-widget { 75 | @include slope-calc(padding, 20px 320px, 60px 1440px, clip); 76 | } 77 | ``` 78 | 79 | CSS Result: 80 | ```css 81 | .my-widget { 82 | padding: calc(3.57143vw + .53571rem); 83 | } 84 | 85 | @media only screen and (min-width: 90em) { 86 | .my-widget { 87 | padding: 3.75rem; 88 | } 89 | } 90 | ``` 91 | 92 | ## More Examples 93 | 94 | Scss Input: 95 | ```scss 96 | .my-widget { 97 | @include slope-calc(margin padding, 20px 320px, 60px 1440px, clip); 98 | } 99 | ``` 100 | 101 | CSS Result: 102 | ```css 103 | .my-widget { 104 | margin: calc(3.57143vw + .53571rem); 105 | padding: calc(3.57143vw + .53571rem); 106 | } 107 | 108 | @media only screen and (min-width: 90em) { 109 | .my-widget { 110 | margin: 3.75rem; 111 | } 112 | } 113 | @media only screen and (min-width: 90em) { 114 | .my-widget { 115 | padding: 3.75rem; 116 | } 117 | } 118 | ``` 119 | 120 | ## Browser Support 121 | - IE 9+ 122 | - Chrome 19+ 123 | - Firefox 4+ 124 | - Safari 6+ 125 | - Android 4.4+ 126 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | //////////////////////////// 2 | // Slope Calc Mixin // 3 | //////////////////////////// 4 | // 5 | // Slope-calc import must come at the end of your `@use` rules (if applicable), but before all other non-use rules so that we can properly use "sass:math" and prevent the following error 6 | // `SassError: @use rules must be written before any other rules.` 7 | // 8 | // /app.scss 9 | // ``` 10 | // @charset "UTF-8"; 11 | // @use X; 12 | // @use Y; 13 | // @import "@union/scss-slope-calc"; 14 | // (^ This places `@use "sass:math";` at the top of your stylesheet followed by slope-calc 15 | // ``` 16 | // 17 | // 18 | // Usage: 19 | // 20 | // .my-widget { 21 | // @include slope-calc(padding, 20px 320px, 60px 1440px, 60px 1441px); 22 | // } 23 | // 24 | // Outputs: 25 | // 26 | // .my-widget { 27 | // padding: calc(3.57143vw + .53571rem); 28 | // } 29 | // 30 | // @media only screen and (min-width: 90em) { 31 | // .my-widget { 32 | // padding: 3.75rem; 33 | // } 34 | // } 35 | // 36 | // 37 | // Flags: 38 | // 39 | // Flags are an optional argument passed in at the end of the slope-calc include. 40 | // In scss, strings work with or without quotes. 41 | // 42 | // - important 43 | // apply the css "!important" flag to the property (or properties) at all breakpoints. 44 | // 45 | // - clip 46 | // add a media query at the largest breakpoint that sets the property to a fixed value 47 | // 48 | // 49 | @use "sass:math"; 50 | 51 | @function remove($list, $value, $recursive: false) { 52 | $result: (); 53 | 54 | @for $i from 1 through length($list) { 55 | @if type-of(nth($list, $i)) == list and $recursive { 56 | $result: append($result, remove(nth($list, $i), $value, $recursive)); 57 | } 58 | 59 | @else if nth($list, $i) != $value { 60 | $result: append($result, nth($list, $i)); 61 | } 62 | } 63 | 64 | @return $result; 65 | } 66 | 67 | @function strip-unit($number) { 68 | @if type-of($number) == 'number' and not unitless($number) { 69 | @return math.div($number, $number * 0 + 1); 70 | } 71 | @return $number; 72 | } 73 | 74 | @function px-to-em($number, $base:16) { 75 | @return math.div(strip-unit($number), $base) * 1em; 76 | } 77 | 78 | @function unitless-px($number, $base:16) { 79 | @if(unitless($number)) { 80 | @return $number; 81 | } 82 | @else { 83 | $unit: unit($number); 84 | $number: strip-unit($number); 85 | 86 | @if ($unit == 'px') { 87 | @return $number; 88 | } 89 | @return $number*$base; 90 | } 91 | } 92 | 93 | @function rem($number, $base:16) { 94 | @return math.div(unitless-px($number), $base) * 1rem; 95 | } 96 | 97 | @function slope-calc-internal($from, $to) { 98 | $to-size: unitless-px(nth($to, 1)); 99 | $to-breakpoint: math.div(unitless-px(nth($to, 2)), 100); 100 | 101 | $from-size: unitless-px(nth($from, 1)); 102 | $from-breakpoint: math.div(unitless-px(nth($from, 2)), 100); 103 | 104 | @if($from-breakpoint > $to-breakpoint) { 105 | $tmp: $to-breakpoint; 106 | $to-breakpoint: $from-breakpoint; 107 | $from-breakpoint: $tmp; 108 | 109 | $tmp: $to-size; 110 | $to-size: $from-size; 111 | $from-size: $tmp; 112 | } 113 | 114 | $pieces: rem($from-size); 115 | $calc: $pieces; 116 | 117 | @if $from-size != $to-size { 118 | $slope: math.div($to-size - $from-size, $to-breakpoint - $from-breakpoint); 119 | $y-intercept: $from-size - ($from-breakpoint * $slope); 120 | 121 | $pieces: #{$slope*1vw} #{math.div($y-intercept, 16)*1rem}; 122 | $calc: calc(#{nth($pieces, 1)} + #{nth($pieces, 2)}); 123 | } 124 | 125 | @return ( 126 | pieces: $pieces, 127 | calc: $calc, 128 | breakpoint: px-to-em($from-breakpoint*100px) 129 | ); 130 | } 131 | 132 | @function slope-calc($mobile, $desktop) { 133 | @return map-get(slope-calc-internal($mobile, $desktop), calc); 134 | } 135 | 136 | @function slope-calc-transform($prop, $from, $to, $args...) { 137 | $pieces: map-get(slope-calc-internal($from, $to), pieces); 138 | @if type-of($pieces) == 'list' { 139 | @return #{$prop + '(' + nth($pieces, 1) + ')'} #{$prop + '(' + nth($pieces, 2) + ')'}; 140 | } 141 | @return #{$prop + '(' + $pieces + ')'}; 142 | } 143 | 144 | @function slope-calc-args($slopes) { 145 | $args: nth($slopes, length($slopes)); 146 | @if type-of($args) == 'string' or type-of($args) == 'list' and type-of(nth($args, 1)) == 'string' { 147 | $slopes: remove($slopes, $args); 148 | @if type-of($args) == 'string' { 149 | $args: append((), $args); 150 | } 151 | } 152 | @else { 153 | $args: (); 154 | } 155 | 156 | @return ( 157 | args: $args, 158 | slopes: $slopes 159 | ); 160 | } 161 | 162 | @mixin slope-calc($prop, $slopes...) { 163 | 164 | @if type-of($prop) == 'list' { 165 | @each $p in $prop { 166 | @include slope-calc($p, $slopes...); 167 | } 168 | } 169 | @else { 170 | $getArgs: slope-calc-args($slopes); 171 | $args: map-get($getArgs, args); 172 | $slopes: map-get($getArgs, slopes); 173 | 174 | $smallest: nth($slopes, 1); 175 | 176 | @each $slope in $slopes { 177 | @if unitless-px(nth($slope, 2)) < unitless-px(nth($smallest, 2)) { 178 | $smallest: $slope; 179 | } 180 | } 181 | 182 | $slopes: remove($slopes, $smallest); 183 | $secondSmallest: nth($slopes, 1); 184 | 185 | @each $slope in $slopes { 186 | @if unitless-px(nth($slope, 2)) < unitless-px(nth($secondSmallest, 2)) { 187 | $secondSmallest: $slope; 188 | } 189 | } 190 | 191 | $results: slope-calc-internal($smallest, $secondSmallest); 192 | $value: map-get($results, calc) if(index($args, important) != null, !important, null); 193 | 194 | @if (nth($secondSmallest, 2) == (nth($smallest, 2) + 1)) { 195 | $value: rem(unitless-px(nth($smallest, 1))); 196 | } 197 | 198 | @if index($args, media-query) == null { 199 | #{$prop}: $value; 200 | } 201 | @else { 202 | @media only screen and (min-width: #{map-get($results, breakpoint)}) { 203 | #{$prop}: $value; 204 | } 205 | } 206 | 207 | @if (index($args, media-query) == null) { 208 | $args: append($args, media-query); 209 | } 210 | @if length($slopes) > 1 { 211 | $slopes: append($slopes, $args); 212 | @include slope-calc($prop, $slopes...); 213 | } 214 | @else if (index($args, clip) != null) { 215 | $args: remove($args, clip); 216 | $slope: nth($slopes, 1); 217 | $slopes: $slope, nth($slope, 1) nth($slope, 2) + 1, $args; 218 | @include slope-calc($prop, $slopes...); 219 | } 220 | } 221 | } 222 | 223 | @mixin font-size($slopes...) { 224 | @include slope-calc(font-size, $slopes...); 225 | } --------------------------------------------------------------------------------