| <!-- |
| @license |
| Copyright (c) 2016 The Polymer Project Authors. All rights reserved. |
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt |
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| The complete set of contributors may be found at http://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 http://polymer.github.io/PATENTS.txt |
| --> |
| |
| <link rel="import" href="../polymer/polymer.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; |
| } |
| |
| #baseURIAnchor { |
| display: none; |
| } |
| |
| #sizedImgDiv { |
| position: absolute; |
| top: 0px; |
| right: 0px; |
| bottom: 0px; |
| left: 0px; |
| |
| 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 { |
| position: absolute; |
| top: 0px; |
| right: 0px; |
| bottom: 0px; |
| left: 0px; |
| |
| background-color: inherit; |
| opacity: 1; |
| |
| @apply --iron-image-placeholder; |
| } |
| |
| #placeholder.faded-out { |
| transition: opacity 0.5s linear; |
| opacity: 0; |
| } |
| </style> |
| |
| <a id="baseURIAnchor" href="#"></a> |
| <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)]]" |
| crossorigin$="[[crossorigin]]" |
| on-load="_imgOnLoad" |
| on-error="_imgOnError"> |
| <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: {type: String, value: ''}, |
| |
| /** |
| * A short text alternative for the image. |
| */ |
| alt: {type: String, value: null}, |
| |
| /** |
| * CORS enabled images support: |
| * https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image |
| */ |
| crossorigin: {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}, |
| |
| /** |
| * 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)', |
| '_loadStateObserver(src, preventLoad)' |
| ], |
| |
| created: function() { |
| this._resolvedSrc = ''; |
| }, |
| |
| _imgOnLoad: function() { |
| if (this.$.img.src !== this._resolveSrc(this.src)) { |
| return; |
| } |
| |
| this._setLoading(false); |
| this._setLoaded(true); |
| this._setError(false); |
| }, |
| |
| _imgOnError: function() { |
| if (this.$.img.src !== this._resolveSrc(this.src)) { |
| return; |
| } |
| |
| this.$.img.removeAttribute('src'); |
| this.$.sizedImgDiv.style.backgroundImage = ''; |
| |
| this._setLoading(false); |
| this._setLoaded(false); |
| this._setError(true); |
| }, |
| |
| _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 ''; |
| } |
| |
| // NOTE: Use of `URL` was removed here because IE11 doesn't support |
| // constructing it. If this ends up being problematic, we should |
| // consider reverting and adding the URL polyfill as a dev dependency. |
| var resolved = this._resolveSrc(this.src); |
| // Remove query parts, get file name. |
| return resolved.replace(/[?|#].*/g, '').split('/').pop(); |
| }, |
| |
| _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'; |
| }, |
| |
| _loadStateObserver: function(src, preventLoad) { |
| var newResolvedSrc = this._resolveSrc(src); |
| if (newResolvedSrc === this._resolvedSrc) { |
| return; |
| } |
| |
| this._resolvedSrc = ''; |
| this.$.img.removeAttribute('src'); |
| this.$.sizedImgDiv.style.backgroundImage = ''; |
| |
| if (src === '' || preventLoad) { |
| this._setLoading(false); |
| this._setLoaded(false); |
| this._setError(false); |
| } else { |
| this._resolvedSrc = newResolvedSrc; |
| this.$.img.src = this._resolvedSrc; |
| this.$.sizedImgDiv.style.backgroundImage = |
| 'url("' + this._resolvedSrc + '")'; |
| |
| this._setLoading(true); |
| this._setLoaded(false); |
| this._setError(false); |
| } |
| }, |
| |
| _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 resolved = |
| Polymer.ResolveUrl.resolveUrl(testSrc, this.$.baseURIAnchor.href); |
| // NOTE: Use of `URL` was removed here because IE11 doesn't support |
| // constructing it. If this ends up being problematic, we should |
| // consider reverting and adding the URL polyfill as a dev dependency. |
| if (resolved[0] === '/') { |
| // In IE location.origin might not work |
| // https://connect.microsoft.com/IE/feedback/details/1763802/location-origin-is-undefined-in-ie-11-on-windows-10-but-works-on-windows-7 |
| resolved = (location.origin || location.protocol + '//' + location.host) + |
| resolved; |
| } |
| return resolved; |
| } |
| }); |
| </script> |
| </dom-module> |