// Styles import "../../../src/components/VImg/VImg.sass"; // Directives import intersect from '../../directives/intersect'; // Components import VResponsive from '../VResponsive'; // Mixins import Themeable from '../../mixins/themeable'; // Utils import mixins from '../../util/mixins'; import mergeData from '../../util/mergeData'; import { consoleWarn } from '../../util/console'; const hasIntersect = typeof window !== 'undefined' && 'IntersectionObserver' in window; /* @vue/component */ export default mixins(VResponsive, Themeable).extend({ name: 'v-img', directives: { intersect }, props: { alt: String, contain: Boolean, eager: Boolean, gradient: String, lazySrc: String, options: { type: Object, // For more information on types, navigate to: // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API default: () => ({ root: undefined, rootMargin: undefined, threshold: undefined }) }, position: { type: String, default: 'center center' }, sizes: String, src: { type: [String, Object], default: '' }, srcset: String, transition: { type: [Boolean, String], default: 'fade-transition' } }, data() { return { currentSrc: '', image: null, isLoading: true, calculatedAspectRatio: undefined, naturalWidth: undefined, hasError: false }; }, computed: { computedAspectRatio() { return Number(this.normalisedSrc.aspect || this.calculatedAspectRatio); }, normalisedSrc() { return typeof this.src === 'string' ? { src: this.src, srcset: this.srcset, lazySrc: this.lazySrc, aspect: Number(this.aspectRatio || 0) } : { src: this.src.src, srcset: this.srcset || this.src.srcset, lazySrc: this.lazySrc || this.src.lazySrc, aspect: Number(this.aspectRatio || this.src.aspect) }; }, __cachedImage() { if (!(this.normalisedSrc.src || this.normalisedSrc.lazySrc || this.gradient)) return []; const backgroundImage = []; const src = this.isLoading ? this.normalisedSrc.lazySrc : this.currentSrc; if (this.gradient) backgroundImage.push(`linear-gradient(${this.gradient})`); if (src) backgroundImage.push(`url("${src}")`); const image = this.$createElement('div', { staticClass: 'v-image__image', class: { 'v-image__image--preload': this.isLoading, 'v-image__image--contain': this.contain, 'v-image__image--cover': !this.contain }, style: { backgroundImage: backgroundImage.join(', '), backgroundPosition: this.position }, key: +this.isLoading }); /* istanbul ignore if */ if (!this.transition) return image; return this.$createElement('transition', { attrs: { name: this.transition, mode: 'in-out' } }, [image]); } }, watch: { src() { // Force re-init when src changes if (!this.isLoading) this.init(undefined, undefined, true);else this.loadImage(); }, '$vuetify.breakpoint.width': 'getSrc' }, mounted() { this.init(); }, methods: { init(entries, observer, isIntersecting) { // If the current browser supports the intersection // observer api, the image is not observable, and // the eager prop isn't being used, do not load if (hasIntersect && !isIntersecting && !this.eager) return; if (this.normalisedSrc.lazySrc) { const lazyImg = new Image(); lazyImg.src = this.normalisedSrc.lazySrc; this.pollForSize(lazyImg, null); } /* istanbul ignore else */ if (this.normalisedSrc.src) this.loadImage(); }, onLoad() { this.getSrc(); this.isLoading = false; this.$emit('load', this.src); }, onError() { this.hasError = true; this.$emit('error', this.src); }, getSrc() { /* istanbul ignore else */ if (this.image) this.currentSrc = this.image.currentSrc || this.image.src; }, loadImage() { const image = new Image(); this.image = image; image.onload = () => { /* istanbul ignore if */ if (image.decode) { image.decode().catch(err => { consoleWarn(`Failed to decode image, trying to render anyway\n\n` + `src: ${this.normalisedSrc.src}` + (err.message ? `\nOriginal error: ${err.message}` : ''), this); }).then(this.onLoad); } else { this.onLoad(); } }; image.onerror = this.onError; this.hasError = false; image.src = this.normalisedSrc.src; this.sizes && (image.sizes = this.sizes); this.normalisedSrc.srcset && (image.srcset = this.normalisedSrc.srcset); this.aspectRatio || this.pollForSize(image); this.getSrc(); }, pollForSize(img, timeout = 100) { const poll = () => { const { naturalHeight, naturalWidth } = img; if (naturalHeight || naturalWidth) { this.naturalWidth = naturalWidth; this.calculatedAspectRatio = naturalWidth / naturalHeight; } else { timeout != null && !this.hasError && setTimeout(poll, timeout); } }; poll(); }, genContent() { const content = VResponsive.options.methods.genContent.call(this); if (this.naturalWidth) { this._b(content.data, 'div', { style: { width: `${this.naturalWidth}px` } }); } return content; }, __genPlaceholder() { if (this.$slots.placeholder) { const placeholder = this.isLoading ? [this.$createElement('div', { staticClass: 'v-image__placeholder' }, this.$slots.placeholder)] : []; if (!this.transition) return placeholder[0]; return this.$createElement('transition', { props: { appear: true, name: this.transition } }, placeholder); } } }, render(h) { const node = VResponsive.options.render.call(this, h); const data = mergeData(node.data, { staticClass: 'v-image', attrs: { 'aria-label': this.alt, role: this.alt ? 'img' : undefined }, class: this.themeClasses, // Only load intersect directive if it // will work in the current browser. directives: hasIntersect ? [{ name: 'intersect', modifiers: { once: true }, value: { handler: this.init, options: this.options } }] : undefined }); node.children = [this.__cachedSizer, this.__cachedImage, this.__genPlaceholder(), this.genContent()]; return h(node.tag, data, node.children); } }); //# sourceMappingURL=VImg.js.map