aboutsummaryrefslogtreecommitdiffstats
path: root/src/_gradients.scss
diff options
context:
space:
mode:
Diffstat (limited to 'src/_gradients.scss')
-rw-r--r--src/_gradients.scss600
1 files changed, 600 insertions, 0 deletions
diff --git a/src/_gradients.scss b/src/_gradients.scss
new file mode 100644
index 0000000..7c52d63
--- /dev/null
+++ b/src/_gradients.scss
@@ -0,0 +1,600 @@
1////
2/// Smoother background gradients.
3///
4/// The default background gradients produced by any browser have a quite harsh transition between
5/// colors. This is especially apparent if you, for example, use a strong fade-out gradient to make
6/// text in front of a background more readable.
7///
8/// The function in this file generates smoother gradients by using easing functions of the user's
9/// choice.
10/// It's essentially a more flexible alternative to the PostCSS plugin "PostCSS Easing Gradients":
11/// https://github.com/larsenwork/postcss-easing-gradients
12///
13/// @group Background gradients
14///
15/// @access public
16////
17
18///
19/// Number of intermediate color stops generated to achieve easing.
20/// A higher value results in better quality, but also much more generated code.
21///
22/// @type number
23///
24$iro-easing-gradient-steps: 10 !default;
25
26///
27/// Generate a new easing background gradient.
28/// This function is intended to be as similar as possible to the newly proposed syntax for
29/// linear-gradient and radial-gradient which includes easing hints.
30///
31/// @param {string} $type - Either 'linear' or 'radial', which means the gradient will be either a linear-gradient or a radial-gradient.
32/// @param {string} $dir - The direction of the gradient. Depending on $type, this value must be a valid direction for linear-gradient or radial-gradient.
33/// @param {color | list} $stop - A color stop as used for linear-gradient or radial-gradient.
34/// @param {arglist} $stops - More color stops as used for linear-gradient or radial-gradient. Between two color stops, you may also define an easing hint such as `ease-in-out`, `cubic-bezier 0.42 0 0.58 1`, `steps 3 jump-end`, and so on.
35///
36/// @return {string} A linear-gradient or radial-gradient with an alternative transitioning behavior.
37///
38/// @throw If $type is invalid
39///
40/// @link https://github.com/w3c/csswg-drafts/issues/1332 The new CSSWG proposal
41///
42/// @example scss - A smoother linear gradient
43/// .background {
44/// background-image: iro-easing-gradient(
45/// linear,
46/// to top,
47/// #000,
48/// in-out-sine,
49/// transparent
50/// );
51/// }
52///
53/// // Generates:
54///
55/// .background {
56/// background-image: linear-gradient(
57/// to top,
58/// black 0%,
59/// rgba(0, 0, 0, 0.975528) 10%,
60/// rgba(0, 0, 0, 0.904508) 20%,
61/// rgba(0, 0, 0, 0.793893) 30%,
62/// rgba(0, 0, 0, 0.654508) 40%,
63/// rgba(0, 0, 0, 0.5) 50%,
64/// rgba(0, 0, 0, 0.345492) 60%,
65/// rgba(0, 0, 0, 0.206107) 70%,
66/// rgba(0, 0, 0, 0.0954915) 80%,
67/// rgba(0, 0, 0, 0.0244717) 90%,
68/// rgba(0, 0, 0, 3.78257e-11) 100%
69/// );
70/// }
71///
72/// @example scss - A smoother radial gradient
73/// .background {
74/// background-image: iro-easing-gradient(
75/// radial,
76/// 50em 16em at 0 0,
77/// #000,
78/// in-out-sine,
79/// transparent
80/// );
81/// }
82///
83/// // Generates:
84///
85/// .background {
86/// background-image: radial-gradient(
87/// 50em 16em at 0 0,
88/// black 0%,
89/// rgba(0, 0, 0, 0.975528) 10%,
90/// rgba(0, 0, 0, 0.904508) 20%,
91/// rgba(0, 0, 0, 0.793893) 30%,
92/// rgba(0, 0, 0, 0.654508) 40%,
93/// rgba(0, 0, 0, 0.5) 50%,
94/// rgba(0, 0, 0, 0.345492) 60%,
95/// rgba(0, 0, 0, 0.206107) 70%,
96/// rgba(0, 0, 0, 0.0954915) 80%,
97/// rgba(0, 0, 0, 0.0244717) 90%,
98/// rgba(0, 0, 0, 3.78257e-11) 100%
99/// );
100/// }
101///
102/// @example scss - A smoother linear gradient with complex color positions
103/// .background {
104/// background-image: iro-easing-gradient(
105/// linear,
106/// to top,
107/// #000 20%,
108/// in-out-sine,
109/// transparent calc(20% + 25em)
110/// );
111/// }
112///
113/// // Generates:
114///
115/// .background {
116/// background-image: linear-gradient(
117/// to top,
118/// black 20%,
119/// rgba(0, 0, 0, 0.975528) calc(20% + (20% + 25em - 20%) * 0.1),
120/// rgba(0, 0, 0, 0.904508) calc(20% + (20% + 25em - 20%) * 0.2),
121/// rgba(0, 0, 0, 0.793893) calc(20% + (20% + 25em - 20%) * 0.3),
122/// rgba(0, 0, 0, 0.654508) calc(20% + (20% + 25em - 20%) * 0.4),
123/// rgba(0, 0, 0, 0.5) calc(20% + (20% + 25em - 20%) * 0.5),
124/// rgba(0, 0, 0, 0.345492) calc(20% + (20% + 25em - 20%) * 0.6),
125/// rgba(0, 0, 0, 0.206107) calc(20% + (20% + 25em - 20%) * 0.7),
126/// rgba(0, 0, 0, 0.0954915) calc(20% + (20% + 25em - 20%) * 0.8),
127/// rgba(0, 0, 0, 0.0244717) calc(20% + (20% + 25em - 20%) * 0.9),
128/// transparent calc(20% + 25em))
129/// );
130/// }
131///
132@function iro-easing-gradient($type, $dir, $stop, $stops...) {
133 $pos-template: null;
134 $stops: iro-list-prepend($stops, $stop);
135
136 $last-positioned-stop: 1;
137 $generated-stops: ();
138
139 //
140 // Generate gradient
141 //
142
143 @for $i from 1 through length($stops) {
144 $stop: nth($stops, $i);
145
146 @if $i == 1 {
147 @if not iro-easing-gradient-is-color-stop($stop) {
148 @error 'The first color stop argument must be a color stop.';
149 }
150
151 @if type-of($stop) == color {
152 //
153 // The first color stop is unpositioned. The default position for the first
154 // color stop is 0, which is explicitly added for easier calculations.
155 //
156
157 $stop: $stop 0;
158 $stops: set-nth($stops, $i, $stop);
159 }
160
161 $generated-stops: append($generated-stops, iro-str-implode($stop, ' '));
162 } @else if iro-easing-gradient-is-positioned-color-stop($stop) or ($i == length($stops)) {
163 @if not iro-easing-gradient-is-color-stop($stop) {
164 @error 'The last color stop argument must be a color stop.';
165 }
166
167 //
168 // Either the current stops list item is a positioned color stop, or the end of
169 // the stops list has been reached.
170 //
171
172 @if (type-of($stop) == color) and ($i == length($stops)) {
173 //
174 // The current stop is an unpositioned color stop, which means this is the end
175 // of the stops list. The default position for the last color stop is 100%, which
176 // is explicitly added for easier calculations.
177 //
178
179 $stop: $stop 100%;
180 $stops: set-nth($stops, $i, $stop);
181 }
182
183 //
184 // Now the current color stop is guaranteed to be a positioned color stop.
185 //
186
187 @if $i > $last-positioned-stop + 1 {
188 //
189 // There is at least one stops list item (unpositioned color stop or easing function)
190 // between the last positioned color stop and the current stops list item. Interpolate
191 // the positions of all stops list items that are color stops.
192 //
193
194 $interpolated-stops: iro-easing-gradient-interpolate-stop-positions(
195 nth($stops, $last-positioned-stop),
196 iro-list-slice($stops, $last-positioned-stop + 1, $i - 1),
197 $stop
198 );
199
200 $new-stops: join(
201 iro-list-slice($stops, 1, $last-positioned-stop),
202 $interpolated-stops
203 );
204 $new-stops: join(
205 $new-stops,
206 iro-list-slice($stops, $i)
207 );
208 $stops: $new-stops;
209 }
210
211 //
212 // Now all color stops between this one and the last positioned one have
213 // interpolated positions.
214 // Next task is to perform an easing transition between all color stops that
215 // have an easing function specified. The rest can be left alone since the
216 // browser will automatically apply a linear transition between them.
217 //
218
219 $j: $last-positioned-stop + 1;
220 @while $j <= $i {
221 $easing: null;
222 $prev-stop: nth($stops, $j - 1);
223 $next-stop: nth($stops, $j);
224
225 @if not iro-easing-gradient-is-color-stop($next-stop) {
226 $j: $j + 1;
227
228 $easing: $next-stop;
229 $next-stop: nth($stops, $j);
230
231 @if not iro-easing-gradient-is-color-stop($next-stop) {
232 @error 'There can be at most one interpolation hint between to color stops.';
233 }
234 }
235
236 @if $easing != null {
237 @if type-of($easing) == number {
238 @error 'Midpoint shifts are not supported.';
239 }
240
241 $easing-func: null;
242 $easing-args: ();
243
244 @if type-of($easing) == list {
245 $easing-args: iro-list-slice($easing, 2);
246 $easing: nth($easing, 1);
247 }
248
249 $generated-stops: join(
250 $generated-stops,
251 iro-easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args)
252 );
253 } @else {
254 $generated-stops: append($generated-stops, iro-str-implode($next-stop, ' '));
255 }
256
257 $j: $j + 1;
258 }
259
260 $last-positioned-stop: $i;
261 }
262 }
263
264 @if $type == 'linear' {
265 @return linear-gradient($dir, unquote(iro-str-implode($generated-stops, ', ')));
266 } @else if $type == 'radial' {
267 @return radial-gradient($dir, unquote(iro-str-implode($generated-stops, ', ')));
268 } @else {
269 @error 'Invalid gradient type: #{inspect($type)}.';
270 }
271}
272
273///
274/// Alias for iro-easing-gradient('linear',...)
275///
276/// @see {function} iro-easing-gradient
277///
278@function iro-easing-linear-gradient($dir, $stop, $stops...) {
279 @return iro-easing-gradient('linear', $dir, $stop, $stops...);
280}
281
282///
283/// Alias for iro-easing-gradient('radial',...)
284///
285/// @see {function} iro-easing-gradient
286///
287@function iro-easing-radial-gradient($dir, $stop, $stops...) {
288 @return iro-easing-gradient('radial', $dir, $stop, $stops...);
289}
290
291///
292/// Generate a smooth transition from one color stop to another using the provided easing function.
293///
294/// @access private
295///
296@function iro-easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args: ()) {
297 @if $easing == 'steps' {
298 $steps: null;
299 $jump: null;
300
301 @if length($easing-args) > 1 {
302 $steps: nth($easing-args, 1);
303 $jump: nth($easing-args, 2);
304 } @else {
305 $steps: nth($easing-args, 1);
306 $jump: jump-end;
307 }
308
309 @return iro-easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump);
310 } @else {
311 $easing-func: null;
312 @if function-exists('iro-' + $easing) {
313 $easing-func: get-function('iro-' + $easing);
314 } @else {
315 $easing-func: get-function($easing);
316 }
317
318 @return iro-easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args);
319 }
320}
321
322///
323/// Generate a smooth transition from one color stop to another using the provided cubic-bezier function.
324///
325/// @access private
326///
327@function iro-easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args: ()) {
328 $prev-stop-color: nth($prev-stop, 1);
329 $prev-stop-pos: nth($prev-stop, 2);
330 $next-stop-color: nth($next-stop, 1);
331 $next-stop-pos: nth($next-stop, 2);
332
333 $stops: ();
334
335 @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
336 //
337 // The transition color stop positions can be statically calculated.
338 //
339
340 $distance: $next-stop-pos - $prev-stop-pos;
341
342 @for $i from 1 through $iro-easing-gradient-steps {
343 $perc: $i / $iro-easing-gradient-steps;
344
345 $color: null;
346 $pos: $prev-stop-pos + $perc * $distance;
347 @if $perc == 1 {
348 $color: $next-stop-color;
349 } @else {
350 $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%);
351 }
352
353 $stops: append($stops, $color + ' ' + $pos);
354 }
355 } @else {
356 //
357 // The transition color stop positions have to be dynamically calculated with the calc() function.
358 //
359
360 @if type-of($prev-stop-pos) != number {
361 // must be calc()
362 @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) {
363 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
364 }
365
366 $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1);
367 }
368
369 @if type-of($next-stop-pos) != number {
370 // must be calc()
371 @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) {
372 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
373 }
374
375 $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1);
376 }
377
378 @for $i from 1 through $iro-easing-gradient-steps {
379 $perc: $i / $iro-easing-gradient-steps;
380
381 $color: null;
382 $pos: null;
383 @if $perc == 1 {
384 $color: $next-stop-color;
385 $pos: calc(#{$next-stop-pos});
386 } @else {
387 $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%);
388 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc});
389 }
390
391 $stops: append($stops, $color + ' ' + $pos);
392 }
393 }
394
395 @return $stops;
396}
397
398///
399/// Generate a step transition from one color stop to another.
400///
401/// @access private
402///
403@function iro-easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump: jump-end) {
404 $prev-stop-color: nth($prev-stop, 1);
405 $prev-stop-pos: nth($prev-stop, 2);
406 $next-stop-color: nth($next-stop, 1);
407 $next-stop-pos: nth($next-stop, 2);
408
409 $stops: ();
410
411 @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
412 //
413 // The transition color stop positions can be statically calculated.
414 //
415
416 $distance: $next-stop-pos - $prev-stop-pos;
417
418 @for $i from 1 through $steps {
419 $x1: ($i - 1) / $steps;
420 $x2: $i / $steps;
421 $y: null;
422
423 @if $jump == jump-start {
424 $y: $i / $steps;
425 } @else if $jump == jump-end {
426 $y: ($i - 1) / $steps;
427 } @else if $jump == jump-both {
428 $y: $i / ($steps + 1);
429 } @else if $jump == jump-none {
430 $y: ($i - 1) / ($steps - 1);
431 } @else {
432 @error 'Invalid $jump: #{inspect($jump)}';
433 }
434
435 $color: null;
436 $pos1: if($x1 == 0, $prev-stop-pos, $prev-stop-pos + $x1 * $distance);
437 $pos2: if($x2 == 1, $next-stop-pos, $prev-stop-pos + $x2 * $distance);
438
439 @if $y == 0 {
440 $color: $prev-stop-color;
441 } @else if $y == 1 {
442 $color: $next-stop-color;
443 } @else {
444 $color: mix($next-stop-color, $prev-stop-color, $y * 100%);
445 }
446
447 $stops: append($stops, $color + ' ' + $pos1);
448 $stops: append($stops, $color + ' ' + $pos2);
449 }
450 } @else {
451 //
452 // The transition color stop positions have to be dynamically calculated with the calc() function.
453 //
454
455 @if type-of($prev-stop-pos) != number {
456 // must be calc()
457 @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) {
458 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
459 }
460
461 $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1);
462 }
463
464 @if type-of($next-stop-pos) != number {
465 // must be calc()
466 @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) {
467 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
468 }
469
470 $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1);
471 }
472
473 @for $i from 1 through $steps {
474 $x1: ($i - 1) / $steps;
475 $x2: $i / $steps;
476 $y: null;
477
478 @if $jump == jump-start {
479 $y: $i / $steps;
480 } @else if $jump == jump-end {
481 $y: ($i - 1) / $steps;
482 } @else if $jump == jump-both {
483 $y: $i / ($steps + 1);
484 } @else if $jump == jump-none {
485 $y: ($i - 1) / ($steps - 1);
486 } @else {
487 @error 'Invalid $jump: #{inspect($jump)}';
488 }
489
490 $color: null;
491 $pos1: if($x1 == 0, $prev-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x1}));
492 $pos2: if($x2 == 1, $next-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x2}));
493
494 @if $y == 0 {
495 $color: $prev-stop-color;
496 } @else if $y == 1 {
497 $color: $next-stop-color;
498 } @else {
499 $color: mix($next-stop-color, $prev-stop-color, $y * 100%);
500 }
501
502 $stops: append($stops, $color + ' ' + $pos1);
503 $stops: append($stops, $color + ' ' + $pos2);
504 }
505 }
506
507 @return $stops;
508}
509
510///
511/// Interpolate the positions of multiple color stops between two color stops whose positions are set.
512///
513/// @access private
514///
515@function iro-easing-gradient-interpolate-stop-positions($prev-stop, $stops, $next-stop) {
516 $prev-stop-pos: nth($prev-stop, 2);
517 $next-stop-pos: nth($next-stop, 2);
518
519 $stops-num: 0;
520 @for $i from 1 through length($stops) {
521 $stop: nth($stops, $i);
522 @if iro-easing-gradient-is-color-stop($stop) {
523 $stops-num: $stops-num + 1;
524 }
525 }
526
527 $i: 1;
528 $cur-stop-num: 1;
529
530 @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
531 //
532 // The color stop positions can be statically calculated.
533 //
534
535 $distance: $next-stop-pos - $prev-stop-pos;
536
537 @for $i from 1 through length($stops) {
538 $stop: nth($stops, $i);
539 @if iro-easing-gradient-is-color-stop($stop) {
540 $pos: $prev-stop-pos + $distance / ($stops-num + 1) * $cur-stop-num;
541 $stops: set-nth($stops, $i, $stop $pos);
542
543 $cur-stop-num: $cur-stop-num + 1;
544 }
545 }
546 } @else {
547 //
548 // The color stop positions have to be dynamically calculated with the calc() function.
549 //
550
551 @if type-of($prev-stop-pos) != number {
552 // must be calc()
553 @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) {
554 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
555 }
556
557 $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1);
558 }
559
560 @if type-of($next-stop-pos) != number {
561 // must be calc()
562 @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) {
563 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
564 }
565
566 $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1);
567 }
568
569 @for $i from 1 through length($stops) {
570 $stop: nth($stops, $i);
571 @if iro-easing-gradient-is-color-stop($stop) {
572 $perc: $cur-stop-num / ($stops-num + 1);
573 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc});
574 $stops: set-nth($stops, $i, $stop $pos);
575
576 $cur-stop-num: $cur-stop-num + 1;
577 }
578 }
579 }
580
581 @return $stops;
582}
583
584///
585/// Check if the input is a valid color stop.
586///
587/// @access private
588///
589@function iro-easing-gradient-is-color-stop($input) {
590 @return (type-of($input) == color) or iro-easing-gradient-is-positioned-color-stop($input);
591}
592
593///
594/// Check if the input is a valid positioned color stop.
595///
596/// @access private
597///
598@function iro-easing-gradient-is-positioned-color-stop($input) {
599 @return (type-of($input) == list) and (type-of(nth($input, 1)) == color);
600}