Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected light output of light-or-dark function #2

Open
gfellerph opened this issue Feb 22, 2023 · 0 comments
Open

Unexpected light output of light-or-dark function #2

gfellerph opened this issue Feb 22, 2023 · 0 comments

Comments

@gfellerph
Copy link

Hello, really appreciating the effort and very much looking forward to https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-contrast.

I noticed two things with the functions:

I also did some tests and was not sure if the function light-or-dark is intended to be used on its own because the following output does not seem right:

@debug (light-or-dark(rgb(30,30,30))); // => "light"
@debug (light-or-dark(rgb(25, 25, 25))); // => "dark"

rgb(30, 30, 30) should not be considered light, as writing with black color on this background is almost invisible (https://webaim.org/resources/contrastchecker/?fcolor=000000&bcolor=1E1E1E).

Here is the file I used to test using sass 1.58 with auto-migrated slash division, defined white and black variables and debug statements at the end:

@use "sass:math";

$white: #fff;
$black: #000;

// @function strip-unit($value)
// @param {number with unit} $value
@function strip-unit($value) {
  @return math.div($value, $value * 0 + 1);
}

// @function pow($base, $exponents)
// Helper: Raise a number the power of another number
// Adapted from: https://gist.github.com/hail2u/1964056
//
// @param {number} $base — The base number
// @param {number} $exponents — The exponent to which to raise $base
@function pow($base, $exponents) {
  $raised: 1;
  @for $i from 1 through $exponents {
    $raised: $raised * $base;
  }
  @return $raised;
}

// @function luminance($color)
// Helper: Calculate Luminance of a single color
// Adapted from: https://medium.com/dev-channel/using-sass-to-automatically-pick-text-colors-4ba7645d2796
// White luminance is 1, Black luminance is 0
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { luminance: luminance(#c00); }
// Output:
// .sample { luminance: 0.1283736922; }
//
@function luminance($color) {
  // Thanks voxpelli for a very concise implementation of luminance measure in sass
  // Adapted from: https://gist.github.com/voxpelli/6304812
  $rgba: red($color), green($color), blue($color);
  $rgba2: ();
  @for $i from 1 through 3 {
    $rgb: nth($rgba, $i);
    $rgb: math.div($rgb, 255);
    $rgb: if($rgb < .03928, math.div($rgb, 12.92), pow(math.div($rgb + .055, 1.055), 2));
    $rgba2: append($rgba2, $rgb);
  }
  @return (.2126 * nth($rgba2, 1) + .7152 * nth($rgba2, 2) + 0.0722 * nth($rgba2, 3))*100;
}

// @function contrast-ratio($fg, $bg)
// Helper: Calculate "readability" as defined by WCAG 2.1
// Adapted from: https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { color-contrast: contrast-ratio(#c00, #fff); }
// Output:
// .sample { color-contrast: 5.89; }
//
@function contrast-ratio($fg, $bg) {
  $luminance1: luminance($fg) + 0.05;
  $luminance2: luminance($bg) + 0.05;
  $ratio: math.div($luminance1, $luminance2);
  @if $luminance2 > $luminance1 {
    $ratio: math.div(1, $ratio);
  }
  // Round to a hundreth because 6.96 should not pass a ratio of 7.0
  $ratio: round($ratio * 100) * 0.01;
  @return $ratio;
}

// @function validate-font-size($size)
// Helper: Depending on the unit recalculate a font size value into pixels if possible
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { validate-font-size: validate-font-size(1em); }
// Output:
// .sample { validate-font-size: 16; }
//
@function validate-font-size($size) {
  @if unit($size) == 'em' or unit($size) == 'rem' or unit($size) == 'px' or unit($size) == '' {
    // Check if a flexible unit
    @if unit($size) == 'em' or unit($size) == 'rem' {
      // Need to convert to a pixel value. Let's not overcomplicate it with possible EM inheritence scale factors
      @return strip-unit($size * 16)
    }
    @if unit($size) == 'px' {
      // We expect PX, so strip the value and return it
      @return strip-unit($size);
    }
    @if unit($size) == '' {
      @return $size;
    }
  } @else {
    @error 'validate-font-size(): An unexpected font size unit was supplied.';
  }
}

// @function get-ratio($level: 'AA', $size: 16, $bold: false)
// Helper: Determine the correct ratio value to use based on font-size and WCAG Level
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { get-ratio: get-ratio('AAA', 19, true); }
// Output:
// .sample { get-ratio: 4.5; }
//
@function get-ratio($level: 'AA', $size: 16, $bold: false) {
  // Default ratio
  $ratio: 4.5;
  @if $level == 'AAA' {
    $ratio: 7;
  }

  // Make sure the size is valid. If the value is not EM, REM, or PX (preferred), we can't help
  $size: validate-font-size($size);

  // Check font size
  @if $size < 24 {
    // Small text, use defaults
    // But:
    @if $size >= 19 and $bold == true {
      // Special case: Small text but also bold
      @if $level == 'AAA' {
        $ratio: 4.5;
      } @else {
        $ratio: 3;
      }
    }
  } @else {
    // Larger than 24
    $ratio: 3;
    @if $level == 'AAA' {
      $ratio: 4.5;
    }
  }
  @return $ratio;
}

// @function light-or-dark($color)
// Helper: Use contrast against white or black to determine if a color is "light" or "dark"
// Adapted from: https://medium.com/dev-channel/using-sass-to-automatically-pick-text-colors-4ba7645d2796
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { light-or-dark: light-or-dark(#c00); }
// Output:
// .sample { light-or-dark: "light"; }
//
@function light-or-dark($color) {
  $light-contrast: contrast-ratio($color, $white);
  $dark-contrast: contrast-ratio($color, $black);

  @if $light-contrast > $dark-contrast {
    // Contrast against white is higher than against black, so, this is a dark color
    @return "dark";
  } @else {
    @return "light";
  }
}

// @function color-contrast($color)
// Helper: Returns black or white compared to a color, whichever produces the highest contrast
// Usage:
// .sample {
//   background-color: #c00;
//   color: color-contrast(#c00);
// }
// Output:
// .sample {
//   background-color: #c00;
//   color: #fff;
// }
//
// Named for the forthcoming CSS Module 5 spec function color-contrast();
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-contrast()
//
@function color-contrast($color) {
  $color-lod: light-or-dark($color);

  @if ($color-lod == "dark") {
    @return $white;
  } @else {
    @return $black;
  }
}

// @function a11y-color($fg, $bg, $level: 'AA', $size: 16, $bold: false)
// Goal: Return a color that passes for the chosen WCAG level without changing the Hue of the color
// Usage:
// .sample {
//   background-color: #000;
//   color: a11y-color(#c0c, #000);
// }
// Output:
// .sample {
//   background-color: #000;
//   color: #d200d2;
// }
//
@function a11y-color($fg, $bg, $level: 'AA', $size: 16, $bold: false) {
  // Helper: make sure the font size value is acceptable
  $font-size: validate-font-size($size);
  // Helper: With the level, font size, and bold boolean, return the proper target ratio. 3.0, 4.5, or 7.0 expected
  $ratio: get-ratio($level, $font-size, $bold);
  // Calculate the first contrast ratio of the given pair
  $original-contrast: contrast-ratio($fg, $bg);

  @if $original-contrast >= $ratio {
    // If we pass the ratio already, return the original color
    @return $fg;
  } @else {
    // Doesn't pass. Time to get to work
    // Should the color be lightened or darkened?
    $fg-lod: light-or-dark($fg);
    $bg-lod: light-or-dark($bg);

    // Set a "step" value to lighten or darken a color
    // Note: Higher percentage steps means faster compile time, but we might overstep the required threshold too far with something higher than 4%
    $step: 1%;

    // Run through some cases where we want to darken, or use a negative step value
    @if $fg-lod == 'light' and $bg-lod == 'light' {
      // Both are light colors, darken the fg
      $step: - $step;
    } @else if $fg-lod == 'dark' and $bg-lod == 'light' {
      // bg is light, fg is dark but does not pass, darken more
      $step: - $step;
    }
    // Keeping the rest of the logic here, but our default values do not change, so this logic is not needed
    //@else if $fg-lod == 'light' and $bg-lod == 'dark' {
    //  // bg is dark, fg is light but does not pass, lighten further
    //  $step: $step;
    //} @else if $fg-lod == 'dark' and $bg-lod == 'dark' {
    //  // Both are dark, so lighten the fg
    //  $step: $step;
    //}

    // The magic happens here
    // Loop through with a @while statement until the color combination passes our required ratio. Scale the color by our step value until the expression is false
    // This might loop 100 times or more depending on the colors
    @while contrast-ratio($fg, $bg) < $ratio {
      $sat-step: 0%;
      @if saturation($fg) > 10 {
        $sat-step: $step;
      }
      $fg: scale-color($fg, $lightness: $step, $saturation:$sat-step);
    }
    @return $fg;
  }
}

@debug (light-or-dark(rgb(30,30,30)));
@debug (light-or-dark(rgb(25, 25, 25)));
@debug (light-or-dark(#1E1E1E));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant