<!– @license Copyright © 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt The complete set of authors may be found at polymer.github.io/AUTHORS.txt The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at polymer.github.io/PATENTS.txt –>

<link rel=“import” href=“../polymer/polymer.html”> <link rel=“import” href=“../iron-flex-layout/iron-flex-layout.html”>

<!– `iron-image` is an element for displaying an image that provides useful sizing and preloading options not found on the standard `<img>` tag.

The `sizing` option allows the image to be either cropped (`cover`) or letterboxed (`contain`) to fill a fixed user-size placed on the element.

The `preload` option prevents the browser from rendering the image until the image is fully loaded. In the interim, either the element's CSS `background-color` can be be used as the placeholder, or the `placeholder` property can be set to a URL (preferably a data-URI, for instant rendering) for an placeholder image.

The `fade` option (only valid when `preload` is set) will cause the placeholder image/color to be faded out once the image is rendered.

Examples:

Basically identical to `<img src="...">` tag:

  <iron-image src="http://lorempixel.com/400/400"></iron-image>

Will letterbox the image to fit:

  <iron-image style="width:400px; height:400px;" sizing="contain"
    src="http://lorempixel.com/600/400"></iron-image>

Will crop the image to fit:

  <iron-image style="width:400px; height:400px;" sizing="cover"
    src="http://lorempixel.com/600/400"></iron-image>

Will show light-gray background until the image loads:

  <iron-image style="width:400px; height:400px; background-color: lightgray;"
    sizing="cover" preload src="http://lorempixel.com/600/400"></iron-image>

Will show a base-64 encoded placeholder image until the image loads:

  <iron-image style="width:400px; height:400px;" placeholder="data:image/gif;base64,..."
    sizing="cover" preload src="http://lorempixel.com/600/400"></iron-image>

Will fade the light-gray background out once the image is loaded:

  <iron-image style="width:400px; height:400px; background-color: lightgray;"
    sizing="cover" preload fade src="http://lorempixel.com/600/400"></iron-image>

Custom property | Description | Default —————-|————-|———- `–iron-image-placeholder` | Mixin applied to placeholder | `{}` `–iron-image-width` | Sets the width of the wrapped image | `auto` `–iron-image-height` | Sets the height of the wrapped image | `auto`

@group Iron Elements @element iron-image @demo demo/index.html –>

<dom-module id=“iron-image”>

<template>
  <style>
    :host {
      display: inline-block;
      overflow: hidden;
      position: relative;
    }

    #sizedImgDiv {
      @apply(--layout-fit);

      display: none;
    }

    #img {
      display: block;
      width: var(--iron-image-width, auto);
      height: var(--iron-image-height, auto);
    }

    :host([sizing]) #sizedImgDiv {
      display: block;
    }

    :host([sizing]) #img {
      display: none;
    }

    #placeholder {
      @apply(--layout-fit);

      background-color: inherit;
      opacity: 1;

      @apply(--iron-image-placeholder);
    }

    #placeholder.faded-out {
      transition: opacity 0.5s linear;
      opacity: 0;
    }
  </style>

  <div id="sizedImgDiv"
    role="img"
    hidden$="[[_computeImgDivHidden(sizing)]]"
    aria-hidden$="[[_computeImgDivARIAHidden(alt)]]"
    aria-label$="[[_computeImgDivARIALabel(alt, src)]]"></div>
  <img id="img" alt$="[[alt]]" hidden$="[[_computeImgHidden(sizing)]]">
  <div id="placeholder"
    hidden$="[[_computePlaceholderHidden(preload, fade, loading, loaded)]]"
    class$="[[_computePlaceholderClassName(preload, fade, loading, loaded)]]"></div>
</template>

