// Randomize.scss // https://github.com/mknadler/randomize.scss @mixin keyframes($name) { @-webkit-keyframes #{$name} { @content; } @keyframes #{$name} { @content; } } @function limit($num, $min: 0, $max: 1) { @if $max < $min { @error 'max isnt greater than min'; } @if $num <= $max and $num >= $min { @return $num }; @if $num < $min { @return $min }; @if $num > $max { @return $max }; } /// Generates a random integer within a given range, inclusive of both the minimum and maximum values passed in (so, for example, a call of this function with a $min of 5 could potentially return 5). /// @param {Number} $min - The lowest integer /// @param {Number} $max - The highest integer /// /// @example scss /// // SCSS /// div.widget { /// width: random-between(30px, 100px); /// } /// /// @example css /// // CSS /// div.widget { /// width: 58px; /// } @function random-between($min, $max) { @if type-of($min) != 'number' or type-of($max) != 'number' or $min < 0 or $max < 0 or $min % 1 != 0 or $max % 1 != 0 { @error '$min and $max must both be positive integers.'; } @if $min > $max { $old_min: $min; $min: $max; $max: $old_min; } @return ($min - 1) + random(($max - $min) + 1); } /// Generates a random decimal within a given range, inclusive of both the minimum and maximum values passed in (so, for example, a call of this function with a $min of 0.1 could potentially return 0.1). /// @param {Number} $min - The lowest number /// @param {Number} $max - The highest number /// /// @example scss /// // SCSS /// div.widget { /// width: random-decimal-between(30.5px, 30.9px); /// } /// /// @example css /// // CSS /// div.widget { /// width: 30.79564px; /// } @function random-decimal-between($min, $max) { @if type-of($min) != 'number' or type-of($max) != 'number' or $min < 0 or $max < 0 { @error '$min and $max must both be positive numbers.'; } @if $min > $max { $old_min: $min; $min: $max; $max: $old_min; } @return (($max - $min) * random()) + $min; } /// Generates a random color. /// /// @example scss /// // SCSS /// span { /// color: random-hex(); /// } /// /// @example css /// // CSS /// span { /// color: #C39A9F; /// } @function random-hex() { $rgb: ''; $i: 6; @while $i > 0 { $hex-unit: random(16) - 1; $letters: 'A', 'B', 'C', 'D', 'E', 'F'; @if $hex-unit > 9 { $hex-unit: nth($letters, ($hex-unit - 9)); } $rgb: str-insert($rgb, ('' + $hex-unit), 0); $i: $i - 1; } @return unquote('#' + '#{$rgb}'); } /// Generates a random color. By default, it is unbiased, and the colors will almost certainly be translucent. Optionally, you can pass a list of multipliers to weigh the output. For instance, if you pass a red modifier of 2, the function will multiply the maximum red value, 255, by 2, getting 510, find a random number between 1 and 510, and, finally, return that number as the R value, allowing any number over 255 to be clamped to 255. This means that a red multiplier of 2 means that there is a ~50% chance of the red value being 255; a ~67% chance that a red multiplier of 3 will result in a red value of 255; and so on. /// @param {List} $multiplier (1, 1, 1) - Red, green, and blue multipliers /// @param {Boolean} $opacity (false) - Whether the output should be opaque /// /// @example scss /// // SCSS /// span { /// color: random-rgba(3, 1, 1); /// } /// /// @example css /// // CSS /// span { /// color: rgba(229, 240, 108); /// } @function random-rgba($multiplier: (1, 1, 1), $opacity: false) { @each $val in $multiplier { @if $val < 0 or $val == 0 { @error 'RGBa multipliers must be positive.'; } } $r: round(255 * nth($multiplier, 1)); $g: round(255 * nth($multiplier, 2)); $b: round(255 * nth($multiplier, 3)); @if type-of($opacity) == 'number' { @if $opacity < 0 or $opacity > 1 { @warn 'opacity should be a number between 0 and 1: `#{$opacity}` given. As a result, the color will be fully opaque.'; $opacity: 1; } @return rgba(random($r), random($g), random($b), $opacity); } @return rgba(random($r), random($g), random($b), random()); } /// Given a hue value, generates a color with random saturation and lightness, and, if the opacity parameter default of 1 is overriden, random opacity. To increase the chances of a somewhat-expected color being output, saturation is clamped between 20 and 100, and lightness between 20 and 80. /// @param {Number} $hue - A hue value between 0 and 360 /// @param {Number} $opacity (1) - The opacity of the generated color /// /// @example scss /// // SCSS /// span { /// color: random-color-by-hue(230); /// } /// /// @example css /// // CSS /// span { /// color: #1d35af; /// } @function random-color-by-hue($hue, $opacity: 1) { @if $hue < 0 { @warn 'hue must be greater than 0, but `#{$hue}` given. Clamping to 0'; $hue: 0; } @if $hue > 360 { $hue: $hue % 360; } @if $opacity < 0 or $opacity > 1 { @warn 'opacity should be a number between 0 and 1: `#{$opacity}` given. As a result, the color will be fully opaque.'; $opacity: 1; } @return hsla($hue, random-between(20, 100), random-between(20, 80), $opacity); } /// Given saturation and lightness values, generates a color with random hue, and, if the opacity parameter default of 1 is overriden, random opacity. /// @param {Number} $saturation(100) - A saturation value between 0 and 100 /// @param {Number} $lightness(50) - A lightness value between 0 and 100 /// @param {Number} $opacity (1) - The opacity of the generated color /// /// @example scss /// // SCSS /// span { /// color: color-with-random-hue(); /// } /// /// @example css /// // CSS /// span { /// color: #00fff7; /// } @function color-with-random-hue($saturation: 100, $lightness: 50, $opacity: 1) { $saturation: limit($saturation, 0, 100); $lightness: limit($lightness, 0, 100); $opacity: limit($opacity); @return hsla(random-between(0, 360), percentage($saturation/100), percentage($lightness/100), $opacity); } /// Given a list, returns a random item from that list. N.B. -- in the current implementation, if random-value is passed a comma-delineated list, that list must be passed in parentheses. /// @param {List} $value-list - The list of values to randomly select from /// /// @example scss /// // SCSS /// div { /// animation: 2s ease random-value(ease ease-in-out linear); /// border-radius: random-value((2rem, 5px, 10%)); /// } /// /// @example css /// // CSS /// div { /// animation: 2s ease linear; /// border-radius: 5px; /// } @function random-value($value-list) { $value-to-find: random(length($value-list)); @return nth($value-list, $value-to-find); } /// Generates a value from a simulated dice roll. The input is in the form of, for instance, 2d6 to refer to rolling two six-sided dice; 5d8 to refer to rolling five eight-sided dice; and so on. /// @param {number} $die - The number and type of dice to roll /// /// @example scss /// // SCSS /// div { /// height: 2d6 * 10px; /// } /// /// @example css /// // CSS /// div { /// height: 80px; /// } @function roll($die) { // method to typecast strings to numbers adapted from http://hugogiraudel.com/2014/01/15/sass-string-to-number/ // Account for a few potential mistakes in input @if type-of($die) != 'number' { @error '$die, "#{$die}", was not a number; was expecting input in the format of, e.g., 2d6'; } @if unitless($die) { @error '$die, "#{$die}", was unitless; was expecting input in the format of, e.g., 2d6'; } // Parse the input into two strings $num-of-dice: str-slice($die + '', 1, str-index($die + '', d) - 1); $die-max: str-slice($die + '', str-index($die + '', d) + 1); // Define some variables at a scope outside the loops below $result: 0; $number-storage: 0; @each $target in $num-of-dice, $die-max { $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; // For each digit in each number-string, convert it from a string to a number @for $i from 1 through str-length(#{$target}) { $char: str-slice($target, $i, $i); $index: index($strings, $char); $number-storage: $number-storage * 10 + $index - 1; } // Assign the results of the above conversion to their proper vars @if $target == $num-of-dice { $num-of-dice: $number-storage; $number-storage: 0; } @else if $target == $die-max { $die-max: $number-storage; } } // Finally, roll the dice! @for $i from 1 through $num-of-dice { $result: $result + random($die-max); } @return $result; } /// Given a list, applies a Fisher-Yates shuffle to that list. /// @param {list} $list - The list to shuffle /// /// @example scss /// // SCSS /// $colors: red, orange, yellow, blue, green; /// $colors: shuffle($colors); /// div:after { /// content: $colors; /// } /// /// @example css /// // CSS /// div: after { /// content: blue, yellow, red, orange, green; /// } @function shuffle($list) { $list-length: length($list); @while($list-length > 0) { $rand: random($list-length); $temp: nth($list, $rand); $list: set-nth($list, $rand, nth($list, $list-length)); $list: set-nth($list, $list-length, $temp); $list-length: $list-length - 1; } @return $list; } /// Randomly animates a number of nth-children. /// @param {Number} $num-elements - The number of elements to animate /// @param {String} $prop-to-animate - The property of those elements which should be animated /// @param {List} $animation-props - The animation properties to use for the animation /// @param {Number} $steps - The number of times each element will be animated /// @param {String} $function-name - The randomizing function to be called for use in the animation /// @param {String} $args... - Parameters to be passed to the $function-name function /// /// @example scss /// // SCSS /// // assuming that there are 2 spans with a class of 'demo', and that they are collectively the first (or only) children of their parent: /// span.demo { /// @include random-animate(3, color, 3s ease, 3, random-rgba, (1, 1, 1), .7); /// } /// /// @example css /// // CSS /// span:nth-child(2) { /// -webkit-animation: ur8mqcnb1 3s ease; /// animation: ur8mqcnb1 3s ease; /// } /// @-webkit-keyframes ur8mqcnb1 { /// 0% { /// color: rgba(133, 150, 105, 0.7); /// } /// 50% { /// color: rgba(202, 105, 191, 0.7); /// } /// 100% { /// color: rgba(59, 238, 72, 0.7); /// } /// } /// @keyframes ur8mqcnb1 { /// 0% { /// color: rgba(25, 229, 252, 0.7); /// } /// 50% { /// color: rgba(128, 54, 10, 0.7); /// } /// 100% { /// color: rgba(49, 146, 146, 0.7); /// } /// } /// span:nth-child(1) { /// -webkit-animation: ur8mqcnba 3s ease; /// animation: ur8mqcnba 3s ease; /// } /// @-webkit-keyframes ur8mqcnba { /// 0% { /// color: rgba(61, 183, 140, 0.7); /// } /// 50% { /// color: rgba(169, 197, 140, 0.7); /// } /// 100% { /// color: rgba(241, 51, 216, 0.7); /// } /// } /// @keyframes ur8mqcnba { /// 0% { /// color: rgba(47, 37, 255, 0.7); /// } /// 50% { /// color: rgba(192, 105, 111, 0.7); /// } /// 100% { /// color: rgba(70, 183, 226, 0.7); /// } /// } @mixin random-animate($num-elements, $prop-to-animate, $animation-props, $steps, $function-name, $args...) { @if (type-of($steps) != 'number') or ($steps % 1 != 0) or ($steps < 2) { @error '$steps must be an integer with a value of at least 1.'; } @if (type-of($num-elements) != 'number') or ($num-elements % 1 != 0) or ($num-elements < 1) { @error '$num-elements must be a positive integer.'; } @if not function-exists($function-name) { @error 'The given function name, `#{$function-name}`, does not exist'; } @while $num-elements > 0 { $animation-name: unique-id(); &:nth-child(#{$num-elements}) { @include keyframes($animation-name) { $step-list: (); $steps-to-add: $steps; @while $steps-to-add > 0 { $step-list: join(floor(($steps-to-add - 1) * (100 / ($steps - 1))), $step-list); $steps-to-add: $steps-to-add - 1; } $steps-length: length($step-list); @for $i from 1 through $steps-length { #{nth($step-list, $i)}% { #{$prop-to-animate}: call($function-name, $args...); } } } -webkit-animation: $animation-name $animation-props; animation: $animation-name $animation-props; } $num-elements: $num-elements - 1; } } /// Given a number of elements, a property, and a function to call upon those elements, call the function separateley on each property of each element; like random-animate, but without the whole 'animation' part. /// @param {Number} $number-of-items - The number of elements to be iterated over /// @param {String} $property - The property of those elements which will call the function /// @param {String} $function - The function to be called on each element /// @param {List} $args... - Parameters, if any, to be passed to that function /// /// @example scss /// // SCSS /// span { /// color: random-iterate(3, color, random-hex); /// } /// /// @example css /// // CSS /// span:nth-child(3) { /// color: #488176; /// } /// span:nth-child(2) { /// color: #0C0FC3; /// } /// span:nth-child(1) { /// color: #DB47C4; /// } @mixin random-iterate($number-of-items, $property, $function, $args...) { @while $number-of-items > 0 { &:nth-child(#{$number-of-items}) { #{$property}: call($function, $args...); } $number-of-items: $number-of-items - 1; } }