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