<script>
  Polymer({
    is: 'iron-image',

    properties: {
      /**
       * The URL of an image.
       */
      src: {
        observer: '_srcChanged',
        type: String,
        value: ''
      },

      /**
       * A short text alternative for the image.
       */
      alt: {
        type: String,
        value: null
      },

      /**
       * When true, the image is prevented from loading and any placeholder is
       * shown.  This may be useful when a binding to the src property is known to
       * be invalid, to prevent 404 requests.
       */
      preventLoad: {
        type: Boolean,
        value: false,
        observer: '_preventLoadChanged'
      },

      /**
       * Sets a sizing option for the image.  Valid values are `contain` (full
       * aspect ratio of the image is contained within the element and
       * letterboxed) or `cover` (image is cropped in order to fully cover the
       * bounds of the element), or `null` (default: image takes natural size).
       */
      sizing: {
        type: String,
        value: null,
        reflectToAttribute: true
      },

      /**
       * When a sizing option is used (`cover` or `contain`), this determines
       * how the image is aligned within the element bounds.
       */
      position: {
        type: String,
        value: 'center'
      },

      /**
       * When `true`, any change to the `src` property will cause the `placeholder`
       * image to be shown until the new image has loaded.
       */
      preload: {
        type: Boolean,
        value: false
      },

      /**
       * This image will be used as a background/placeholder until the src image has
       * loaded.  Use of a data-URI for placeholder is encouraged for instant rendering.
       */
      placeholder: {
        type: String,
        value: null,
        observer: '_placeholderChanged'
      },

      /**
       * When `preload` is true, setting `fade` to true will cause the image to
       * fade into place.
       */
      fade: {
        type: Boolean,
        value: false
      },

      /**
       * Read-only value that is true when the image is loaded.
       */
      loaded: {
        notify: true,
        readOnly: true,
        type: Boolean,
        value: false
      },

      /**
       * Read-only value that tracks the loading state of the image when the `preload`
       * option is used.
       */
      loading: {
        notify: true,
        readOnly: true,
        type: Boolean,
        value: false
      },

      /**
       * Read-only value that indicates that the last set `src` failed to load.
       */
      error: {
        notify: true,
        readOnly: true,
        type: Boolean,
        value: false
      },

      /**
       * Can be used to set the width of image (e.g. via binding); size may also be
       * set via CSS.
       */
      width: {
        observer: '_widthChanged',
        type: Number,
        value: null
      },

      /**
       * Can be used to set the height of image (e.g. via binding); size may also be
       * set via CSS.
       *
       * @attribute height
       * @type number
       * @default null
       */
      height: {
        observer: '_heightChanged',
        type: Number,
        value: null
      },
    },

    observers: [
      '_transformChanged(sizing, position)'
    ],

    ready: function() {
      var img = this.$.img;

      img.onload = function() {
        if (this.$.img.src !== this._resolveSrc(this.src)) return;

        this._setLoading(false);
        this._setLoaded(true);
        this._setError(false);
      }.bind(this);

      img.onerror = function() {
        if (this.$.img.src !== this._resolveSrc(this.src)) return;

        this._reset();

        this._setLoading(false);
        this._setLoaded(false);
        this._setError(true);
      }.bind(this);

      this._resolvedSrc = '';
    },

    _load: function(src) {
      if (src) {
        this.$.img.src = src;
      } else {
        this.$.img.removeAttribute('src');
      }
      this.$.sizedImgDiv.style.backgroundImage = src ? 'url("' + src + '")' : '';

      this._setLoading(!!src);
      this._setLoaded(false);
      this._setError(false);
    },

    _reset: function() {
      this.$.img.removeAttribute('src');
      this.$.sizedImgDiv.style.backgroundImage = '';

      this._setLoading(false);
      this._setLoaded(false);
      this._setError(false);
    },

    _computePlaceholderHidden: function() {
      return !this.preload || (!this.fade && !this.loading && this.loaded);
    },

    _computePlaceholderClassName: function() {
      return (this.preload && this.fade && !this.loading && this.loaded) ? 'faded-out' : '';
    },

    _computeImgDivHidden: function() {
      return !this.sizing;
    },

    _computeImgDivARIAHidden: function() {
      return this.alt === '' ? 'true' : undefined;
    },

    _computeImgDivARIALabel: function() {
      if (this.alt !== null) {
        return this.alt;
      }

      // Polymer.ResolveUrl.resolveUrl will resolve '' relative to a URL x to
      // that URL x, but '' is the default for src.
      if (this.src === '') {
        return '';
      }

      var pathComponents = (new URL(this._resolveSrc(this.src))).pathname.split("/");
      return pathComponents[pathComponents.length - 1];
    },

    _computeImgHidden: function() {
      return !!this.sizing;
    },

    _widthChanged: function() {
      this.style.width = isNaN(this.width) ? this.width : this.width + 'px';
    },

    _heightChanged: function() {
      this.style.height = isNaN(this.height) ? this.height : this.height + 'px';
    },

    _preventLoadChanged: function() {
      if (this.preventLoad || this.loaded) return;

      this._reset();
      this._load(this.src);
    },

    _srcChanged: function(newSrc, oldSrc) {
      var newResolvedSrc = this._resolveSrc(newSrc);
      if (newResolvedSrc === this._resolvedSrc) return;
      this._resolvedSrc = newResolvedSrc;

      this._reset();
      if (!this.preventLoad) {
        this._load(newSrc);
      }
    },

    _placeholderChanged: function() {
      this.$.placeholder.style.backgroundImage =
        this.placeholder ? 'url("' + this.placeholder + '")' : '';
    },

    _transformChanged: function() {
      var sizedImgDivStyle = this.$.sizedImgDiv.style;
      var placeholderStyle = this.$.placeholder.style;

      sizedImgDivStyle.backgroundSize =
      placeholderStyle.backgroundSize =
        this.sizing;

      sizedImgDivStyle.backgroundPosition =
      placeholderStyle.backgroundPosition =
        this.sizing ? this.position : '';

      sizedImgDivStyle.backgroundRepeat =
      placeholderStyle.backgroundRepeat =
        this.sizing ? 'no-repeat' : '';
    },

    _resolveSrc: function(testSrc) {
      var baseURI = /** @type {string} */(this.ownerDocument.baseURI);
      return (new URL(Polymer.ResolveUrl.resolveUrl(testSrc, baseURI), baseURI)).href;
    }
  });
</script>

</dom-module>