diff options
author | Feuerfuchs <git@feuerfuchs.dev> | 2020-11-01 20:55:14 +0100 |
---|---|---|
committer | Feuerfuchs <git@feuerfuchs.dev> | 2020-11-01 20:55:14 +0100 |
commit | d07f664450ddaaebb44127a4bd057763d13d3f82 (patch) | |
tree | 234cfd673ac527869a8dda4f32afbec48c87b512 /src/_responsive.scss | |
download | iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.tar.gz iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.tar.bz2 iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.zip |
Init
Diffstat (limited to 'src/_responsive.scss')
-rw-r--r-- | src/_responsive.scss | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/src/_responsive.scss b/src/_responsive.scss new file mode 100644 index 0000000..6f2a416 --- /dev/null +++ b/src/_responsive.scss | |||
@@ -0,0 +1,406 @@ | |||
1 | //// | ||
2 | /// responsive properties. | ||
3 | /// | ||
4 | /// The mixins and functions in this file allow you to scale any px- or rem-based value depending on | ||
5 | /// the available viewport width. One popular use case is the dynamic scaling of fonts. | ||
6 | /// | ||
7 | /// The code in this file is based on an article by Niklas Postulart: | ||
8 | /// http://niklaspostulart.de/2015/10/sass-responsive-type-mixin | ||
9 | /// | ||
10 | /// The following adjustments were made: | ||
11 | /// - Support any property passed by the user, not just font-size | ||
12 | /// - Allow multiple target viewports / values | ||
13 | /// - Provide a variant of the mixin which integrates include-media for media queries | ||
14 | /// | ||
15 | /// @group Responsive | ||
16 | /// | ||
17 | /// @access public | ||
18 | //// | ||
19 | |||
20 | /// | ||
21 | /// If true, named viewports will be supported if a compatible $breakpoints map exists. | ||
22 | /// This is the case for [include-media](https://include-media.com/), for example. | ||
23 | /// | ||
24 | /// @type bool | ||
25 | /// | ||
26 | $iro-responsive-support-named-viewports: true !default; | ||
27 | |||
28 | /// | ||
29 | /// Context ID used for responsive environment-related mixins. | ||
30 | /// | ||
31 | /// @type string | ||
32 | /// | ||
33 | $iro-responsive-context-id: 'responsive' !default; | ||
34 | |||
35 | /// | ||
36 | /// Scale a property uniformly between a specific set of target viewports / values. | ||
37 | /// | ||
38 | /// @param {string | list} $props - Property or list of properties to set | ||
39 | /// @param {number} $responsive-map - A map with keys = viewports and values = target value | ||
40 | /// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next | ||
41 | /// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width | ||
42 | /// | ||
43 | /// @example scss - Responsive font-size between 2 viewports | ||
44 | /// .something { | ||
45 | /// @include iro-responsive-property(font-size, ( 320px: 20px, 720px: 30px )); | ||
46 | /// } | ||
47 | /// | ||
48 | /// // Generates: | ||
49 | /// | ||
50 | /// @media (min-width: 320px) and (max-width: 720px) { | ||
51 | /// .something { | ||
52 | /// font-size: calc(20px + 10 * ((100vw - 20px) / 400)); | ||
53 | /// } | ||
54 | /// } | ||
55 | /// | ||
56 | /// @media (max-width: 320px) { | ||
57 | /// .something { | ||
58 | /// font-size: 20px; | ||
59 | /// } | ||
60 | /// } | ||
61 | /// | ||
62 | /// @media (min-width: 720px) { | ||
63 | /// .something { | ||
64 | /// font-size: 30px; | ||
65 | /// } | ||
66 | /// } | ||
67 | /// | ||
68 | /// @example scss - Responsive font-size between 3 viewports | ||
69 | /// .something { | ||
70 | /// @include iro-responsive-property(font-size, ( 320px: 20px, 720px: 30px, 1280px: 40px )); | ||
71 | /// } | ||
72 | /// | ||
73 | /// // Generates: | ||
74 | /// | ||
75 | /// @media (min-width: 320px) and (max-width: 720px) { | ||
76 | /// .something { | ||
77 | /// font-size: calc(20px + 10 * ((100vw - 20px) / 400)); | ||
78 | /// } | ||
79 | /// } | ||
80 | /// | ||
81 | /// @media (min-width: 720px) and (max-width: 1280px) { | ||
82 | /// .something { | ||
83 | /// font-size: calc(30px + 10 * ((100vw - 30px) / 400)); | ||
84 | /// } | ||
85 | /// } | ||
86 | /// | ||
87 | /// @media (max-width: 320px) { | ||
88 | /// .something { | ||
89 | /// font-size: 20px; | ||
90 | /// } | ||
91 | /// } | ||
92 | /// | ||
93 | /// @media (min-width: 720px) { | ||
94 | /// .something { | ||
95 | /// font-size: 30px; | ||
96 | /// } | ||
97 | /// } | ||
98 | /// | ||
99 | @mixin iro-responsive-property($props, $responsive-map, $fluid: true, $vertical: false) { | ||
100 | @include iro-responsive-env(map-keys($responsive-map), $fluid, $vertical) { | ||
101 | @if type-of($props) == list { | ||
102 | @each $prop in $props { | ||
103 | #{$prop}: iro-responsive-set(map-values($responsive-map)); | ||
104 | } | ||
105 | } @else { | ||
106 | #{$props}: iro-responsive-set(map-values($responsive-map)); | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /// | ||
112 | /// Create a new responsive environment by specifying a set of viewports. | ||
113 | /// Inside a responsive environment, use the iro-responsive-set function to make a property scale automatically. | ||
114 | /// | ||
115 | /// @param {list} $viewports - Viewports sorted in ascending order | ||
116 | /// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next | ||
117 | /// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width | ||
118 | /// | ||
119 | /// @content | ||
120 | /// | ||
121 | /// @see {function} iro-responsive-set | ||
122 | /// | ||
123 | /// @example scss - Responsive font-size between 2 viewports | ||
124 | /// .something { | ||
125 | /// @include iro-responsive-env((320px, 720px)) { | ||
126 | /// font-size: iro-responsive-set(20px, 30px); | ||
127 | /// } | ||
128 | /// } | ||
129 | /// | ||
130 | /// // Generates: | ||
131 | /// | ||
132 | /// @media (min-width: 320px) and (max-width: 720px) { | ||
133 | /// .something { | ||
134 | /// font-size: calc(20px + 10 * ((100vw - 20px) / 400)); | ||
135 | /// } | ||
136 | /// } | ||
137 | /// | ||
138 | /// @media (max-width: 320px) { | ||
139 | /// .something { | ||
140 | /// font-size: 20px; | ||
141 | /// } | ||
142 | /// } | ||
143 | /// | ||
144 | /// @media (min-width: 720px) { | ||
145 | /// .something { | ||
146 | /// font-size: 30px; | ||
147 | /// } | ||
148 | /// } | ||
149 | /// | ||
150 | @mixin iro-responsive-env($viewports, $fluid: true, $vertical: false) { | ||
151 | @if length($viewports) <= 1 { | ||
152 | @error '$viewports must contain at least two viewports.'; | ||
153 | } | ||
154 | |||
155 | $new-viewports: (); | ||
156 | |||
157 | @each $viewport in $viewports { | ||
158 | @if $iro-responsive-support-named-viewports and global-variable-exists(breakpoints) { | ||
159 | @if map-has-key($breakpoints, $viewport) { | ||
160 | $viewport: map-get($breakpoints, $viewport); | ||
161 | } | ||
162 | } | ||
163 | |||
164 | @if (type-of($viewport) != number) or unitless($viewport) { | ||
165 | @error '$viewports contains invalid viewports.'; | ||
166 | } | ||
167 | |||
168 | $new-viewports: append($new-viewports, $viewport); | ||
169 | } | ||
170 | |||
171 | $viewports: iro-quicksort($new-viewports); | ||
172 | |||
173 | @if $new-viewports != $viewports { | ||
174 | @error '$viewports was not sorted in ascending order.'; | ||
175 | } | ||
176 | |||
177 | @if $fluid { | ||
178 | $first-vp: nth($viewports, 1); | ||
179 | $last-vp: nth($viewports, length($viewports)); | ||
180 | |||
181 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
182 | 'viewports': $viewports, | ||
183 | 'mode': set, | ||
184 | 'index': 1, | ||
185 | 'fluid': $fluid, | ||
186 | 'vertical': $vertical, | ||
187 | )); | ||
188 | |||
189 | @content; | ||
190 | |||
191 | @include iro-context-pop($iro-responsive-context-id); | ||
192 | |||
193 | @for $i from 1 to length($viewports) { | ||
194 | $prev-vp: nth($viewports, $i); | ||
195 | $next-vp: nth($viewports, $i + 1); | ||
196 | |||
197 | @if not $vertical { | ||
198 | @media (min-width: $prev-vp) and (max-width: $next-vp) { | ||
199 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
200 | 'viewports': $viewports, | ||
201 | 'mode': transition, | ||
202 | 'index': $i, | ||
203 | 'fluid': $fluid, | ||
204 | 'vertical': $vertical, | ||
205 | )); | ||
206 | |||
207 | @content; | ||
208 | |||
209 | @include iro-context-pop($iro-responsive-context-id); | ||
210 | } | ||
211 | } @else { | ||
212 | @media (min-height: $prev-vp) and (max-height: $next-vp) { | ||
213 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
214 | 'viewports': $viewports, | ||
215 | 'mode': transition, | ||
216 | 'index': $i, | ||
217 | 'fluid': $fluid, | ||
218 | 'vertical': $vertical, | ||
219 | )); | ||
220 | |||
221 | @content; | ||
222 | |||
223 | @include iro-context-pop($iro-responsive-context-id); | ||
224 | } | ||
225 | } | ||
226 | } | ||
227 | |||
228 | @if not $vertical { | ||
229 | @media (min-width: $last-vp) { | ||
230 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
231 | 'viewports': $viewports, | ||
232 | 'mode': set, | ||
233 | 'index': length($viewports), | ||
234 | 'fluid': $fluid, | ||
235 | 'vertical': $vertical, | ||
236 | )); | ||
237 | |||
238 | @content; | ||
239 | |||
240 | @include iro-context-pop($iro-responsive-context-id); | ||
241 | } | ||
242 | } @else { | ||
243 | @media (min-height: $last-vp) { | ||
244 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
245 | 'viewports': $viewports, | ||
246 | 'mode': set, | ||
247 | 'index': length($viewports), | ||
248 | 'fluid': $fluid, | ||
249 | 'vertical': $vertical, | ||
250 | )); | ||
251 | |||
252 | @content; | ||
253 | |||
254 | @include iro-context-pop($iro-responsive-context-id); | ||
255 | } | ||
256 | } | ||
257 | } @else { | ||
258 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
259 | 'viewports': $viewports, | ||
260 | 'mode': set, | ||
261 | 'index': 1, | ||
262 | 'fluid': $fluid, | ||
263 | 'vertical': $vertical, | ||
264 | )); | ||
265 | |||
266 | @content; | ||
267 | |||
268 | @include iro-context-pop($iro-responsive-context-id); | ||
269 | |||
270 | @for $i from 2 through length($viewports) { | ||
271 | $vp: nth($viewports, $i); | ||
272 | |||
273 | @if not $vertical { | ||
274 | @media (min-width: $vp) { | ||
275 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
276 | 'viewports': $viewports, | ||
277 | 'mode': set, | ||
278 | 'index': $i | ||
279 | )); | ||
280 | |||
281 | @content; | ||
282 | |||
283 | @include iro-context-pop($iro-responsive-context-id); | ||
284 | } | ||
285 | } @else { | ||
286 | @media (min-height: $vp) { | ||
287 | @include iro-context-push($iro-responsive-context-id, 'env', ( | ||
288 | 'viewports': $viewports, | ||
289 | 'mode': set, | ||
290 | 'index': $i | ||
291 | )); | ||
292 | |||
293 | @content; | ||
294 | |||
295 | @include iro-context-pop($iro-responsive-context-id); | ||
296 | } | ||
297 | } | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | |||
302 | /// | ||
303 | /// Make a property scale automatically inside a responsive environment. | ||
304 | /// | ||
305 | /// @param {list} $values - Value for each viewport in the responsive environment. The first value will be used for the first viewport, the second value for the second viewport, and so on. | ||
306 | /// | ||
307 | /// @return {number|string} | ||
308 | /// | ||
309 | @function iro-responsive-set($values, $without-calc: false) { | ||
310 | $noop: iro-context-assert-stack-must-contain($iro-responsive-context-id, 'env'); | ||
311 | |||
312 | $data: nth(iro-context-get($iro-responsive-context-id, 'env'), 2); | ||
313 | $viewports: map-get($data, 'viewports'); | ||
314 | $mode: map-get($data, 'mode'); | ||
315 | $fluid: map-get($data, 'fluid'); | ||
316 | $vertical: map-get($data, 'vertical'); | ||
317 | |||
318 | @if length($values) != length($viewports) { | ||
319 | @error '$values must contain the same number of items as the responsive environment\'s $viewports.'; | ||
320 | } | ||
321 | |||
322 | @if $mode == set { | ||
323 | @return nth($values, map-get($data, 'index')); | ||
324 | } @else { | ||
325 | $index: map-get($data, 'index'); | ||
326 | $prev-vp: nth($viewports, $index); | ||
327 | $next-vp: nth($viewports, $index + 1); | ||
328 | $prev-value: nth($values, $index); | ||
329 | $next-value: nth($values, $index + 1); | ||
330 | |||
331 | @return iro-responsive-fluid-calc($prev-value, $next-value, $prev-vp, $next-vp, $vertical, $without-calc); | ||
332 | } | ||
333 | } | ||
334 | |||
335 | /// | ||
336 | /// Generate the calc() function that uniformly scales a value from $min-value to $max-value depending | ||
337 | /// on the viewport width. | ||
338 | /// | ||
339 | /// @param {number} $min-value - Minimum value | ||
340 | /// @param {number} $max-value - Maximum value | ||
341 | /// @param {number} $min-viewport - Minimum viewport size | ||
342 | /// @param {number} $max-viewport - Maximum viewport size | ||
343 | /// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width | ||
344 | /// | ||
345 | /// @access private | ||
346 | /// | ||
347 | @function iro-responsive-fluid-calc($min-value, $max-value, $min-viewport, $max-viewport, $vertical: false, $without-calc: false) { | ||
348 | $value-unit: unit($min-value); | ||
349 | $max-value-unit: unit($max-value); | ||
350 | $viewport-unit: unit($min-viewport); | ||
351 | $max-viewport-unit: unit($max-viewport); | ||
352 | |||
353 | @if $min-value == 0 { | ||
354 | $value-unit: $max-value-unit; | ||
355 | } | ||
356 | @if $max-value == 0 { | ||
357 | $max-value-unit: $value-unit; | ||
358 | } | ||
359 | @if $min-viewport == 0 { | ||
360 | $viewport-unit: $max-viewport-unit; | ||
361 | } | ||
362 | @if $max-viewport == 0 { | ||
363 | $max-viewport-unit: $viewport-unit; | ||
364 | } | ||
365 | |||
366 | @if ($value-unit != $max-value-unit) or ($viewport-unit != $max-viewport-unit) { | ||
367 | @error 'Units of $min-value and $max-value, $min-viewport and $max-viewport must match.'; | ||
368 | } | ||
369 | |||
370 | @if ($value-unit == rem) and ($viewport-unit == px) { | ||
371 | $min-viewport: iro-px-to-rem($min-viewport); | ||
372 | $max-viewport: iro-px-to-rem($max-viewport); | ||
373 | $viewport-unit: rem; | ||
374 | } @else if ($value-unit == px) and ($viewport-unit == rem) { | ||
375 | $min-value: iro-px-to-rem($min-value); | ||
376 | $max-value: iro-px-to-rem($max-value); | ||
377 | $value-unit: rem; | ||
378 | } | ||
379 | |||
380 | @if $value-unit != $viewport-unit { | ||
381 | @error 'This combination of units is not supported.'; | ||
382 | } | ||
383 | |||
384 | $value-diff: iro-strip-unit($max-value - $min-value); | ||
385 | $viewport-diff: iro-strip-unit($max-viewport - $min-viewport); | ||
386 | |||
387 | $calc: ''; | ||
388 | |||
389 | @if $min-value != 0 { | ||
390 | $calc: '#{$min-value} + '; | ||
391 | } | ||
392 | |||
393 | @if not $vertical { | ||
394 | $calc: unquote('#{$calc}#{$value-diff} * ((100vw - #{$min-viewport}) / #{$viewport-diff})'); | ||
395 | } @else { | ||
396 | $calc: unquote('#{$calc}#{$value-diff} * ((100vh - #{$min-viewport}) / #{$viewport-diff})'); | ||
397 | } | ||
398 | |||
399 | @if $without-calc { | ||
400 | @return $calc; | ||
401 | } @else { | ||
402 | @return calc(#{$calc}); | ||
403 | } | ||
404 | } | ||
405 | |||
406 | @include iro-context-stack-create($iro-responsive-context-id); | ||