• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--
2@license
3Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7Code distributed by Google as part of the polymer project is also
8subject 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
16preloading options not found on the standard `<img>` tag.
17
18The `sizing` option allows the image to be either cropped (`cover`) or
19letterboxed (`contain`) to fill a fixed user-size placed on the element.
20
21The `preload` option prevents the browser from rendering the image until the
22image is fully loaded.  In the interim, either the element's CSS `background-color`
23can be be used as the placeholder, or the `placeholder` property can be
24set to a URL (preferably a data-URI, for instant rendering) for an
25placeholder image.
26
27The `fade` option (only valid when `preload` is set) will cause the placeholder
28image/color to be faded out once the image is rendered.
29
30Examples:
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
61Custom 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