Skip to content

typescript version #64

@eggp

Description

@eggp
/* Detect-zoom
 * -----------
 * Cross Browser Zoom and Pixel Ratio Detector
 * Version 1.0.4 | Apr 1 2013
 * dual-licensed under the WTFPL and MIT license
 * Maintained by https://github/tombigel
 * Original developer https://github.com/yonran
 */

export class DetectZoom {
  constructor(private window: Window) {}
  /**
   * Use devicePixelRatio if supported by the browser
   */
  get devicePixelRatio(): number {
    return this.window.devicePixelRatio || 1;
  }

  /**
   * Fallback function to set default values
   */
  private fallback() {
    return {
      zoom: 1,
      devicePxPerCssPx: 1,
    };
  }

  /**
   * IE 8 and 9: no trick needed!
   */
  private ie8() {
    const zoom = Math.round(((this.window.screen as any).deviceXDPI / (this.window.screen as any).logicalXDPI) * 100) / 100;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  /**
   * For IE10 we need to change our technique again...
   * thanks https://github.com/stefanvanburen
   */
  private ie10() {
    const zoom = Math.round((this.window.document.documentElement.offsetHeight / this.window.innerHeight) * 100) / 100;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  /**
   * For chrome
   */
  private chrome() {
    const zoom = Math.round((this.window.outerWidth / this.window.innerWidth) * 100) / 100;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  /**
   * For safari (same as chrome)
   */
  private safari() {
    const zoom = Math.round((this.window.document.documentElement.clientWidth / this.window.innerWidth) * 100) / 100;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  /**
   * Mobile WebKit
   * the trick: window.innerWIdth is in CSS pixels, while
   * screen.width and screen.height are in system pixels.
   * And there are no scrollbars to mess up the measurement.
   */
  private webkitMobile() {
    const deviceWidth = Math.abs(this.window.screen.orientation.angle) === 90 ? screen.height : screen.width;
    const zoom = deviceWidth / window.innerWidth;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  /**
   * Desktop Webkit
   * the trick: an element's clientHeight is in CSS pixels, while you can
   * set its line-height in system pixels using font-size and
   * -webkit-text-size-adjust:none.
   * device-pixel-ratio: http://www.webkit.org/blog/55/high-dpi-web-sites/
   *
   * Previous trick (used before http://trac.webkit.org/changeset/100847):
   * documentElement.scrollWidth is in CSS pixels, while
   * document.width was in system pixels. Note that this is the
   * layout width of the document, which is slightly different from viewport
   * because document width does not include scrollbars and might be wider
   * due to big elements.
   */
  private webkit() {
    const important = str => str.replace(/;/g, ' !important;');

    const div = this.window.document.createElement('div');
    div.innerHTML = '1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>0';
    div.setAttribute(
      'style',
      important(
        'font: 100px/1em sans-serif; -webkit-text-size-adjust: none; text-size-adjust: none; height: auto; width: 1em; padding: 0; overflow: visible;'
      )
    );

    // The container exists so that the div will be laid out in its own flow
    // while not impacting the layout, viewport size, or display of the
    // webpage as a whole.
    // Add !important and relevant CSS rule resets
    // so that other rules cannot affect the results.
    const container = this.window.document.createElement('div');
    container.setAttribute('style', important('width:0; height:0; overflow:hidden; visibility:hidden; position: absolute;'));
    container.appendChild(div);

    document.body.appendChild(container);
    let zoom = 1000 / div.clientHeight;
    zoom = Math.round(zoom * 100) / 100;
    document.body.removeChild(container);

    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  /**
   * no real trick; device-pixel-ratio is the ratio of device dpi / css dpi.
   * (Note that this is a different interpretation than Webkit's device
   * pixel ratio, which is the ratio device dpi / system dpi).
   *
   * Also, for Mozilla, there is no difference between the zoom factor and the device ratio.
   */
  private firefox4() {
    const zoom = Math.round(this.mediaQueryBinarySearch('min--moz-device-pixel-ratio', '', 0, 10, 20, 0.0001) * 100) / 100;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom,
    };
  }

  /**
   * Firefox 18.x
   * Mozilla added support for devicePixelRatio to Firefox 18,
   * but it is affected by the zoom level, so, like in older
   * Firefox we can't tell if we are in zoom mode or in a device
   * with a different pixel ratio
   */
  private firefox18() {
    return {
      zoom: this.firefox4().zoom,
      devicePxPerCssPx: this.devicePixelRatio,
    };
  }

  /**
   * works starting Opera 11.11
   * the trick: outerWidth is the viewport width including scrollbars in
   * system px, while innerWidth is the viewport width including scrollbars
   * in CSS px
   */
  private opera11() {
    const zoom = Math.round((this.window.top.outerWidth / this.window.top.innerWidth) * 100) / 100;
    return {
      zoom: zoom,
      devicePxPerCssPx: zoom * this.devicePixelRatio,
    };
  }

  binarySearch(property: string, unit: string, a: number, b: number, maxIter: number, epsilon: number, matchMedia: any) {
    const mid = (a + b) / 2;
    if (maxIter <= 0 || b - a < epsilon) {
      return mid;
    }
    const query = '(' + property + ':' + mid + unit + ')';
    if (matchMedia(query).matches) {
      return this.binarySearch(property, unit, mid, b, maxIter - 1, epsilon, matchMedia);
    } else {
      return this.binarySearch(property, unit, a, mid, maxIter - 1, epsilon, matchMedia);
    }
  }

  /**
   * Use a binary search through media queries to find zoom level in Firefox
   */
  private mediaQueryBinarySearch(property: string, unit: string, a: number, b: number, maxIter: number, epsilon: number) {
    let matchMedia;
    let head, style, div;
    if (window.matchMedia) {
      matchMedia = window.matchMedia;
    } else {
      head = document.getElementsByTagName('head')[0];
      style = document.createElement('style');
      head.appendChild(style);

      div = document.createElement('div');
      div.className = 'mediaQueryBinarySearch';
      div.style.display = 'none';
      document.body.appendChild(div);

      matchMedia = function (query) {
        style.sheet.insertRule('@media ' + query + '{.mediaQueryBinarySearch ' + '{text-decoration: underline} }', 0);
        const matched = getComputedStyle(div, null).textDecoration === 'underline';
        style.sheet.deleteRule(0);
        return { matches: matched };
      };
    }
    const ratio = this.binarySearch(property, unit, a, b, maxIter, epsilon, matchMedia);
    if (div) {
      head.removeChild(style);
      document.body.removeChild(div);
    }
    return ratio;
  }

  private detect() {
    //IE8+
    if (!isNaN((this.window.screen as any).logicalXDPI) && !isNaN((this.window.screen as any).systemXDPI)) {
      return this.ie8();
    }
    // IE10+ / Touch
    else if (this.window.navigator.msMaxTouchPoints) {
      return this.ie10();
    }
    //chrome
    else if (!!(this.window as any).chrome && !(!!(this.window as any).opera || this.window.navigator.userAgent.indexOf(' Opera') >= 0)) {
      return this.chrome();
    }
    //safari
    else if (Object.prototype.toString.call((this.window as any).HTMLElement).indexOf('Constructor') > 0) {
      return this.safari();
    }
    //Mobile Webkit
    else if ('orientation' in this.window && 'webkitRequestAnimationFrame' in this.window) {
      return this.webkitMobile();
    }
    //WebKit
    else if ('webkitRequestAnimationFrame' in this.window) {
      return this.webkit();
    }
    //Opera
    else if ((this.window as any).navigator.userAgent.indexOf('Opera') >= 0) {
      return this.opera11();
    }
    //Last one is Firefox
    //FF 18.x
    else if ((this.window as any).devicePixelRatio) {
      return this.firefox18();
    }
    //FF 4.0 - 17.x
    else if (this.firefox4().zoom > 0.001) {
      return this.firefox4();
    }

    return this.fallback();
  }

  zoom() {
    return this.detect().zoom;
  }

  /**
   * Ratios.devicePxPerCssPx shorthand
   */
  device() {
    return this.detect().devicePxPerCssPx;
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions