Home | History | Annotate | Download | only in iron-image
      1 <!--
      2 @license
      3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
      4 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
      5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
      6 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
      7 Code distributed by Google as part of the polymer project is also
      8 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
      9 -->
     10 
     11 <link rel="import" href="../polymer/polymer.html">
     12 <link rel="import" href="../iron-flex-layout/iron-flex-layout.html">
     13 
     14 <!--
     15 `iron-image` is an element for displaying an image that provides useful sizing and
     16 preloading options not found on the standard `<img>` tag.
     17 
     18 The `sizing` option allows the image to be either cropped (`cover`) or
     19 letterboxed (`contain`) to fill a fixed user-size placed on the element.
     20 
     21 The `preload` option prevents the browser from rendering the image until the
     22 image is fully loaded.  In the interim, either the element's CSS `background-color`
     23 can be be used as the placeholder, or the `placeholder` property can be
     24 set to a URL (preferably a data-URI, for instant rendering) for an
     25 placeholder image.
     26 
     27 The `fade` option (only valid when `preload` is set) will cause the placeholder
     28 image/color to be faded out once the image is rendered.
     29 
     30 Examples:
     31 
     32   Basically identical to `<img src="...">` tag:
     33 
     34     <iron-image src="http://lorempixel.com/400/400"></iron-image>
     35 
     36   Will letterbox the image to fit:
     37 
     38     <iron-image style="width:400px; height:400px;" sizing="contain"
     39       src="http://lorempixel.com/600/400"></iron-image>
     40 
     41   Will crop the image to fit:
     42 
     43     <iron-image style="width:400px; height:400px;" sizing="cover"
     44       src="http://lorempixel.com/600/400"></iron-image>
     45 
     46   Will show light-gray background until the image loads:
     47 
     48     <iron-image style="width:400px; height:400px; background-color: lightgray;"
     49       sizing="cover" preload src="http://lorempixel.com/600/400"></iron-image>
     50 
     51   Will show a base-64 encoded placeholder image until the image loads:
     52 
     53     <iron-image style="width:400px; height:400px;" placeholder="data:image/gif;base64,..."
     54       sizing="cover" preload src="http://lorempixel.com/600/400"></iron-image>
     55 
     56   Will fade the light-gray background out once the image is loaded:
     57 
     58     <iron-image style="width:400px; height:400px; background-color: lightgray;"
     59       sizing="cover" preload fade src="http://lorempixel.com/600/400"></iron-image>
     60 
     61 Custom property | Description | Default
     62 ----------------|-------------|----------
     63 `--iron-image-placeholder` | Mixin applied to #placeholder | `{}`
     64 `--iron-image-width` | Sets the width of the wrapped image | `auto`
     65 `--iron-image-height` | Sets the height of the wrapped image | `auto`
     66 
     67 @group Iron Elements
     68 @element iron-image
     69 @demo demo/index.html
     70 -->
     71 
     72 <dom-module id="iron-image">
     73   <template>
     74     <style>
     75       :host {
     76         display: inline-block;
     77         overflow: hidden;
     78         position: relative;
     79       }
     80 
     81       #sizedImgDiv {
     82         @apply(--layout-fit);
     83 
     84         display: none;
     85       }
     86 
     87       #img {
     88         display: block;
     89         width: var(--iron-image-width, auto);
     90         height: var(--iron-image-height, auto);
     91       }
     92 
     93       :host([sizing]) #sizedImgDiv {
     94         display: block;
     95       }
     96 
     97       :host([sizing]) #img {
     98         display: none;
     99       }
    100 
    101       #placeholder {
    102         @apply(--layout-fit);
    103 
    104         background-color: inherit;
    105         opacity: 1;
    106 
    107         @apply(--iron-image-placeholder);
    108       }
    109 
    110       #placeholder.faded-out {
    111         transition: opacity 0.5s linear;
    112         opacity: 0;
    113       }
    114     </style>
    115 
    116     <div id="sizedImgDiv"
    117       role="img"
    118       hidden$="[[_computeImgDivHidden(sizing)]]"
    119       aria-hidden$="[[_computeImgDivARIAHidden(alt)]]"
    120       aria-label$="[[_computeImgDivARIALabel(alt, src)]]"></div>
    121     <img id="img" alt$="[[alt]]" hidden$="[[_computeImgHidden(sizing)]]">
    122     <div id="placeholder"
    123       hidden$="[[_computePlaceholderHidden(preload, fade, loading, loaded)]]"
    124       class$="[[_computePlaceholderClassName(preload, fade, loading, loaded)]]"></div>
    125   </template>
    126 
    127   <script>
    128     Polymer({
    129       is: 'iron-image',
    130 
    131       properties: {
    132         /**
    133          * The URL of an image.
    134          */
    135         src: {
    136           observer: '_srcChanged',
    137           type: String,
    138           value: ''
    139         },
    140 
    141         /**
    142          * A short text alternative for the image.
    143          */
    144         alt: {
    145           type: String,
    146           value: null
    147         },
    148 
    149         /**
    150          * When true, the image is prevented from loading and any placeholder is
    151          * shown.  This may be useful when a binding to the src property is known to
    152          * be invalid, to prevent 404 requests.
    153          */
    154         preventLoad: {
    155           type: Boolean,
    156           value: false,
    157           observer: '_preventLoadChanged'
    158         },
    159 
    160         /**
    161          * Sets a sizing option for the image.  Valid values are `contain` (full
    162          * aspect ratio of the image is contained within the element and
    163          * letterboxed) or `cover` (image is cropped in order to fully cover the
    164          * bounds of the element), or `null` (default: image takes natural size).
    165          */
    166         sizing: {
    167           type: String,
    168           value: null,
    169           reflectToAttribute: true
    170         },
    171 
    172         /**
    173          * When a sizing option is used (`cover` or `contain`), this determines
    174          * how the image is aligned within the element bounds.
    175          */
    176         position: {
    177           type: String,
    178           value: 'center'
    179         },
    180 
    181         /**
    182          * When `true`, any change to the `src` property will cause the `placeholder`
    183          * image to be shown until the new image has loaded.
    184          */
    185         preload: {
    186           type: Boolean,
    187           value: false
    188         },
    189 
    190         /**
    191          * This image will be used as a background/placeholder until the src image has
    192          * loaded.  Use of a data-URI for placeholder is encouraged for instant rendering.
    193          */
    194         placeholder: {
    195           type: String,
    196           value: null,
    197           observer: '_placeholderChanged'
    198         },
    199 
    200         /**
    201          * When `preload` is true, setting `fade` to true will cause the image to
    202          * fade into place.
    203          */
    204         fade: {
    205           type: Boolean,
    206           value: false
    207         },
    208 
    209         /**
    210          * Read-only value that is true when the image is loaded.
    211          */
    212         loaded: {
    213           notify: true,
    214           readOnly: true,
    215           type: Boolean,
    216           value: false
    217         },
    218 
    219         /**
    220          * Read-only value that tracks the loading state of the image when the `preload`
    221          * option is used.
    222          */
    223         loading: {
    224           notify: true,
    225           readOnly: true,
    226           type: Boolean,
    227           value: false
    228         },
    229 
    230         /**
    231          * Read-only value that indicates that the last set `src` failed to load.
    232          */
    233         error: {
    234           notify: true,
    235           readOnly: true,
    236           type: Boolean,
    237           value: false
    238         },
    239 
    240         /**
    241          * Can be used to set the width of image (e.g. via binding); size may also be
    242          * set via CSS.
    243          */
    244         width: {
    245           observer: '_widthChanged',
    246           type: Number,
    247           value: null
    248         },
    249 
    250         /**
    251          * Can be used to set the height of image (e.g. via binding); size may also be
    252          * set via CSS.
    253          *
    254          * @attribute height
    255          * @type number
    256          * @default null
    257          */
    258         height: {
    259           observer: '_heightChanged',
    260           type: Number,
    261           value: null
    262         },
    263       },
    264 
    265       observers: [
    266         '_transformChanged(sizing, position)'
    267       ],
    268 
    269       ready: function() {
    270         var img = this.$.img;
    271 
    272         img.onload = function() {
    273           if (this.$.img.src !== this._resolveSrc(this.src)) return;
    274 
    275           this._setLoading(false);
    276           this._setLoaded(true);
    277           this._setError(false);
    278         }.bind(this);
    279 
    280         img.onerror = function() {
    281           if (this.$.img.src !== this._resolveSrc(this.src)) return;
    282 
    283           this._reset();
    284 
    285           this._setLoading(false);
    286           this._setLoaded(false);
    287           this._setError(true);
    288         }.bind(this);
    289 
    290         this._resolvedSrc = '';
    291       },
    292 
    293       _load: function(src) {
    294         if (src) {
    295           this.$.img.src = src;
    296         } else {
    297           this.$.img.removeAttribute('src');
    298         }
    299         this.$.sizedImgDiv.style.backgroundImage = src ? 'url("' + src + '")' : '';
    300 
    301         this._setLoading(!!src);
    302         this._setLoaded(false);
    303         this._setError(false);
    304       },
    305 
    306       _reset: function() {
    307         this.$.img.removeAttribute('src');
    308         this.$.sizedImgDiv.style.backgroundImage = '';
    309 
    310         this._setLoading(false);
    311         this._setLoaded(false);
    312         this._setError(false);
    313       },
    314 
    315       _computePlaceholderHidden: function() {
    316         return !this.preload || (!this.fade && !this.loading && this.loaded);
    317       },
    318 
    319       _computePlaceholderClassName: function() {
    320         return (this.preload && this.fade && !this.loading && this.loaded) ? 'faded-out' : '';
    321       },
    322 
    323       _computeImgDivHidden: function() {
    324         return !this.sizing;
    325       },
    326 
    327       _computeImgDivARIAHidden: function() {
    328         return this.alt === '' ? 'true' : undefined;
    329       },
    330 
    331       _computeImgDivARIALabel: function() {
    332         if (this.alt !== null) {
    333           return this.alt;
    334         }
    335 
    336         // Polymer.ResolveUrl.resolveUrl will resolve '' relative to a URL x to
    337         // that URL x, but '' is the default for src.
    338         if (this.src === '') {
    339           return '';
    340         }
    341 
    342         var pathComponents = (new URL(this._resolveSrc(this.src))).pathname.split("/");
    343         return pathComponents[pathComponents.length - 1];
    344       },
    345 
    346       _computeImgHidden: function() {
    347         return !!this.sizing;
    348       },
    349 
    350       _widthChanged: function() {
    351         this.style.width = isNaN(this.width) ? this.width : this.width + 'px';
    352       },
    353 
    354       _heightChanged: function() {
    355         this.style.height = isNaN(this.height) ? this.height : this.height + 'px';
    356       },
    357 
    358       _preventLoadChanged: function() {
    359         if (this.preventLoad || this.loaded) return;
    360 
    361         this._reset();
    362         this._load(this.src);
    363       },
    364 
    365       _srcChanged: function(newSrc, oldSrc) {
    366         var newResolvedSrc = this._resolveSrc(newSrc);
    367         if (newResolvedSrc === this._resolvedSrc) return;
    368         this._resolvedSrc = newResolvedSrc;
    369 
    370         this._reset();
    371         if (!this.preventLoad) {
    372           this._load(newSrc);
    373         }
    374       },
    375 
    376       _placeholderChanged: function() {
    377         this.$.placeholder.style.backgroundImage =
    378           this.placeholder ? 'url("' + this.placeholder + '")' : '';
    379       },
    380 
    381       _transformChanged: function() {
    382         var sizedImgDivStyle = this.$.sizedImgDiv.style;
    383         var placeholderStyle = this.$.placeholder.style;
    384 
    385         sizedImgDivStyle.backgroundSize =
    386         placeholderStyle.backgroundSize =
    387           this.sizing;
    388 
    389         sizedImgDivStyle.backgroundPosition =
    390         placeholderStyle.backgroundPosition =
    391           this.sizing ? this.position : '';
    392 
    393         sizedImgDivStyle.backgroundRepeat =
    394         placeholderStyle.backgroundRepeat =
    395           this.sizing ? 'no-repeat' : '';
    396       },
    397 
    398       _resolveSrc: function(testSrc) {
    399         return Polymer.ResolveUrl.resolveUrl(testSrc, this.ownerDocument.baseURI);
    400       }
    401     });
    402   </script>
    403 </dom-module>
    404