All files IdenticonOrImageElement.js

100% Statements 19/19
71.42% Branches 10/14
100% Functions 8/8
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79      1x   1x 1x 1x     1x 1x             16x         112x                               16x   16x 16x     400x               16x     1x   5x     11x     2x     16x               1x      
// Minidenticons MIT Credit to https://github.com/laurentpayot/minidenticons
 
// density of 4 for the lowest probability of collision
const SQUARE_DENSITY = 4;
// 18 different colors only for easy distinction
const COLORS_NB = 18;
const DEFAULT_SATURATION = 50;
const DEFAULT_LIGHTNESS = 50;
 
// 32 bit FNV-1a hash parameters
const FNV_PRIME = 16777619;
const OFFSET_BASIS = 2166136261;
 
// based on the FNV-1a hash algorithm, modified for *signed* 32 bit integers http://www.isthe.com/chongo/tech/comp/fnv/index.html
/**
 * @param str
 */
function simpleHash(str) {
  return (
    str
      .split('')
      // >>> 0 for 32 bit unsigned integer conversion https://2ality.com/2012/02/js-integers.html
      .reduce(
        (hash, char) => ((hash ^ char.charCodeAt(0)) >>> 0) * FNV_PRIME,
        OFFSET_BASIS
      )
  );
}
 
/**
 * @param username
 * @param saturation
 * @param lightness
 */
export function identicon(
  username,
  saturation = DEFAULT_SATURATION,
  lightness = DEFAULT_LIGHTNESS
) {
  const hash = simpleHash(username);
  // dividing hash by FNV_PRIME to get last XOR result for better color randomness (will be an integer except for empty string hash)
  const hue = ((hash / FNV_PRIME) % COLORS_NB) * (360 / COLORS_NB);
  const rects = [...Array(username ? 25 : 0).keys()]
    // 2 + ((3 * 5 - 1) - modulo) to concentrate squares at the center
    .map((i) =>
      hash % (16 - (i % 15)) < SQUARE_DENSITY
        ? `<rect x="${i > 14 ? 7 - ~~(i / 5) : ~~(i / 5)}" y="${
            i % 5
          }" width="1" height="1"/>`
        : ''
    )
    .join('');
  // xmlns attribute added in case of SVG file generation https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg#sect1
  return `<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(${hue} ${saturation}% ${lightness}%)">${rects}</svg>`;
}
 
class IdenticonOrImageElement extends HTMLElement {
  connectedCallback() {
    this.identiconSvg();
  }
  attributeChangedCallback() {
    this.identiconSvg();
  }
  static get observedAttributes() {
    return ['username', 'saturation', 'lightness'];
  }
  identiconSvg() {
    this.innerHTML = identicon(
      this.getAttribute('username') || '',
      this.getAttribute('saturation') || DEFAULT_SATURATION,
      this.getAttribute('lightness') || DEFAULT_LIGHTNESS
    );
  }
}
 
customElements.define('identicon-or-image', IdenticonOrImageElement);
 
export { IdenticonOrImageElement };