aboutsummaryrefslogtreecommitdiffstats
path: root/src/_responsive.scss
diff options
context:
space:
mode:
Diffstat (limited to 'src/_responsive.scss')
-rw-r--r--src/_responsive.scss406
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);